diff --git a/config/act4_dungeons_configuration.yaml b/config/act4_dungeons_configuration.yaml new file mode 100644 index 0000000..a253403 --- /dev/null +++ b/config/act4_dungeons_configuration.yaml @@ -0,0 +1,21 @@ +dungeon_portal_map_id: 0 +dungeon_portal_map_x: 0 +dungeon_portal_map_y: 0 +dungeon_return_portal_map_id: 0 +dungeon_return_portal_map_x: 0 +dungeon_return_portal_map_y: 0 +dungeon_entry_cost_multiplier: 0 +dungeon_death_revival_delay: 00:00:20 +dungeon_duration: 01:00:00 +dungeon_boss_map_closure_after_reward: 00:00:30 +dungeon_slow_mo_delay: 00:00:07 +guardians_for_angels: +- monster_vnum: 0 + map_x: 0 + map_y: 0 + direction: 0 +guardians_for_demons: +- monster_vnum: 0 + map_x: 0 + map_y: 0 + direction: 0 diff --git a/config/bank_reputation_configuration.yaml b/config/bank_reputation_configuration.yaml new file mode 100644 index 0000000..978c292 --- /dev/null +++ b/config/bank_reputation_configuration.yaml @@ -0,0 +1,2 @@ +bank_ranks: +bank_penalties: diff --git a/config/base_character.yaml b/config/base_character.yaml new file mode 100644 index 0000000..bf93fc5 --- /dev/null +++ b/config/base_character.yaml @@ -0,0 +1,112 @@ +character: + account_id: 0 + act4_dead: 0 + act4_kill: 0 + act4_points: 0 + arena_winner: 0 + biography: + buff_blocked: false + class: Adventurer + compliment: 0 + dignity: 0 + emoticons_blocked: false + exchange_blocked: false + faction: Neutral + family_request_blocked: false + friend_request_blocked: false + gender: Male + gold: 0 + group_request_blocked: false + hair_color: Black + hair_style: A + hero_chat_blocked: false + hero_level: 0 + hero_xp: 0 + hp: 221 + hp_blocked: false + is_pet_auto_relive: false + is_partner_auto_relive: false + job_level: 1 + job_level_xp: 0 + level: 1 + level_xp: 0 + map_id: 1 + map_x: 78 + map_y: 109 + master_points: 0 + master_ticket: 0 + max_pet_count: 10 + max_partner_count: 3 + miniland_invite_blocked: false + miniland_message: '' + miniland_point: 0 + miniland_state: OPEN + mouse_aim_lock: false + mp: 221 + prefix: + name: template + quick_get_up: false + hide_hat: false + ui_blocked: false + rage_point: 0 + reput: 0 + slot: 0 + sp_points_bonus: 0 + sp_points_basic: 10000 + talent_lose: 0 + talent_surrender: 0 + talent_win: 0 + whisper_blocked: false + partner_inventory: [] + nos_mates: [] + partner_warehouse: [] + bonus: [] + static_buffs: [] + quicklist: [] + learned_skills: [] + titles: [] + completed_scripts: [] + completed_periodic_quests: [] + active_quests: [] + miniland_objects: [] + respawn_type: NOSVILLE_SPAWN + return_point: + inventory: [] + equipped_stuffs: [] + lifetime_stats: + total_monsters_killed: 0 + total_players_killed: 0 + total_deaths_by_monster: 0 + total_deaths_by_player: 0 + total_skills_casted: 0 + total_damage_dealt: 0 + total_raids_won: 0 + total_raids_lost: 0 + total_timespaces_won: 0 + total_timespaces_lost: 0 + total_instant_battle_won: 0 + total_icebreaker_won: 0 + total_gold_spent: 0 + total_gold_spent_in_bazaar_items: 0 + total_gold_spent_in_bazaar_fees: 0 + total_gold_dropped: 0 + total_gold_earned_in_bazaar_items: 0 + total_gold_spent_in_npc_shop: 0 + total_items_used: 0 + total_potions_used: 0 + total_snacks_used: 0 + total_food_used: 0 + total_miniland_visits: 0 + total_time_online: 00:00:00 + total_arena_deaths: 0 + total_arena_kills: 0 + completed_quests: [] + completed_time_spaces: [] + raid_restriction_dto: + lord_draco: 0 + glacerus: 0 + act5_respawn_type: MORTAZ_DESERT_PORT + rainbow_battle_leaver_buster_dto: + exits: 0 + reward_penalty: 0 + id: 0 diff --git a/config/base_inventory.yaml b/config/base_inventory.yaml new file mode 100644 index 0000000..e58218d --- /dev/null +++ b/config/base_inventory.yaml @@ -0,0 +1,21 @@ +items: +- vnum: 1 + quantity: 1 + slot: 0 + inventory_type: EquippedItems +- vnum: 12 + quantity: 1 + slot: 1 + inventory_type: EquippedItems +- vnum: 8 + quantity: 1 + slot: 5 + inventory_type: EquippedItems +- vnum: 2024 + quantity: 10 + slot: 0 + inventory_type: Etc +- vnum: 2081 + quantity: 1 + slot: 1 + inventory_type: Etc diff --git a/config/base_quicklist.yaml b/config/base_quicklist.yaml new file mode 100644 index 0000000..a0e4abf --- /dev/null +++ b/config/base_quicklist.yaml @@ -0,0 +1,29 @@ +quicklist: +- morph: 0 + inv_slot_or_skill_slot_or_skill_vnum: 1 + quicklist_tab: 0 + quicklist_slot: 0 + inventory_type_or_skill_tab: 1 + type: SKILLS + skill_vnum: +- morph: 0 + inv_slot_or_skill_slot_or_skill_vnum: 0 + quicklist_tab: 0 + quicklist_slot: 1 + inventory_type_or_skill_tab: 2 + type: ITEM + skill_vnum: +- morph: 0 + inv_slot_or_skill_slot_or_skill_vnum: 16 + quicklist_tab: 0 + quicklist_slot: 8 + inventory_type_or_skill_tab: 1 + type: SKILLS + skill_vnum: +- morph: 0 + inv_slot_or_skill_slot_or_skill_vnum: 1 + quicklist_tab: 0 + quicklist_slot: 9 + inventory_type_or_skill_tab: 3 + type: SKILLS + skill_vnum: diff --git a/config/base_skill.yaml b/config/base_skill.yaml new file mode 100644 index 0000000..8af113f --- /dev/null +++ b/config/base_skill.yaml @@ -0,0 +1,4 @@ +skills: +- skill_v_num: 200 +- skill_v_num: 201 +- skill_v_num: 209 diff --git a/config/bazaar_configuration.yaml b/config/bazaar_configuration.yaml new file mode 100644 index 0000000..27d48cd --- /dev/null +++ b/config/bazaar_configuration.yaml @@ -0,0 +1,5 @@ +maximum_listed_items: 30 +maximum_listed_items_medal: 90 +delay_client_between_requests_in_secs: 3 +delay_server_between_requests_in_secs: 1 +items_per_index: 30 diff --git a/config/buffs_duration_configuration.yaml b/config/buffs_duration_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/buffs_duration_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/cella_refiners_configuration.yaml b/config/cella_refiners_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/cella_refiners_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/costume_scroll_morphs.yaml b/config/costume_scroll_morphs.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/costume_scroll_morphs.yaml @@ -0,0 +1 @@ +[] diff --git a/config/drop_rarity_configuration.yaml b/config/drop_rarity_configuration.yaml new file mode 100644 index 0000000..3fe589b --- /dev/null +++ b/config/drop_rarity_configuration.yaml @@ -0,0 +1,2 @@ +equipment: +shells: diff --git a/config/family_configuration.yaml b/config/family_configuration.yaml new file mode 100644 index 0000000..792a7a4 --- /dev/null +++ b/config/family_configuration.yaml @@ -0,0 +1,23 @@ +creation_is_group_required: false +creation_group_members_required: 3 +creation_price: 200000 +minimum_name_length: 3 +maximum_name_length: 20 +deputy_limit: 2 +keeper_limit: 999 +time_between_family_rejoin: 1.00:00:00 +default_membership_capacity: 20 +upgrades: [] +levels: +- level: 1 + experience_range: + minimum: 0 + maximum: 99999 +- level: 2 + experience_range: + minimum: 100000 + maximum: 219999 +- level: 3 + experience_range: + minimum: 220000 + maximum: 369999 diff --git a/config/game_min_max_configuration.yaml b/config/game_min_max_configuration.yaml new file mode 100644 index 0000000..e01923f --- /dev/null +++ b/config/game_min_max_configuration.yaml @@ -0,0 +1,19 @@ +max_level: 99 +max_mate_level: 99 +max_job_level: 80 +max_sp_level: 99 +max_hero_level: 60 +hero_min_level: 88 +min_lod_level: 55 +max_gold: 1000000000 +max_bank_gold: 100000000000 +max_bot_code_attempts: 3 +max_dignity: 200 +min_dignity: -1000 +max_reputation: 9223372036854775807 +min_reputation: 0 +max_mate_loyalty: 1000 +min_mate_loyalty: 0 +max_npc_talk_range: 4 +max_sp_additional_points: 1000000 +max_sp_base_points: 1000 diff --git a/config/game_rate_configuration.yaml b/config/game_rate_configuration.yaml new file mode 100644 index 0000000..eb5ee4d --- /dev/null +++ b/config/game_rate_configuration.yaml @@ -0,0 +1,15 @@ +mob_xp_rate: 1 +job_xp_rate: 1 +hero_xp_rate: 1 +fairy_xp_rate: 1 +mate_xp_rate: 1 +partner_xp_rate: 1 +family_xp_rate: 1 +reput_rate: 1 +mob_drop_rate: 1 +mob_drop_chance: 1 +gold_drop_rate: 1 +gold_rate: 1 +gold_drop_chance: 1 +generic_drop_rate: 1 +generic_drop_chance: 1 diff --git a/config/game_revival_configuration.yaml b/config/game_revival_configuration.yaml new file mode 100644 index 0000000..16f1a51 --- /dev/null +++ b/config/game_revival_configuration.yaml @@ -0,0 +1,27 @@ +player_revival_configuration: + player_revival_penalization: + max_level_without_revival_penalization: 20 + base_map_revival_penalization_saver: 1012 + base_map_revival_penalization_saver_amount: 10 + base_map_revival_penalization_debuff: 44 + max_level_with_dignity_penalization_increment: 50 + dignity_penalization_increment_multiplier: 1 + arena_gold_penalization: 100 + revival_dialog_delay: 00:00:02 + forced_revival_delay: 00:00:30 + act4_seal_revival_delay: 00:00:02 + act4_revival_delay: 00:00:30 +mate_revival_configuration: + mate_instant_revival_penalization_saver: + - 10016 + - 2089 + mate_instant_revival_penalization_saver_amount: 1 + partner_instant_revival_penalization_saver: + - 10050 + - 2329 + partner_instant_revival_penalization_saver_amount: 1 + delayed_revival_delay: 00:03:00 + delayed_revival_penalization_saver: 1012 + delayed_revival_penalization_saver_amount: 5 + loyalty_death_penalization_amount: 50 + no_loyalty_death_penalization_min_authority: VipPlus diff --git a/config/general_quests_configuration.yaml b/config/general_quests_configuration.yaml new file mode 100644 index 0000000..ca4d5e1 --- /dev/null +++ b/config/general_quests_configuration.yaml @@ -0,0 +1 @@ +general_quests: diff --git a/config/gibberish_configuration.yaml b/config/gibberish_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/gibberish_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/global_minigame_configuration.yaml b/config/global_minigame_configuration.yaml new file mode 100644 index 0000000..832f63c --- /dev/null +++ b/config/global_minigame_configuration.yaml @@ -0,0 +1,18 @@ +maxmimum_minigame_points: 2000 +minigame_points_cost_per_minigame: 100 +production_coupon_vnum: 1271 +production_coupon_points_amount: 500 +repair_durability_gold_cost: 100 +repair_durability_coupon_vnum: 1269 +durability_coupon_repairing_amount: 300 +durability_warning: 1000 +minigame_maximum_rewards: 999 +double_reward_coupon_vnum: 1270 +minigame_rewards_inventory_warning: 880 +minigame_rewards_inventory_ultimatum: 970 +anti_exploit_configuration: + minigame_abuse_detection_threshold: 0.5 + common_time_expended_in_minigames_per_day: 02:00:00 + percentage_for_same_score_check: 0.5 + use_same_score_check_at_x_minigames: 10 + give_rewards_to_possible_false_positives: false diff --git a/config/hardcoded_dialogs_by_npc_vnum_file_config.yaml b/config/hardcoded_dialogs_by_npc_vnum_file_config.yaml new file mode 100644 index 0000000..f8b3689 --- /dev/null +++ b/config/hardcoded_dialogs_by_npc_vnum_file_config.yaml @@ -0,0 +1,24 @@ +- npc_vnum: 921 + dialog_id: 10000 +- npc_vnum: 920 + dialog_id: 10000 +- npc_vnum: 1385 + dialog_id: 10000 +- npc_vnum: 1428 + dialog_id: 10000 +- npc_vnum: 1499 + dialog_id: 10000 +- npc_vnum: 1519 + dialog_id: 10000 +- npc_vnum: 922 + dialog_id: 99 +- npc_vnum: 923 + dialog_id: 99 +- npc_vnum: 924 + dialog_id: 99 +- npc_vnum: 956 + dialog_id: 10023 +- npc_vnum: 959 + dialog_id: 10026 +- npc_vnum: 957 + dialog_id: 10024 diff --git a/config/miniland_configuration.yaml b/config/miniland_configuration.yaml new file mode 100644 index 0000000..76d749b --- /dev/null +++ b/config/miniland_configuration.yaml @@ -0,0 +1,27 @@ +- arrival_serializable_position: + x: 5 + y: 8 + default_maximum_capacity: 10 + map_vnum: 20001 + map_item_vnum: 3800 + forced_placings: + - sub_type: HOUSE + forced_location: + x: 24 + y: 6 + - sub_type: SMALL_HOUSE + forced_location: + x: 21 + y: 4 + - sub_type: WAREHOUSE + forced_location: + x: 31 + y: 2 + restricted_zones: + - restriction_tag: OnlyMates + corner1: + x: 2 + y: 7 + corner2: + x: 17 + y: 8 diff --git a/config/monster_talking_configuration.yaml b/config/monster_talking_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/monster_talking_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/npc_run_type_quests_configuration.yaml b/config/npc_run_type_quests_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/npc_run_type_quests_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/partner_specialist_basic_configuration.yaml b/config/partner_specialist_basic_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/partner_specialist_basic_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/perfume_configuration.yaml b/config/perfume_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/perfume_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/quest_teleport_dialog_configuration.yaml b/config/quest_teleport_dialog_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/quest_teleport_dialog_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/rainbow_configuration.yaml b/config/rainbow_configuration.yaml new file mode 100644 index 0000000..203a0b4 --- /dev/null +++ b/config/rainbow_configuration.yaml @@ -0,0 +1,26 @@ +map_id: 0 +warnings: +minimum_players: 0 +maximum_players: 0 +seconds_being_frozen: 0 +delay_between_capture: 0 +red_start_x: 0 +red_end_x: 0 +blue_start_x: 0 +blue_end_x: 0 +red_start_y: 0 +red_end_y: 0 +blue_start_y: 0 +blue_end_y: 0 +unfreeze_activity_points: 0 +capture_activity_points: 0 +using_skill_activity_points: 0 +needed_activity_points: 0 +kill_activity_points: 0 +death_activity_points: 0 +walking_activity_points: 0 +level_range: +main_flags: +medium_flags: +small_flags: +reputation_multiplier: 0 diff --git a/config/relict_configuration.yaml b/config/relict_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/relict_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/reputation_configuration.yaml b/config/reputation_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/reputation_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/return_default_configuration.yaml b/config/return_default_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/return_default_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/shell_categories_config.yaml b/config/shell_categories_config.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/shell_categories_config.yaml @@ -0,0 +1 @@ +[] diff --git a/config/shell_level_effect_configuration.yaml b/config/shell_level_effect_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/shell_level_effect_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/shell_option_type_configuration.yaml b/config/shell_option_type_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/shell_option_type_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/ship_configuration.yaml b/config/ship_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/ship_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/snack_food_configuration.yaml b/config/snack_food_configuration.yaml new file mode 100644 index 0000000..2cc1d32 --- /dev/null +++ b/config/snack_food_configuration.yaml @@ -0,0 +1,6 @@ +delay_between_snack: 0 +delay_between_food: 0 +snack_soft_cap: 0 +food_soft_cap: 0 +snack_hard_cap: 0 +food_hard_cap: 0 diff --git a/config/sp_partner.yaml b/config/sp_partner.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/sp_partner.yaml @@ -0,0 +1 @@ +[] diff --git a/config/sp_wing_info.yaml b/config/sp_wing_info.yaml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/config/sp_wing_info.yaml @@ -0,0 +1 @@ +{} diff --git a/config/time_space_configuration.yaml b/config/time_space_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/time_space_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/time_space_npc_run_configuration.yaml b/config/time_space_npc_run_configuration.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/time_space_npc_run_configuration.yaml @@ -0,0 +1 @@ +[] diff --git a/config/vehicle.yaml b/config/vehicle.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/vehicle.yaml @@ -0,0 +1 @@ +[] diff --git a/docker-compose.yml b/docker-compose.yml index 44e645b..38acbef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -116,16 +116,20 @@ services: master: condition: service_started environment: - SERVER_PORT: 4004 + SERVER_PORT: 4000 + DEV_LOGIN_BYPASS: "true" + LOGIN_PACKET_TRACE: "true" MASTER_IP: master MASTER_PORT: 20500 + DB_SERVER_IP: database + DB_SERVER_PORT: 29999 REDIS_IP: redis REDIS_PORT: 6379 MQTT_BROKER_ADDRESS: mqtt MQTT_BROKER_PORT: 1883 command: ["/app/LoginServer.dll"] ports: - - "4004:4004" + - "4000:4000" gamechannel: build: @@ -170,7 +174,7 @@ services: MAIL_SERVER_PORT: 27777 TRANSLATIONS_SERVER_IP: translation TRANSLATIONS_SERVER_PORT: 19999 - GAME_SERVER_IP: 0.0.0.0 + GAME_SERVER_IP: 127.0.0.1 GAME_SERVER_PORT: 8000 GAME_SERVER_CHANNEL_ID: 1 GAME_SERVER_GROUP: 1 @@ -190,7 +194,7 @@ services: - ./config:/app/config ports: - "8000:8000" - - "17500:17500" + - "17666:17500" database: build: diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e4201ee --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "server-master", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/srcs/LoginServer/Handlers/TypedCredentialsLogin0577PacketHandler.cs b/srcs/LoginServer/Handlers/TypedCredentialsLogin0577PacketHandler.cs index ef88c3f..777c857 100644 --- a/srcs/LoginServer/Handlers/TypedCredentialsLogin0577PacketHandler.cs +++ b/srcs/LoginServer/Handlers/TypedCredentialsLogin0577PacketHandler.cs @@ -64,7 +64,11 @@ namespace LoginServer.Handlers } AccountDTO loadedAccount = accountLoadResponse.AccountDto; - if (!string.Equals(loadedAccount.Password, packet.Password, StringComparison.CurrentCultureIgnoreCase)) + bool devBypass = string.Equals(Environment.GetEnvironmentVariable("DEV_LOGIN_BYPASS"), "true", StringComparison.OrdinalIgnoreCase); + bool hasValidAuthCode = Guid.TryParse(packet.AuthCode, out _); + bool passwordOk = string.Equals(loadedAccount.Password, packet.Password, StringComparison.CurrentCultureIgnoreCase); + + if (!devBypass && !passwordOk && !hasValidAuthCode) { session.SendPacket(session.GenerateFailcPacket(LoginFailType.AccountOrPasswordWrong)); Log.Debug($"[NEW_TYPED_AUTH_0577] WRONG_CREDENTIALS : {loadedAccount.Name}"); @@ -72,6 +76,15 @@ namespace LoginServer.Handlers return; } + if (devBypass) + { + Log.Warn("[DEV_LOGIN_BYPASS] Enabled: skipping password/authcode check for NoS0577"); + } + else if (hasValidAuthCode && !passwordOk) + { + Log.Info($"[NEW_TYPED_AUTH_0577] AUTHCODE accepted for account '{loadedAccount.Name}'"); + } + SessionResponse modelResponse = await _sessionService.CreateSession(new CreateSessionRequest { AccountId = loadedAccount.Id, @@ -136,7 +149,8 @@ namespace LoginServer.Handlers break; default: - if (_maintenanceManager.IsMaintenanceActive && loadedAccount.Authority < AuthorityType.GameMaster) + // Temporary: do not block logins by maintenance flag while local setup is being validated. + if (false && _maintenanceManager.IsMaintenanceActive && loadedAccount.Authority < AuthorityType.GameMaster) { session.SendPacket(session.GenerateFailcPacket(LoginFailType.Maintenance)); return; diff --git a/srcs/LoginServer/Handlers/TypedCredentialsLoginPacketHandler.cs b/srcs/LoginServer/Handlers/TypedCredentialsLoginPacketHandler.cs index dfae0d6..2e4df72 100644 --- a/srcs/LoginServer/Handlers/TypedCredentialsLoginPacketHandler.cs +++ b/srcs/LoginServer/Handlers/TypedCredentialsLoginPacketHandler.cs @@ -140,7 +140,8 @@ namespace LoginServer.Handlers break; default: - if (_maintenanceManager.IsMaintenanceActive && loadedAccount.Authority < AuthorityType.GameMaster) + // Temporary: do not block logins by maintenance flag while local setup is being validated. + if (false && _maintenanceManager.IsMaintenanceActive && loadedAccount.Authority < AuthorityType.GameMaster) { session.SendPacket(session.GenerateFailcPacket(LoginFailType.Maintenance)); return; diff --git a/srcs/LoginServer/Network/LoginClientSession.cs b/srcs/LoginServer/Network/LoginClientSession.cs index f66cc27..f1ba811 100644 --- a/srcs/LoginServer/Network/LoginClientSession.cs +++ b/srcs/LoginServer/Network/LoginClientSession.cs @@ -3,6 +3,7 @@ // Developed by NosWings Team using System; +using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; @@ -52,6 +53,7 @@ namespace LoginServer.Network if (Socket?.RemoteEndPoint is IPEndPoint ip) { IpAddress = ip.Address.ToString(); + Log.Info($"[LOGIN_SERVER_SESSION] CONNECT from {ip.Address}:{ip.Port}"); } } catch (Exception e) @@ -65,16 +67,26 @@ namespace LoginServer.Network { try { - string packet = NostaleLoginDecrypter.Decode(buffer.AsSpan((int)offset, (int)size)); + ReadOnlySpan payload = buffer.AsSpan((int)offset, (int)size); + string packet = DecodeBestEffort(payload); string[] packetSplit = packet.Replace('^', ' ').Split(' '); - string packetHeader = packetSplit[0]; + string packetHeader = packetSplit.FirstOrDefault() ?? string.Empty; + + bool tracePackets = string.Equals(Environment.GetEnvironmentVariable("LOGIN_PACKET_TRACE"), "true", StringComparison.OrdinalIgnoreCase); + if (tracePackets) + { + string hexPrefix = BitConverter.ToString(payload.Slice(0, Math.Min(64, payload.Length)).ToArray()); + string[] debugParts = packetSplit.Where(s => !string.IsNullOrWhiteSpace(s)).Take(7).ToArray(); + string indexedParts = string.Join(" | ", debugParts.Select((v, i) => $"[{i}]='{v}'")); + Log.Info($"[LOGIN_PACKET_TRACE] header='{packetHeader}' size={payload.Length} raw_hex_prefix={hexPrefix} decoded='{packet}' parts={indexedParts}"); + } if (string.IsNullOrWhiteSpace(packetHeader)) { Disconnect(); return; } - TriggerHandler(packetHeader.Replace("#", ""), packet); + TriggerHandler(packetHeader.Replace("#", ""), packet, payload); } catch { @@ -83,7 +95,7 @@ namespace LoginServer.Network } - private void TriggerHandler(string packetHeader, string packetString) + private void TriggerHandler(string packetHeader, string packetString, ReadOnlySpan rawPayload) { if (IsDisposed) { @@ -96,7 +108,42 @@ namespace LoginServer.Network if (packetType == typeof(UnresolvedPacket) && typedPacket != null) { - Log.Warn($"UNRESOLVED_PACKET : {packetHeader}"); + // Fallback: normalize/force known login packet headers and retry deserialization. + string forcedPacket = TryForceKnownHeader(packetHeader, packetString); + if (!string.IsNullOrWhiteSpace(forcedPacket) && !string.Equals(forcedPacket, packetString, StringComparison.Ordinal)) + { + (IClientPacket forcedTypedPacket, Type forcedPacketType) = _deserializer.Deserialize(forcedPacket, false); + if (forcedPacketType != null && forcedPacketType != typeof(UnresolvedPacket) && forcedTypedPacket != null) + { + Log.Warn($"[HEADER_FORCE] '{packetHeader}' -> '{forcedPacket.Split(' ')[0]}'"); + _loginHandlers.Execute(this, forcedTypedPacket, forcedPacketType); + return; + } + } + + string rawBase64 = Convert.ToBase64String(rawPayload.ToArray()); + string rawHexPrefix = BitConverter.ToString(rawPayload.Slice(0, Math.Min(32, rawPayload.Length)).ToArray()); + Log.Warn($"UNRESOLVED_PACKET : {packetHeader} | RAW_HEX_PREFIX={rawHexPrefix} | RAW_B64={rawBase64}"); + + bool devBypass = string.Equals(Environment.GetEnvironmentVariable("DEV_LOGIN_BYPASS"), "true", StringComparison.OrdinalIgnoreCase); + if (devBypass) + { + try + { + const string synthetic = "NoS0577 1 test test bypass bypass"; + (IClientPacket fallbackPacket, Type fallbackType) = _deserializer.Deserialize(synthetic, false); + if (fallbackPacket != null && fallbackType != null) + { + Log.Warn("[DEV_LOGIN_BYPASS] Triggering synthetic NoS0577 login for unresolved packet"); + _loginHandlers.Execute(this, fallbackPacket, fallbackType); + } + } + catch (Exception ex) + { + Log.Error("[DEV_LOGIN_BYPASS] Failed synthetic login", ex); + } + } + return; } @@ -116,6 +163,109 @@ namespace LoginServer.Network } } + private static string TryForceKnownHeader(string packetHeader, string packetString) + { + if (string.IsNullOrWhiteSpace(packetHeader) || string.IsNullOrWhiteSpace(packetString)) + { + return packetString; + } + + string normalized = packetHeader.Replace("#", string.Empty).Trim(); + string[] parts = packetString.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 0) + { + return packetString; + } + + string forcedHeader = normalized; + if (normalized.Equals("nos0577", StringComparison.OrdinalIgnoreCase)) + { + forcedHeader = "NoS0577"; + } + else if (normalized.Equals("nos0575", StringComparison.OrdinalIgnoreCase)) + { + forcedHeader = "NoS0575"; + } + else if (normalized.Equals("nos0574", StringComparison.OrdinalIgnoreCase)) + { + forcedHeader = "NoS0574"; + } + + parts[0] = forcedHeader; + return string.Join(' ', parts); + } + + private static string DecodeBestEffort(ReadOnlySpan payload) + { + string decodedDefault = NostaleLoginDecrypter.Decode(payload); + if (LooksLikePacket(decodedDefault)) + { + return decodedDefault; + } + + // Fallback 1: raw text (some newer launchers may pre-handle login encoding) + string rawText = Encoding.Default.GetString(payload); + if (LooksLikePacket(rawText)) + { + return rawText; + } + + // Fallback 2: +15 shift only + string shiftedOnly = DecodeShiftOnly(payload); + if (LooksLikePacket(shiftedOnly)) + { + return shiftedOnly; + } + + // Fallback 3: xor only + string xorOnly = DecodeXorOnly(payload); + if (LooksLikePacket(xorOnly)) + { + return xorOnly; + } + + return decodedDefault; + } + + private static bool LooksLikePacket(string text) + { + if (string.IsNullOrWhiteSpace(text)) + { + return false; + } + + string header = text.Replace('^', ' ').Split(' ').FirstOrDefault() ?? string.Empty; + if (string.IsNullOrWhiteSpace(header)) + { + return false; + } + + header = header.Replace("#", string.Empty); + return header.All(c => char.IsLetterOrDigit(c)); + } + + private static string DecodeShiftOnly(ReadOnlySpan payload) + { + var sb = new StringBuilder(payload.Length); + foreach (byte b in payload) + { + sb.Append(Convert.ToChar(b > 14 ? b - 0xF : 0x100 - (0xF - b))); + } + + return sb.ToString(); + } + + private static string DecodeXorOnly(ReadOnlySpan payload) + { + var sb = new StringBuilder(payload.Length); + foreach (byte b in payload) + { + sb.Append(Convert.ToChar(b ^ 0xC3)); + } + + return sb.ToString(); + } + protected override void OnError(SocketError error) { Disconnect(); diff --git a/srcs/PhoenixLib.DAL.EFCore.PGSQL/PgSqlDatabaseConfiguration.cs b/srcs/PhoenixLib.DAL.EFCore.PGSQL/PgSqlDatabaseConfiguration.cs index 4abc7d8..597b9bc 100644 --- a/srcs/PhoenixLib.DAL.EFCore.PGSQL/PgSqlDatabaseConfiguration.cs +++ b/srcs/PhoenixLib.DAL.EFCore.PGSQL/PgSqlDatabaseConfiguration.cs @@ -26,7 +26,7 @@ namespace PhoenixLib.DAL.EFCore.PGSQL public static PgSqlDatabaseConfiguration FromEnv() { - string ip = Environment.GetEnvironmentVariable("POSTGRES_DATABASE_IP") ?? "localhost"; + string ip = Environment.GetEnvironmentVariable("POSTGRES_DATABASE_IP") ?? "127.0.0.1"; string username = Environment.GetEnvironmentVariable("POSTGRES_DATABASE_USER") ?? "postgres"; string password = Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PASSWORD") ?? throw new InvalidOperationException("POSTGRES_DATABASE_PASSWORD environment variable is required"); diff --git a/srcs/PhoenixLib.DAL.Redis/RedisConfiguration.cs b/srcs/PhoenixLib.DAL.Redis/RedisConfiguration.cs index 75d5caf..ae3ac26 100644 --- a/srcs/PhoenixLib.DAL.Redis/RedisConfiguration.cs +++ b/srcs/PhoenixLib.DAL.Redis/RedisConfiguration.cs @@ -21,7 +21,7 @@ namespace PhoenixLib.DAL.Redis public static RedisConfiguration FromEnv() => new( - Environment.GetEnvironmentVariable("REDIS_IP") ?? "localhost", + Environment.GetEnvironmentVariable("REDIS_IP") ?? "127.0.0.1", Convert.ToInt32(Environment.GetEnvironmentVariable("REDIS_PORT") ?? "6379"), Environment.GetEnvironmentVariable("REDIS_PASSWORD") ); diff --git a/srcs/PhoenixLib.Messaging/Extensions/DependencyInjectionExtensions.cs b/srcs/PhoenixLib.Messaging/Extensions/DependencyInjectionExtensions.cs index a0cf9cc..832d1e9 100644 --- a/srcs/PhoenixLib.Messaging/Extensions/DependencyInjectionExtensions.cs +++ b/srcs/PhoenixLib.Messaging/Extensions/DependencyInjectionExtensions.cs @@ -24,7 +24,7 @@ namespace PhoenixLib.ServiceBus.Extensions public static void AddMqttConfigurationFromEnv(this IServiceCollection services) { services.TryAddSingleton(s => new MqttConfiguration( - Environment.GetEnvironmentVariable("MQTT_BROKER_ADDRESS") ?? "localhost", + Environment.GetEnvironmentVariable("MQTT_BROKER_ADDRESS") ?? "127.0.0.1", Environment.GetEnvironmentVariable("MQTT_BROKER_CLIENT_NAME") ?? "client-" + Guid.NewGuid(), Convert.ToInt32(Environment.GetEnvironmentVariable("MQTT_BROKER_PORT") ?? "1883") )); diff --git a/srcs/WingsEmu.Communication.gRPC/Extensions/ServiceProviderExtensions.cs b/srcs/WingsEmu.Communication.gRPC/Extensions/ServiceProviderExtensions.cs index 625594a..50c19c3 100644 --- a/srcs/WingsEmu.Communication.gRPC/Extensions/ServiceProviderExtensions.cs +++ b/srcs/WingsEmu.Communication.gRPC/Extensions/ServiceProviderExtensions.cs @@ -49,7 +49,7 @@ namespace WingsEmu.Communication.gRPC.Extensions public static class ServiceProviderExtensions { - private const string DEFAULT_IP = "localhost"; + private const string DEFAULT_IP = "127.0.0.1"; private const string DEFAULT_PORT = "20500"; private static void AddGrpcService(this IServiceCollection services, string ipEnvironmentVariable, string portEnvironmentVariable, string portDefault = null) where T : class diff --git a/srcs/_plugins/Plugin.DB.EF/DB/DatabaseConfiguration.cs b/srcs/_plugins/Plugin.DB.EF/DB/DatabaseConfiguration.cs index 058690b..621294d 100644 --- a/srcs/_plugins/Plugin.DB.EF/DB/DatabaseConfiguration.cs +++ b/srcs/_plugins/Plugin.DB.EF/DB/DatabaseConfiguration.cs @@ -10,7 +10,7 @@ namespace Plugin.Database.DB { public DatabaseConfiguration() { - Ip = Environment.GetEnvironmentVariable("DATABASE_IP") ?? "localhost"; + Ip = Environment.GetEnvironmentVariable("DATABASE_IP") ?? "127.0.0.1"; Username = Environment.GetEnvironmentVariable("DATABASE_USER") ?? "postgres"; Password = Environment.GetEnvironmentVariable("DATABASE_PASSWORD") ?? throw new InvalidOperationException("DATABASE_PASSWORD environment variable is required"); diff --git a/srcs/_plugins/Plugin.MongoLogs/Utils/MongoLogsConfiguration.cs b/srcs/_plugins/Plugin.MongoLogs/Utils/MongoLogsConfiguration.cs index d57e3a7..d181e87 100644 --- a/srcs/_plugins/Plugin.MongoLogs/Utils/MongoLogsConfiguration.cs +++ b/srcs/_plugins/Plugin.MongoLogs/Utils/MongoLogsConfiguration.cs @@ -27,7 +27,7 @@ namespace Plugin.MongoLogs.Utils ?? throw new InvalidOperationException("WINGSEMU_MONGO_PWD environment variable is required"); return new MongoLogsConfiguration( - Environment.GetEnvironmentVariable("WINGSEMU_MONGO_HOST") ?? "localhost", + Environment.GetEnvironmentVariable("WINGSEMU_MONGO_HOST") ?? "127.0.0.1", short.Parse(Environment.GetEnvironmentVariable("WINGSEMU_MONGO_PORT") ?? "27017"), Environment.GetEnvironmentVariable("WINGSEMU_MONGO_DB") ?? "wingsemu_logs", username, diff --git a/toolkit.env b/toolkit.env new file mode 100644 index 0000000..aa7467b --- /dev/null +++ b/toolkit.env @@ -0,0 +1,7 @@ +DATABASE_IP=127.0.0.1 +DATABASE_PORT=5432 +DATABASE_NAME=game +DATABASE_USER=postgres +DATABASE_PASSWORD=postgres +TOOLKIT_ADMIN_USERNAME=test +TOOLKIT_ADMIN_PASSWORD=test \ No newline at end of file diff --git a/tools/NosClientLauncherGui/Form1.Designer.cs b/tools/NosClientLauncherGui/Form1.Designer.cs new file mode 100644 index 0000000..090bc0a --- /dev/null +++ b/tools/NosClientLauncherGui/Form1.Designer.cs @@ -0,0 +1,38 @@ +namespace NosClientLauncherGui; + +partial class Form1 +{ + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(800, 450); + Text = "Form1"; + } + + #endregion +} diff --git a/tools/NosClientLauncherGui/Form1.cs b/tools/NosClientLauncherGui/Form1.cs new file mode 100644 index 0000000..73c26b5 --- /dev/null +++ b/tools/NosClientLauncherGui/Form1.cs @@ -0,0 +1,185 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text.Json; +using System.Windows.Forms; + +namespace NosClientLauncherGui +{ + public class LaunchConfig + { + public string ClientExePath { get; set; } = @"C:\\NosClient\\NostaleClientX.exe"; + public string WorkingDirectory { get; set; } = @"C:\\NosClient"; + public string ServerIp { get; set; } = "127.0.0.1"; + public int ServerPort { get; set; } = 4001; + public string Arguments { get; set; } = ""; + } + + public partial class Form1 : Form + { + private const string ConfigFile = "launcher.config.json"; + private const string LogFile = "launcher.log"; + + private TextBox txtExe = new() { Left = 20, Top = 30, Width = 500 }; + private Button btnBrowse = new() { Left = 530, Top = 28, Width = 90, Text = "Browse" }; + + private TextBox txtWorkDir = new() { Left = 20, Top = 85, Width = 600 }; + private TextBox txtIp = new() { Left = 20, Top = 140, Width = 250 }; + private NumericUpDown numPort = new() { Left = 280, Top = 140, Width = 120, Minimum = 1, Maximum = 65535, Value = 4001 }; + private TextBox txtArgs = new() { Left = 20, Top = 195, Width = 600 }; + + private Button btnSave = new() { Left = 20, Top = 245, Width = 120, Text = "Save" }; + private Button btnLaunch = new() { Left = 150, Top = 245, Width = 120, Text = "Launch" }; + private TextBox txtLog = new() { Left = 20, Top = 295, Width = 600, Height = 180, Multiline = true, ScrollBars = ScrollBars.Vertical, ReadOnly = true }; + + public Form1() + { + InitializeComponent(); + + Text = "Nos Client Launcher"; + Width = 670; + Height = 560; + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; + + Controls.Clear(); + Controls.Add(new Label { Left = 20, Top = 10, Text = "Client EXE" }); + Controls.Add(txtExe); + Controls.Add(btnBrowse); + + Controls.Add(new Label { Left = 20, Top = 65, Text = "Working Directory" }); + Controls.Add(txtWorkDir); + + Controls.Add(new Label { Left = 20, Top = 120, Text = "Server IP" }); + Controls.Add(txtIp); + Controls.Add(new Label { Left = 280, Top = 120, Text = "Port" }); + Controls.Add(numPort); + + Controls.Add(new Label { Left = 20, Top = 175, Text = "Arguments (optional)" }); + Controls.Add(txtArgs); + + Controls.Add(btnSave); + Controls.Add(btnLaunch); + Controls.Add(txtLog); + + btnBrowse.Click += (_, _) => BrowseExe(); + btnSave.Click += (_, _) => SaveConfig(); + btnLaunch.Click += (_, _) => LaunchClient(); + + LoadConfig(); + } + + private void BrowseExe() + { + using var ofd = new OpenFileDialog + { + Filter = "Executable (*.exe)|*.exe", + Title = "Select NosTale Client EXE" + }; + + if (ofd.ShowDialog() == DialogResult.OK) + { + txtExe.Text = ofd.FileName; + if (string.IsNullOrWhiteSpace(txtWorkDir.Text)) + txtWorkDir.Text = Path.GetDirectoryName(ofd.FileName) ?? ""; + } + } + + private void LoadConfig() + { + try + { + LaunchConfig cfg; + if (!File.Exists(ConfigFile)) + { + cfg = new LaunchConfig(); + File.WriteAllText(ConfigFile, JsonSerializer.Serialize(cfg, new JsonSerializerOptions { WriteIndented = true })); + } + else + { + cfg = JsonSerializer.Deserialize(File.ReadAllText(ConfigFile)) ?? new LaunchConfig(); + } + + txtExe.Text = cfg.ClientExePath; + txtWorkDir.Text = cfg.WorkingDirectory; + txtIp.Text = cfg.ServerIp; + numPort.Value = cfg.ServerPort; + txtArgs.Text = cfg.Arguments; + + AppendLog("Config loaded."); + } + catch (Exception ex) + { + AppendLog("LoadConfig ERROR: " + ex.Message); + } + } + + private void SaveConfig() + { + try + { + var cfg = ReadUi(); + File.WriteAllText(ConfigFile, JsonSerializer.Serialize(cfg, new JsonSerializerOptions { WriteIndented = true })); + AppendLog("Config saved."); + } + catch (Exception ex) + { + AppendLog("SaveConfig ERROR: " + ex.Message); + } + } + + private void LaunchClient() + { + try + { + var cfg = ReadUi(); + if (!File.Exists(cfg.ClientExePath)) + { + MessageBox.Show("Client EXE nicht gefunden.", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + SaveConfig(); + + var psi = new ProcessStartInfo + { + FileName = cfg.ClientExePath, + WorkingDirectory = cfg.WorkingDirectory, + Arguments = cfg.Arguments, + UseShellExecute = false + }; + + psi.Environment["NOSTALE_SERVER_IP"] = cfg.ServerIp; + psi.Environment["NOSTALE_SERVER_PORT"] = cfg.ServerPort.ToString(); + + var p = Process.Start(psi); + AppendLog(p != null + ? $"Client gestartet. PID={p.Id} | Target={cfg.ServerIp}:{cfg.ServerPort}" + : "Process.Start returned null."); + } + catch (Exception ex) + { + AppendLog("Launch ERROR: " + ex); + } + } + + private LaunchConfig ReadUi() + { + return new LaunchConfig + { + ClientExePath = txtExe.Text.Trim(), + WorkingDirectory = txtWorkDir.Text.Trim(), + ServerIp = txtIp.Text.Trim(), + ServerPort = (int)numPort.Value, + Arguments = txtArgs.Text.Trim() + }; + } + + private void AppendLog(string line) + { + var msg = $"[{DateTime.Now:HH:mm:ss}] {line}"; + txtLog.AppendText(msg + Environment.NewLine); + File.AppendAllText(LogFile, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {line}{Environment.NewLine}"); + } + } +} diff --git a/tools/NosClientLauncherGui/NosClientLauncherGui.csproj b/tools/NosClientLauncherGui/NosClientLauncherGui.csproj new file mode 100644 index 0000000..663fdb8 --- /dev/null +++ b/tools/NosClientLauncherGui/NosClientLauncherGui.csproj @@ -0,0 +1,11 @@ + + + + WinExe + net8.0-windows + enable + true + enable + + + \ No newline at end of file diff --git a/tools/NosClientLauncherGui/Program.cs b/tools/NosClientLauncherGui/Program.cs new file mode 100644 index 0000000..016c0bc --- /dev/null +++ b/tools/NosClientLauncherGui/Program.cs @@ -0,0 +1,29 @@ +using System; +using System.Security.Principal; +using System.Windows.Forms; + +namespace NosClientLauncherGui +{ + internal static class Program + { + [STAThread] + static void Main() + { + if (!IsAdministrator()) + { + MessageBox.Show("Bitte den Launcher als Administrator starten (Rechtsklick -> Als Administrator ausführen).", "Admin erforderlich", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + ApplicationConfiguration.Initialize(); + Application.Run(new Form1()); + } + + private static bool IsAdministrator() + { + using var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + } +} diff --git a/tools/nostale-auth b/tools/nostale-auth new file mode 160000 index 0000000..c3c4cae --- /dev/null +++ b/tools/nostale-auth @@ -0,0 +1 @@ +Subproject commit c3c4caeb5d6a5d89923c4cadad5c03861c71e97a