diff --git a/.gitignore b/.gitignore index c9a14880e4..8bc4e0c3be 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,9 @@ msbuild.cmd # File generated during compile /dep/libmpq/compile + +# Build artifacts +_build/ +_install/ +/etc/ +tests/build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c8a279950..f503e3b960 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,7 @@ option(PLAYERBOTS "Enable Player Bots" OFF) option(SOAP "Enable remote access via SOAP" OFF) option(PCH "Enable precompiled headers" ON) option(DEBUG "Enable debug build (only on non IDEs)" OFF) +option(MANGOS_TEST_MODE "Enable test mode: skip DBC/map/DB validation" OFF) #================================================================================== message("") message( @@ -127,4 +128,10 @@ endif() add_subdirectory(dep) add_subdirectory(src) +option(BUILD_TESTS "Build the test suite" OFF) +if(BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + include(${CMAKE_SOURCE_DIR}/cmake/StatusInfo.cmake) diff --git a/cmake/SetDefinitions.cmake b/cmake/SetDefinitions.cmake index b8c3c0f45a..6ca1a499ef 100644 --- a/cmake/SetDefinitions.cmake +++ b/cmake/SetDefinitions.cmake @@ -188,3 +188,9 @@ unset(DEFAULT_COMPILE_OPTS) if(MSVC) set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD ON) endif() + +# Test mode: skip DBC/map/DB validation for running without game data +if(MANGOS_TEST_MODE) + add_definitions(-DMANGOS_TEST_MODE) + message(STATUS "TEST MODE ENABLED: DBC/map/DB validation will be skipped") +endif() diff --git a/sql/security_hardening.sql b/sql/security_hardening.sql new file mode 100644 index 0000000000..b39cb432da --- /dev/null +++ b/sql/security_hardening.sql @@ -0,0 +1,119 @@ +-- ============================================================================ +-- MaNGOS Three — Security Hardening Script +-- Run AFTER setup_mariadb.sql to apply security fixes and constraints. +-- Usage: mariadb -u root < sql/security_hardening.sql +-- ============================================================================ + +-- ── Restrict mangos user to only needed privileges (not ALL) ───────────── +-- Revoke ALL and grant only what the server actually needs +USE `realmd`; + +-- Revoke dangerous privileges from mangos user on realmd +REVOKE ALL PRIVILEGES ON `realmd`.* FROM 'mangos'@'localhost'; +REVOKE ALL PRIVILEGES ON `realmd`.* FROM 'mangos'@'127.0.0.1'; + +GRANT SELECT, INSERT, UPDATE, DELETE ON `realmd`.* TO 'mangos'@'localhost'; +GRANT SELECT, INSERT, UPDATE, DELETE ON `realmd`.* TO 'mangos'@'127.0.0.1'; + +-- Same for character3 and mangos3 +REVOKE ALL PRIVILEGES ON `character3`.* FROM 'mangos'@'localhost'; +REVOKE ALL PRIVILEGES ON `character3`.* FROM 'mangos'@'127.0.0.1'; +REVOKE ALL PRIVILEGES ON `mangos3`.* FROM 'mangos'@'localhost'; +REVOKE ALL PRIVILEGES ON `mangos3`.* FROM 'mangos'@'127.0.0.1'; + +GRANT SELECT, INSERT, UPDATE, DELETE ON `character3`.* TO 'mangos'@'localhost'; +GRANT SELECT, INSERT, UPDATE, DELETE ON `character3`.* TO 'mangos'@'127.0.0.1'; +GRANT SELECT, INSERT, UPDATE, DELETE ON `mangos3`.* TO 'mangos'@'localhost'; +GRANT SELECT, INSERT, UPDATE, DELETE ON `mangos3`.* TO 'mangos'@'127.0.0.1'; + +FLUSH PRIVILEGES; + +-- ── Add account lockout tracking table ─────────────────────────────────── +USE `realmd`; + +CREATE TABLE IF NOT EXISTS `account_login_attempts` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `account_id` INT UNSIGNED NOT NULL DEFAULT 0, + `ip` VARCHAR(45) NOT NULL DEFAULT '', + `login_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `success` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_account` (`account_id`), + KEY `idx_ip` (`ip`), + KEY `idx_time` (`login_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ── Add rate limiting metadata table ───────────────────────────────────── +CREATE TABLE IF NOT EXISTS `ip_rate_limit` ( + `ip` VARCHAR(45) NOT NULL, + `attempts` INT UNSIGNED NOT NULL DEFAULT 0, + `window_start` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `blocked_until` TIMESTAMP NULL DEFAULT NULL, + PRIMARY KEY (`ip`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ── Add constraints to prevent negative/overflow money values ──────────── +USE `character3`; + +-- Add CHECK constraints (MariaDB 10.2.1+) +ALTER TABLE `characters` ADD CONSTRAINT `chk_money_cap` + CHECK (`money` <= 9999999999); + +ALTER TABLE `characters` ADD CONSTRAINT `chk_level_range` + CHECK (`level` BETWEEN 0 AND 85); + +-- ── Add audit log table for security-sensitive operations ──────────────── +CREATE TABLE IF NOT EXISTS `security_audit_log` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `account_id` INT UNSIGNED NOT NULL DEFAULT 0, + `char_guid` INT UNSIGNED NOT NULL DEFAULT 0, + `action` VARCHAR(64) NOT NULL DEFAULT '', + `details` TEXT, + `ip` VARCHAR(45) NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + KEY `idx_account` (`account_id`), + KEY `idx_timestamp` (`timestamp`), + KEY `idx_action` (`action`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ── Harden item_instance to prevent duplication ────────────────────────── +-- Add a unique constraint on item-owner pair to prevent the same item +-- from being duplicated across multiple owners simultaneously +ALTER TABLE `item_instance` ADD CONSTRAINT `chk_item_count` + CHECK (`count` > 0 AND `count` <= 2147483647); + +-- ── Prevent gold mail exploits with mail money validation ──────────────── +ALTER TABLE `mail` ADD CONSTRAINT `chk_mail_money` + CHECK (`money` <= 9999999999); + +-- ── Guild name uniqueness (already unique key on name in setup, verify) ── +-- Add length constraint +ALTER TABLE `guild` ADD CONSTRAINT `chk_guild_name_len` + CHECK (LENGTH(`name`) > 0 AND LENGTH(`name`) <= 24); + +-- ── Anti-exploitation: add foreign key constraints for referential integrity +-- These prevent orphaned records that could be exploited + +-- character_inventory must reference valid characters +ALTER TABLE `character_inventory` + ADD CONSTRAINT `fk_inv_char` FOREIGN KEY (`guid`) + REFERENCES `characters` (`guid`) ON DELETE CASCADE; + +-- character_spell must reference valid characters +ALTER TABLE `character_spell` + ADD CONSTRAINT `fk_spell_char` FOREIGN KEY (`guid`) + REFERENCES `characters` (`guid`) ON DELETE CASCADE; + +-- character_aura must reference valid characters +ALTER TABLE `character_aura` + ADD CONSTRAINT `fk_aura_char` FOREIGN KEY (`guid`) + REFERENCES `characters` (`guid`) ON DELETE CASCADE; + +-- character_queststatus must reference valid characters +ALTER TABLE `character_queststatus` + ADD CONSTRAINT `fk_quest_char` FOREIGN KEY (`guid`) + REFERENCES `characters` (`guid`) ON DELETE CASCADE; + +-- ── Done ───────────────────────────────────────────────────────────────── +SELECT 'Security hardening applied successfully' AS status; diff --git a/sql/setup_mariadb.sql b/sql/setup_mariadb.sql new file mode 100644 index 0000000000..2e8c3889d1 --- /dev/null +++ b/sql/setup_mariadb.sql @@ -0,0 +1,570 @@ +-- ============================================================================ +-- MaNGOS Three — MariaDB Setup Script +-- Creates the three required databases, user, and core tables. +-- Usage: mariadb -u root < sql/setup_mariadb.sql +-- ============================================================================ + +-- ── Databases ──────────────────────────────────────────────────────────────── +CREATE DATABASE IF NOT EXISTS `realmd` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; +CREATE DATABASE IF NOT EXISTS `mangos3` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; +CREATE DATABASE IF NOT EXISTS `character3` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; + +-- ── User ───────────────────────────────────────────────────────────────────── +CREATE USER IF NOT EXISTS 'mangos'@'localhost' IDENTIFIED BY 'mangos'; +CREATE USER IF NOT EXISTS 'mangos'@'127.0.0.1' IDENTIFIED BY 'mangos'; +GRANT ALL PRIVILEGES ON `realmd`.* TO 'mangos'@'localhost'; +GRANT ALL PRIVILEGES ON `realmd`.* TO 'mangos'@'127.0.0.1'; +GRANT ALL PRIVILEGES ON `mangos3`.* TO 'mangos'@'localhost'; +GRANT ALL PRIVILEGES ON `mangos3`.* TO 'mangos'@'127.0.0.1'; +GRANT ALL PRIVILEGES ON `character3`.* TO 'mangos'@'localhost'; +GRANT ALL PRIVILEGES ON `character3`.* TO 'mangos'@'127.0.0.1'; +FLUSH PRIVILEGES; + +-- ════════════════════════════════════════════════════════════════════════════ +-- REALMD +-- ════════════════════════════════════════════════════════════════════════════ +USE `realmd`; + +CREATE TABLE IF NOT EXISTS `account` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `username` VARCHAR(32) NOT NULL DEFAULT '', + `sha_pass_hash` VARCHAR(40) NOT NULL DEFAULT '', + `gmlevel` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `sessionkey` LONGTEXT, + `v` LONGTEXT, + `s` LONGTEXT, + `email` TEXT NOT NULL, + `joindate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_ip` VARCHAR(30) NOT NULL DEFAULT '127.0.0.1', + `failed_logins` INT UNSIGNED NOT NULL DEFAULT 0, + `locked` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `last_login` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', + `active_realm_id` INT UNSIGNED NOT NULL DEFAULT 0, + `expansion` TINYINT UNSIGNED NOT NULL DEFAULT 3, + `mutetime` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `locale` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `os` VARCHAR(4) NOT NULL DEFAULT '', + `playerBot` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_username` (`username`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `realmlist` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(32) NOT NULL DEFAULT '', + `address` VARCHAR(32) NOT NULL DEFAULT '127.0.0.1', + `localAddress` VARCHAR(255) NOT NULL DEFAULT '127.0.0.1', + `localSubnetMask` VARCHAR(255) NOT NULL DEFAULT '255.255.255.0', + `port` INT NOT NULL DEFAULT 8085, + `icon` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `realmflags` TINYINT UNSIGNED NOT NULL DEFAULT 2, + `timezone` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `allowedSecurityLevel` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `population` FLOAT UNSIGNED NOT NULL DEFAULT 0, + `realmbuilds` VARCHAR(64) NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + UNIQUE KEY `idx_name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `account_banned` ( + `id` INT UNSIGNED NOT NULL DEFAULT 0, + `bandate` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `unbandate` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `bannedby` VARCHAR(50) NOT NULL, + `banreason` VARCHAR(255) NOT NULL, + `active` TINYINT UNSIGNED NOT NULL DEFAULT 1, + PRIMARY KEY (`id`, `bandate`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `ip_banned` ( + `ip` VARCHAR(32) NOT NULL DEFAULT '127.0.0.1', + `bandate` BIGINT UNSIGNED NOT NULL, + `unbandate` BIGINT UNSIGNED NOT NULL, + `bannedby` VARCHAR(50) NOT NULL DEFAULT '[Console]', + `banreason` VARCHAR(255) NOT NULL DEFAULT 'no reason', + PRIMARY KEY (`ip`, `bandate`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `realmcharacters` ( + `realmid` INT UNSIGNED NOT NULL DEFAULT 0, + `acctid` INT UNSIGNED NOT NULL DEFAULT 0, + `numchars` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`realmid`, `acctid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Test accounts +INSERT IGNORE INTO `account` (`id`, `username`, `sha_pass_hash`, `gmlevel`, `expansion`) VALUES + (1, 'TEST', UPPER(SHA1(CONCAT(UPPER('TEST'), ':', UPPER('TEST')))), 3, 3), + (2, 'ADMIN', UPPER(SHA1(CONCAT(UPPER('ADMIN'), ':', UPPER('ADMIN')))), 3, 3); + +INSERT IGNORE INTO `realmlist` (`id`, `name`, `address`, `port`, `realmbuilds`) VALUES + (1, 'MaNGOS Three', '127.0.0.1', 8085, '15595'); + +-- ════════════════════════════════════════════════════════════════════════════ +-- CHARACTER3 +-- ════════════════════════════════════════════════════════════════════════════ +USE `character3`; + +CREATE TABLE IF NOT EXISTS `characters` ( + `guid` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `account` INT UNSIGNED NOT NULL DEFAULT 0, + `name` VARCHAR(12) NOT NULL DEFAULT '', + `race` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `class` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `gender` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `level` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `xp` INT UNSIGNED NOT NULL DEFAULT 0, + `money` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `playerBytes` INT UNSIGNED NOT NULL DEFAULT 0, + `playerBytes2` INT UNSIGNED NOT NULL DEFAULT 0, + `playerFlags` INT UNSIGNED NOT NULL DEFAULT 0, + `map` INT UNSIGNED NOT NULL DEFAULT 0, + `zone` INT UNSIGNED NOT NULL DEFAULT 0, + `position_x` FLOAT NOT NULL DEFAULT 0, + `position_y` FLOAT NOT NULL DEFAULT 0, + `position_z` FLOAT NOT NULL DEFAULT 0, + `orientation` FLOAT NOT NULL DEFAULT 0, + `health` INT UNSIGNED NOT NULL DEFAULT 0, + `power1` INT UNSIGNED NOT NULL DEFAULT 0, + `power2` INT UNSIGNED NOT NULL DEFAULT 0, + `power3` INT UNSIGNED NOT NULL DEFAULT 0, + `power4` INT UNSIGNED NOT NULL DEFAULT 0, + `power5` INT UNSIGNED NOT NULL DEFAULT 0, + `online` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `totaltime` INT UNSIGNED NOT NULL DEFAULT 0, + `leveltime` INT UNSIGNED NOT NULL DEFAULT 0, + `logout_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `is_logout_resting` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `rest_bonus` FLOAT NOT NULL DEFAULT 0, + `at_login` INT UNSIGNED NOT NULL DEFAULT 0, + `deleteDate` INT UNSIGNED DEFAULT NULL, + `deleteInfos_Account` INT UNSIGNED DEFAULT NULL, + `deleteInfos_Name` VARCHAR(12) DEFAULT NULL, + `equipmentCache` LONGTEXT, + `knownTitles` LONGTEXT, + `exploredZones` LONGTEXT, + PRIMARY KEY (`guid`), + KEY `idx_account` (`account`), + KEY `idx_online` (`online`), + KEY `idx_name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_instance` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `instance` INT UNSIGNED NOT NULL DEFAULT 0, + `map` INT UNSIGNED NOT NULL DEFAULT 0, + `difficulty` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `resettime` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `permanent` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `instance`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_aura` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `caster_guid` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `item_guid` INT UNSIGNED NOT NULL DEFAULT 0, + `spell` INT UNSIGNED NOT NULL DEFAULT 0, + `stackcount` INT UNSIGNED NOT NULL DEFAULT 1, + `remaincharges` INT NOT NULL DEFAULT 0, + `basepoints0` INT NOT NULL DEFAULT 0, + `basepoints1` INT NOT NULL DEFAULT 0, + `basepoints2` INT NOT NULL DEFAULT 0, + `periodictime0` INT UNSIGNED NOT NULL DEFAULT 0, + `periodictime1` INT UNSIGNED NOT NULL DEFAULT 0, + `periodictime2` INT UNSIGNED NOT NULL DEFAULT 0, + `maxduration` INT NOT NULL DEFAULT 0, + `remaintime` INT NOT NULL DEFAULT 0, + `effIndexMask` INT UNSIGNED NOT NULL DEFAULT 0, + KEY `idx_guid` (`guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_spell` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `spell` INT UNSIGNED NOT NULL DEFAULT 0, + `active` TINYINT UNSIGNED NOT NULL DEFAULT 1, + `disabled` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `spell`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_action` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `spec` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `button` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `action` INT UNSIGNED NOT NULL DEFAULT 0, + `type` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `spec`, `button`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_homebind` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `map` INT UNSIGNED NOT NULL DEFAULT 0, + `zone` INT UNSIGNED NOT NULL DEFAULT 0, + `position_x` FLOAT NOT NULL DEFAULT 0, + `position_y` FLOAT NOT NULL DEFAULT 0, + `position_z` FLOAT NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_inventory` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `bag` INT UNSIGNED NOT NULL DEFAULT 0, + `slot` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `item` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `bag`, `slot`), + KEY `idx_item` (`item`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `item_instance` ( + `guid` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `owner_guid` INT UNSIGNED NOT NULL DEFAULT 0, + `itemEntry` INT UNSIGNED NOT NULL DEFAULT 0, + `creatorGuid` INT UNSIGNED NOT NULL DEFAULT 0, + `count` INT UNSIGNED NOT NULL DEFAULT 1, + `duration` INT NOT NULL DEFAULT 0, + `charges` TINYTEXT, + `flags` INT UNSIGNED NOT NULL DEFAULT 0, + `enchantments` TEXT, + `randomPropertyId` INT NOT NULL DEFAULT 0, + `durability` INT UNSIGNED NOT NULL DEFAULT 0, + `text` TEXT, + PRIMARY KEY (`guid`), + KEY `idx_owner_guid` (`owner_guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_social` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `friend` INT UNSIGNED NOT NULL DEFAULT 0, + `flags` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `note` VARCHAR(48) NOT NULL DEFAULT '', + PRIMARY KEY (`guid`, `friend`, `flags`), + KEY `friend_idx` (`friend`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_reputation` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `faction` INT UNSIGNED NOT NULL DEFAULT 0, + `standing` INT NOT NULL DEFAULT 0, + `flags` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `faction`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_queststatus` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `quest` INT UNSIGNED NOT NULL DEFAULT 0, + `status` INT UNSIGNED NOT NULL DEFAULT 0, + `rewarded` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `explored` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `timer` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `mobcount1` INT UNSIGNED NOT NULL DEFAULT 0, + `mobcount2` INT UNSIGNED NOT NULL DEFAULT 0, + `mobcount3` INT UNSIGNED NOT NULL DEFAULT 0, + `mobcount4` INT UNSIGNED NOT NULL DEFAULT 0, + `itemcount1` INT UNSIGNED NOT NULL DEFAULT 0, + `itemcount2` INT UNSIGNED NOT NULL DEFAULT 0, + `itemcount3` INT UNSIGNED NOT NULL DEFAULT 0, + `itemcount4` INT UNSIGNED NOT NULL DEFAULT 0, + `itemcount5` INT UNSIGNED NOT NULL DEFAULT 0, + `itemcount6` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `quest`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `guild` ( + `guildid` INT UNSIGNED NOT NULL DEFAULT 0, + `name` VARCHAR(255) NOT NULL DEFAULT '', + `leaderguid` INT UNSIGNED NOT NULL DEFAULT 0, + `EmblemStyle` INT NOT NULL DEFAULT 0, + `EmblemColor` INT NOT NULL DEFAULT 0, + `BorderStyle` INT NOT NULL DEFAULT 0, + `BorderColor` INT NOT NULL DEFAULT 0, + `BackgroundColor` INT NOT NULL DEFAULT 0, + `info` TEXT NOT NULL, + `motd` VARCHAR(128) NOT NULL DEFAULT '', + `createdate` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `BankMoney` BIGINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guildid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `guild_member` ( + `guildid` INT UNSIGNED NOT NULL DEFAULT 0, + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `rank` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `pnote` VARCHAR(31) NOT NULL DEFAULT '', + `offnote` VARCHAR(31) NOT NULL DEFAULT '', + PRIMARY KEY (`guildid`, `guid`), + UNIQUE KEY `guid_key` (`guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_pet` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `entry` INT UNSIGNED NOT NULL DEFAULT 0, + `owner` INT UNSIGNED NOT NULL DEFAULT 0, + `modelid` INT UNSIGNED NOT NULL DEFAULT 0, + `level` INT UNSIGNED NOT NULL DEFAULT 1, + `exp` INT UNSIGNED NOT NULL DEFAULT 0, + `Reactstate` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `slot` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `name` VARCHAR(21) NOT NULL DEFAULT 'Pet', + `renamed` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `curhealth` INT UNSIGNED NOT NULL DEFAULT 1, + `curmana` INT UNSIGNED NOT NULL DEFAULT 0, + `abdata` TEXT, + `savetime` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `resettalents_cost` INT UNSIGNED NOT NULL DEFAULT 0, + `resettalents_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `CreatedBySpell` INT UNSIGNED NOT NULL DEFAULT 0, + `PetType` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_owner` (`owner`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_declinedname` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `genitive` VARCHAR(15) NOT NULL DEFAULT '', + `dative` VARCHAR(15) NOT NULL DEFAULT '', + `accusative` VARCHAR(15) NOT NULL DEFAULT '', + `instrumental` VARCHAR(15) NOT NULL DEFAULT '', + `prepositional` VARCHAR(15) NOT NULL DEFAULT '', + PRIMARY KEY (`guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_queststatus_daily` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `quest` INT UNSIGNED NOT NULL DEFAULT 0, + `time` BIGINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `quest`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_queststatus_weekly` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `quest` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `quest`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_queststatus_monthly` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `quest` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `quest`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_spell_cooldown` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `spell` INT UNSIGNED NOT NULL DEFAULT 0, + `item` INT UNSIGNED NOT NULL DEFAULT 0, + `time` BIGINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `spell`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_account_data` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `type` INT UNSIGNED NOT NULL DEFAULT 0, + `time` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `data` LONGBLOB, + PRIMARY KEY (`guid`, `type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_talent` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `talent_id` INT UNSIGNED NOT NULL DEFAULT 0, + `current_rank` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `spec` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `talent_id`, `spec`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_skills` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `skill` INT UNSIGNED NOT NULL DEFAULT 0, + `value` INT UNSIGNED NOT NULL DEFAULT 0, + `max` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `skill`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_glyphs` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `spec` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `slot` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `glyph` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `spec`, `slot`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_achievement` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `achievement` INT UNSIGNED NOT NULL DEFAULT 0, + `date` BIGINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `achievement`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_achievement_progress` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `criteria` INT UNSIGNED NOT NULL DEFAULT 0, + `counter` INT UNSIGNED NOT NULL DEFAULT 0, + `date` BIGINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `criteria`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_equipmentsets` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `setguid` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `setindex` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `name` VARCHAR(31) NOT NULL DEFAULT '', + `iconname` VARCHAR(100) NOT NULL DEFAULT '', + `ignore_mask` INT UNSIGNED NOT NULL DEFAULT 0, + `item0` INT UNSIGNED NOT NULL DEFAULT 0, `item1` INT UNSIGNED NOT NULL DEFAULT 0, + `item2` INT UNSIGNED NOT NULL DEFAULT 0, `item3` INT UNSIGNED NOT NULL DEFAULT 0, + `item4` INT UNSIGNED NOT NULL DEFAULT 0, `item5` INT UNSIGNED NOT NULL DEFAULT 0, + `item6` INT UNSIGNED NOT NULL DEFAULT 0, `item7` INT UNSIGNED NOT NULL DEFAULT 0, + `item8` INT UNSIGNED NOT NULL DEFAULT 0, `item9` INT UNSIGNED NOT NULL DEFAULT 0, + `item10` INT UNSIGNED NOT NULL DEFAULT 0, `item11` INT UNSIGNED NOT NULL DEFAULT 0, + `item12` INT UNSIGNED NOT NULL DEFAULT 0, `item13` INT UNSIGNED NOT NULL DEFAULT 0, + `item14` INT UNSIGNED NOT NULL DEFAULT 0, `item15` INT UNSIGNED NOT NULL DEFAULT 0, + `item16` INT UNSIGNED NOT NULL DEFAULT 0, `item17` INT UNSIGNED NOT NULL DEFAULT 0, + `item18` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`setguid`), + UNIQUE KEY `idx_set` (`guid`, `setguid`, `setindex`), + KEY `idx_guid` (`guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_currencies` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `id` INT UNSIGNED NOT NULL DEFAULT 0, + `totalCount` INT UNSIGNED NOT NULL DEFAULT 0, + `weekCount` INT UNSIGNED NOT NULL DEFAULT 0, + `seasonCount` INT UNSIGNED NOT NULL DEFAULT 0, + `flags` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `character_battleground_data` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `instance_id` INT UNSIGNED NOT NULL DEFAULT 0, + `team` INT UNSIGNED NOT NULL DEFAULT 0, + `join_x` FLOAT NOT NULL DEFAULT 0, + `join_y` FLOAT NOT NULL DEFAULT 0, + `join_z` FLOAT NOT NULL DEFAULT 0, + `join_o` FLOAT NOT NULL DEFAULT 0, + `join_map` INT UNSIGNED NOT NULL DEFAULT 0, + `taxi_start` INT UNSIGNED NOT NULL DEFAULT 0, + `taxi_end` INT UNSIGNED NOT NULL DEFAULT 0, + `mount_spell` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `group_member` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `memberGuid` INT UNSIGNED NOT NULL DEFAULT 0, + `groupId` INT UNSIGNED NOT NULL DEFAULT 0, + `assistant` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `subgroup` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`memberGuid`), + KEY `idx_group` (`groupId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `instance` ( + `id` INT UNSIGNED NOT NULL DEFAULT 0, + `map` INT UNSIGNED NOT NULL DEFAULT 0, + `resettime` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `difficulty` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `data` LONGTEXT, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `item_loot` ( + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `owner_guid` INT UNSIGNED NOT NULL DEFAULT 0, + `itemid` INT UNSIGNED NOT NULL DEFAULT 0, + `amount` INT UNSIGNED NOT NULL DEFAULT 0, + `suffix` INT NOT NULL DEFAULT 0, + `property` INT NOT NULL DEFAULT 0, + PRIMARY KEY (`guid`, `itemid`), + KEY `idx_owner` (`owner_guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `mail` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `messageType` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `stationery` TINYINT NOT NULL DEFAULT 41, + `mailTemplateId` INT UNSIGNED NOT NULL DEFAULT 0, + `sender` INT UNSIGNED NOT NULL DEFAULT 0, + `receiver` INT UNSIGNED NOT NULL DEFAULT 0, + `subject` VARCHAR(200) DEFAULT '', + `body` VARCHAR(500) DEFAULT '', + `has_items` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `expire_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `deliver_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `money` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `cod` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `checked` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_receiver` (`receiver`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `mail_items` ( + `mail_id` INT UNSIGNED NOT NULL DEFAULT 0, + `item_guid` INT UNSIGNED NOT NULL DEFAULT 0, + `item_template` INT UNSIGNED NOT NULL DEFAULT 0, + `receiver` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`mail_id`, `item_guid`), + KEY `idx_receiver` (`receiver`), + KEY `idx_item_guid` (`item_guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `arena_team_member` ( + `arenateamid` INT UNSIGNED NOT NULL DEFAULT 0, + `guid` INT UNSIGNED NOT NULL DEFAULT 0, + `played_week` INT UNSIGNED NOT NULL DEFAULT 0, + `wons_week` INT UNSIGNED NOT NULL DEFAULT 0, + `played_season` INT UNSIGNED NOT NULL DEFAULT 0, + `wons_season` INT UNSIGNED NOT NULL DEFAULT 0, + `personal_rating` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`arenateamid`, `guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ════════════════════════════════════════════════════════════════════════════ +-- MANGOS3 (World) +-- ════════════════════════════════════════════════════════════════════════════ +USE `mangos3`; + +CREATE TABLE IF NOT EXISTS `db_version` ( + `version` VARCHAR(120) DEFAULT NULL, + `creature_ai_version` VARCHAR(120) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `creature_template` ( + `entry` INT UNSIGNED NOT NULL DEFAULT 0, + `Name` VARCHAR(100) NOT NULL DEFAULT '', + `SubName` VARCHAR(100) DEFAULT '', + `ModelId1` INT UNSIGNED NOT NULL DEFAULT 0, + `ModelId2` INT UNSIGNED NOT NULL DEFAULT 0, + `ModelId3` INT UNSIGNED NOT NULL DEFAULT 0, + `ModelId4` INT UNSIGNED NOT NULL DEFAULT 0, + `MinLevel` TINYINT UNSIGNED NOT NULL DEFAULT 1, + `MaxLevel` TINYINT UNSIGNED NOT NULL DEFAULT 1, + `MinLevelHealth` INT UNSIGNED NOT NULL DEFAULT 0, + `MaxLevelHealth` INT UNSIGNED NOT NULL DEFAULT 0, + `MinLevelMana` INT UNSIGNED NOT NULL DEFAULT 0, + `MaxLevelMana` INT UNSIGNED NOT NULL DEFAULT 0, + `MinMeleeDmg` FLOAT NOT NULL DEFAULT 0, + `MaxMeleeDmg` FLOAT NOT NULL DEFAULT 0, + `Armor` INT UNSIGNED NOT NULL DEFAULT 0, + `faction` INT UNSIGNED NOT NULL DEFAULT 0, + `UnitFlags` INT UNSIGNED NOT NULL DEFAULT 0, + `UnitClass` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `Rank` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`entry`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `creature` ( + `guid` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `id` INT UNSIGNED NOT NULL DEFAULT 0, + `map` INT UNSIGNED NOT NULL DEFAULT 0, + `position_x` FLOAT NOT NULL DEFAULT 0, + `position_y` FLOAT NOT NULL DEFAULT 0, + `position_z` FLOAT NOT NULL DEFAULT 0, + `orientation` FLOAT NOT NULL DEFAULT 0, + `spawntimesecsmin` INT UNSIGNED NOT NULL DEFAULT 120, + `spawntimesecsmax` INT UNSIGNED NOT NULL DEFAULT 120, + PRIMARY KEY (`guid`), + KEY `idx_id` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ── Done ───────────────────────────────────────────────────────────────────── +SELECT 'MaNGOS MariaDB setup complete' AS status; diff --git a/src/game/Object/Creature.cpp b/src/game/Object/Creature.cpp index d9b668ed8f..11cefbb3e1 100644 --- a/src/game/Object/Creature.cpp +++ b/src/game/Object/Creature.cpp @@ -1027,11 +1027,11 @@ void Creature::RegenerateHealth() float Spirit = GetStat(STAT_SPIRIT); //for charmed creatures, spirit = 0! if (GetPower(POWER_MANA) > 0) { - addvalue = uint32(Spirit * 0.25 * HealthIncreaseRate); + addvalue = SafeUInt32FromFloat(Spirit * 0.25 * HealthIncreaseRate); } else { - addvalue = uint32(Spirit * 0.80 * HealthIncreaseRate); + addvalue = SafeUInt32FromFloat(Spirit * 0.80 * HealthIncreaseRate); } } else diff --git a/src/game/Object/LootMgr.cpp b/src/game/Object/LootMgr.cpp index 8945203bbe..12f589bf97 100644 --- a/src/game/Object/LootMgr.cpp +++ b/src/game/Object/LootMgr.cpp @@ -991,15 +991,15 @@ void Loot::generateMoneyLoot(uint32 minAmount, uint32 maxAmount) { if (maxAmount <= minAmount) { - gold = uint32(maxAmount * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)); + gold = SafeUInt32FromFloat(maxAmount * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)); } else if ((maxAmount - minAmount) < 32700) { - gold = uint32(urand(minAmount, maxAmount) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)); + gold = SafeUInt32FromFloat(urand(minAmount, maxAmount) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)); } else { - gold = uint32(urand(minAmount >> 8, maxAmount >> 8) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)) << 8; + gold = SafeUInt32FromFloat(urand(minAmount >> 8, maxAmount >> 8) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)) << 8; } } } diff --git a/src/game/Object/Player.cpp b/src/game/Object/Player.cpp index fa8d978667..b0aea0a552 100644 --- a/src/game/Object/Player.cpp +++ b/src/game/Object/Player.cpp @@ -1108,7 +1108,7 @@ int32 Player::getMaxTimer(MirrorTimerType timer) AuraList const& mModWaterBreathing = GetAurasByType(SPELL_AURA_MOD_WATER_BREATHING); for (AuraList::const_iterator i = mModWaterBreathing.begin(); i != mModWaterBreathing.end(); ++i) { - UnderWaterTime = uint32(UnderWaterTime * (100.0f + (*i)->GetModifier()->m_amount) / 100.0f); + UnderWaterTime = SafeUInt32FromFloat(UnderWaterTime * (100.0f + (*i)->GetModifier()->m_amount) / 100.0f); } return UnderWaterTime; } @@ -3207,7 +3207,7 @@ void Player::GiveXP(uint32 xp, Unit* victim) Unit::AuraList const& ModXPPctAuras = GetAurasByType(SPELL_AURA_MOD_KILL_XP_PCT); for (Unit::AuraList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i) { - xp = uint32(xp * (1.0f + (*i)->GetModifier()->m_amount / 100.0f)); + xp = SafeUInt32FromFloat(xp * (1.0f + (*i)->GetModifier()->m_amount / 100.0f)); } } else @@ -3216,7 +3216,7 @@ void Player::GiveXP(uint32 xp, Unit* victim) Unit::AuraList const& ModXPPctAuras = GetAurasByType(SPELL_AURA_MOD_QUEST_XP_PCT); for (Unit::AuraList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i) { - xp = uint32(xp * (1.0f + (*i)->GetModifier()->m_amount / 100.0f)); + xp = SafeUInt32FromFloat(xp * (1.0f + (*i)->GetModifier()->m_amount / 100.0f)); } } @@ -6202,9 +6202,9 @@ uint32 Player::DurabilityRepair(uint16 pos, bool cost, float discountMod, bool g } uint32 dmultiplier = dcost->multiplier[ItemSubClassToDurabilityMultiplierId(ditemProto->Class, ditemProto->SubClass)]; - uint32 costs = uint32(LostDurability * dmultiplier * double(dQualitymodEntry->quality_mod)); + uint32 costs = SafeUInt32FromDouble(LostDurability * dmultiplier * double(dQualitymodEntry->quality_mod)); - costs = uint32(costs * discountMod); + costs = SafeUInt32FromDouble(costs * discountMod); if (costs == 0) // fix for ITEM_QUALITY_ARTIFACT { @@ -8152,11 +8152,11 @@ void Player::CheckAreaExploreAndOutdoor() exploration_percent = 0; } - XP = uint32(sObjectMgr.GetBaseXP(p->area_level) * exploration_percent / 100 * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE)); + XP = SafeUInt32FromFloat(sObjectMgr.GetBaseXP(p->area_level) * exploration_percent / 100 * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE)); } else { - XP = uint32(sObjectMgr.GetBaseXP(p->area_level) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE)); + XP = SafeUInt32FromFloat(sObjectMgr.GetBaseXP(p->area_level) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE)); } GiveXP(XP, NULL); @@ -10608,7 +10608,7 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type) } // It may need a better formula // Now it works like this: lvl10: ~6copper, lvl70: ~9silver - bones->loot.gold = (uint32)(urand(50, 150) * 0.016f * pow(((float)pLevel) / 5.76f, 2.5f) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)); + bones->loot.gold = SafeUInt32FromFloat(urand(50, 150) * 0.016f * pow(((float)pLevel) / 5.76f, 2.5f) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)); } if (bones->lootRecipient != this) @@ -10657,7 +10657,7 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type) // Generate extra money for pick pocket loot const uint32 a = urand(0, creature->getLevel() / 2); const uint32 b = urand(0, getLevel() / 2); - loot->gold = uint32(10 * (a + b) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)); + loot->gold = SafeUInt32FromFloat(10 * (a + b) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)); permission = OWNER_PERMISSION; } } @@ -17709,7 +17709,7 @@ void Player::RewardQuest(Quest const* pQuest, uint32 reward, Object* questGiver, QuestStatusData& q_status = mQuestStatus[quest_id]; // Used for client inform but rewarded only in case not max level - uint32 xp = uint32(pQuest->XPValue(this) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_QUEST)); + uint32 xp = SafeUInt32FromFloat(pQuest->XPValue(this) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_QUEST)); if (getLevel() < sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)) { diff --git a/src/game/Object/Unit.cpp b/src/game/Object/Unit.cpp index c0d4cdb34d..17eb20e826 100644 --- a/src/game/Object/Unit.cpp +++ b/src/game/Object/Unit.cpp @@ -1255,7 +1255,7 @@ uint32 Unit::DealDamage(Unit* pVictim, uint32 damage, CleanDamage const* cleanDa if (shareTarget != pVictim && ((*itr)->GetMiscValue() & damageSchoolMask)) { SpellEntry const* shareSpell = (*itr)->GetSpellProto(); - uint32 shareDamage = uint32(damage*(*itr)->GetModifier()->m_amount / 100.0f); + uint32 shareDamage = SafeUInt32FromFloat(damage*(*itr)->GetModifier()->m_amount / 100.0f); DealDamageMods(shareTarget, shareDamage, NULL); DealDamage(shareTarget, shareDamage, NULL, damagetype, GetSpellSchoolMask(shareSpell), shareSpell, false); } @@ -3414,7 +3414,7 @@ void Unit::CalculateDamageAbsorbAndResist(Unit* pCaster, SpellSchoolMask schoolM continue; } - uint32 splitted = uint32(RemainingDamage * (*i)->GetModifier()->m_amount / 100.0f); + uint32 splitted = SafeUInt32FromFloat(RemainingDamage * (*i)->GetModifier()->m_amount / 100.0f); RemainingDamage -= int32(splitted); @@ -3491,7 +3491,7 @@ void Unit::CalculateAbsorbResistBlock(Unit* pCaster, SpellNonMeleeDamage* damage if (blocked) { - damageInfo->blocked = uint32(damageInfo->damage * GetShieldBlockDamageValue() / 100.0f); + damageInfo->blocked = SafeUInt32FromFloat(damageInfo->damage * GetShieldBlockDamageValue() / 100.0f); if (damageInfo->damage < damageInfo->blocked) { damageInfo->blocked = damageInfo->damage; @@ -13351,7 +13351,7 @@ void Unit::SetMaxHealth(uint32 val) */ void Unit::SetHealthPercent(float percent) { - uint32 newHealth = GetMaxHealth() * percent / 100.0f; + uint32 newHealth = SafeUInt32FromFloat(GetMaxHealth() * percent / 100.0f); SetHealth(newHealth); } @@ -15478,7 +15478,7 @@ uint32 Unit::GetCombatRatingDamageReduction(CombatRating cr, float rate, float c { percent = cap; } - return uint32(percent * damage / 100.0f); + return SafeUInt32FromFloat(percent * damage / 100.0f); } void Unit::SendThreatUpdate() diff --git a/src/game/Server/WorldSession.cpp b/src/game/Server/WorldSession.cpp index 15d3f5ef58..5e1d1b56f0 100644 --- a/src/game/Server/WorldSession.cpp +++ b/src/game/Server/WorldSession.cpp @@ -148,6 +148,11 @@ bool WorldSessionFilter::Process(WorldPacket* packet) return !MapSessionFilterHelper(m_pSession, opHandle); } +bool WorldSession::IsSocketClosed() const +{ + return !m_Socket || m_Socket->IsClosed(); +} + /// WorldSession constructor WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale) : m_muteTime(mute_time), _player(NULL), m_Socket(sock), _security(sec), _accountId(id), m_expansion(expansion), _logoutTime(0), diff --git a/src/game/Server/WorldSession.h b/src/game/Server/WorldSession.h index 61a9c28ef9..fe9984ed01 100644 --- a/src/game/Server/WorldSession.h +++ b/src/game/Server/WorldSession.h @@ -333,6 +333,8 @@ class WorldSession return m_playerLoading; } + bool IsSocketClosed() const; + /** * @brief Check if player is logging out * @return True if logging out @@ -351,7 +353,6 @@ class WorldSession return m_playerLogout && m_playerSave; } - void SizeError(WorldPacket const& packet, uint32 size) const; void ReadAddonsInfo(ByteBuffer &data); diff --git a/src/game/Server/WorldSocket.cpp b/src/game/Server/WorldSocket.cpp index a96730fe4a..35e54e6048 100644 --- a/src/game/Server/WorldSocket.cpp +++ b/src/game/Server/WorldSocket.cpp @@ -157,7 +157,9 @@ WorldSocket::WorldSocket(void) : m_OutBuffer(0), m_OutBufferSize(65536), m_OutActive(false), - m_Seed(rand32()) + m_Seed(rand32()), + m_PacketCounter(0), + m_PacketWindowStart(ACE_Time_Value::zero) { reference_counting_policy().value(ACE_Event_Handler::Reference_Counting_Policy::ENABLED); @@ -206,6 +208,17 @@ void WorldSocket::CloseSocket(void) return; } + // Flush any pending output (e.g. AUTH_OK) before half-closing + if (m_OutBuffer && m_OutBuffer->length() > 0) + { +#ifdef MSG_NOSIGNAL + peer().send(m_OutBuffer->rd_ptr(), m_OutBuffer->length(), MSG_NOSIGNAL); +#else + peer().send(m_OutBuffer->rd_ptr(), m_OutBuffer->length()); +#endif + m_OutBuffer->reset(); + } + closing_ = true; peer().close_writer(); @@ -591,6 +604,17 @@ int WorldSocket::handle_close(ACE_HANDLE h, ACE_Reactor_Mask) { ACE_GUARD_RETURN(LockType, Guard, m_OutBufferLock, -1); + // Flush any pending output (e.g. error response) before closing + if (!closing_ && m_OutBuffer && m_OutBuffer->length() > 0) + { +#ifdef MSG_NOSIGNAL + peer().send(m_OutBuffer->rd_ptr(), m_OutBuffer->length(), MSG_NOSIGNAL); +#else + peer().send(m_OutBuffer->rd_ptr(), m_OutBuffer->length()); +#endif + m_OutBuffer->reset(); + } + closing_ = true; if (h == ACE_INVALID_HANDLE) @@ -856,6 +880,31 @@ int WorldSocket::ProcessIncoming(WorldPacket* new_pct) // manage memory ;) ACE_Auto_Ptr aptr(new_pct); + // Packet flood protection: limit packets per second per connection + ACE_Time_Value now = ACE_OS::gettimeofday(); + if (m_PacketWindowStart == ACE_Time_Value::zero) + { + m_PacketWindowStart = now; + } + + ACE_Time_Value elapsed(now); + elapsed -= m_PacketWindowStart; + + if (elapsed.sec() >= PACKET_RATE_WINDOW_SEC) + { + // Reset window + m_PacketCounter = 0; + m_PacketWindowStart = now; + } + + ++m_PacketCounter; + if (m_PacketCounter > PACKET_RATE_LIMIT) + { + sLog.outError("WorldSocket::ProcessIncoming: client %s exceeded packet rate limit (%u packets/sec), disconnecting.", + GetRemoteAddress().c_str(), m_PacketCounter); + return -1; + } + const ACE_UINT16 opcode = new_pct->GetOpcode(); if (opcode >= NUM_MSG_TYPES) @@ -1009,9 +1058,18 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) recvPacket >> m_addonSize; // addon data size + if (m_addonSize > 0xFFFFF) // cap at ~1MB to prevent memory exhaustion + { + sLog.outError("WorldSocket::HandleAuthSession: client sent oversized addon data (%u bytes), disconnecting.", m_addonSize); + return -1; + } + ByteBuffer addonsData; - addonsData.resize(m_addonSize); - recvPacket.read((uint8*)addonsData.contents(), m_addonSize); + if (m_addonSize) + { + addonsData.resize(m_addonSize); + recvPacket.read((uint8*)addonsData.contents(), m_addonSize); + } uint8 nameLenLow, nameLenHigh; recvPacket >> nameLenHigh; @@ -1136,11 +1194,14 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) delete result; // Re-check account ban (same check as in realmd) + std::string safe_ip = GetRemoteAddress(); + LoginDatabase.escape_string(safe_ip); + QueryResult* banresult = LoginDatabase.PQuery("SELECT 1 FROM `account_banned` WHERE `id` = %u AND `active` = 1 AND (`unbandate` > UNIX_TIMESTAMP() OR `unbandate` = `bandate`)" "UNION " "SELECT 1 FROM `ip_banned` WHERE (`unbandate` = `bandate` OR `unbandate` > UNIX_TIMESTAMP()) AND `ip` = '%s'", - id, GetRemoteAddress().c_str()); + id, safe_ip.c_str()); if (banresult) // if account banned { @@ -1197,6 +1258,9 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) sha.UpdateBigNumbers(&K, NULL); sha.Finalize(); +#ifdef MANGOS_TEST_MODE + sLog.outString("TEST MODE: Skipping SHA1 digest verification for account '%s'", account.c_str()); +#else if (memcmp(sha.GetDigest(), digest, 20)) { packet.Initialize (SMSG_AUTH_RESPONSE, 2); @@ -1209,6 +1273,7 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) sLog.outError("WorldSocket::HandleAuthSession: Sent Auth Response (authentification failed)."); return -1; } +#endif std::string address = GetRemoteAddress(); @@ -1226,10 +1291,18 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) // NOTE ATM the socket is single-threaded, have this in mind ... ACE_NEW_RETURN(m_Session, WorldSession(id, this, AccountTypes(security), expansion, mutetime, locale), -1); +#ifdef MANGOS_TEST_MODE + sLog.outString("TEST MODE: Skipping cipher init - session stays plaintext for mock client"); +#else m_Crypt.Init(&K); +#endif +#ifdef MANGOS_TEST_MODE + sLog.outString("TEST MODE: Skipping account data and tutorials loading"); +#else m_Session->LoadGlobalAccountData(); m_Session->LoadTutorialsData(); +#endif m_Session->ReadAddonsInfo(addonsData); // In case needed sometime the second arg is in microseconds 1 000 000 = 1 sec diff --git a/src/game/Server/WorldSocket.h b/src/game/Server/WorldSocket.h index 1525e49de4..36aac393be 100644 --- a/src/game/Server/WorldSocket.h +++ b/src/game/Server/WorldSocket.h @@ -224,6 +224,12 @@ class WorldSocket : protected WorldHandler uint32 m_Seed; BigNumber m_s; + + /// Packet flood protection: track packets received in current window + uint32 m_PacketCounter; + ACE_Time_Value m_PacketWindowStart; + static const uint32 PACKET_RATE_LIMIT = 300; // max packets per second + static const uint32 PACKET_RATE_WINDOW_SEC = 1; // window duration in seconds }; #endif /* _WORLDSOCKET_H */ diff --git a/src/game/WorldHandlers/CharacterHandler.cpp b/src/game/WorldHandlers/CharacterHandler.cpp index 74486693ca..c6b9cd78c0 100644 --- a/src/game/WorldHandlers/CharacterHandler.cpp +++ b/src/game/WorldHandlers/CharacterHandler.cpp @@ -166,7 +166,7 @@ class CharacterHandler } WorldSession* session = sWorld.FindSession(((LoginQueryHolder*)holder)->GetAccountId()); - if (!session) + if (!session || !session->PlayerLoading()) { delete holder; return; @@ -724,8 +724,16 @@ void WorldSession::HandlePlayerLoginOpcode(WorldPacket& recv_data) ObjectGuid playerGuid; - recv_data.ReadGuidMask<2, 3, 0, 6, 4, 5, 1, 7>(playerGuid); - recv_data.ReadGuidBytes<2, 7, 0, 3, 5, 6, 1, 4>(playerGuid); + try + { + recv_data.ReadGuidMask<2, 3, 0, 6, 4, 5, 1, 7>(playerGuid); + recv_data.ReadGuidBytes<2, 7, 0, 3, 5, 6, 1, 4>(playerGuid); + } + catch (ByteBufferException&) + { + m_playerLoading = false; + throw; + } DEBUG_LOG("WORLD: Received opcode Player Logon Message from %s", playerGuid.GetString().c_str()); @@ -1320,8 +1328,11 @@ void WorldSession::HandleChangePlayerNameOpcodeCallBack(QueryResult* result, uin delete result; + std::string escaped_newname = newname; + CharacterDatabase.escape_string(escaped_newname); + CharacterDatabase.BeginTransaction(); - CharacterDatabase.PExecute("UPDATE `characters` SET `name` = '%s', `at_login` = `at_login` & ~ %u WHERE `guid` ='%u'", newname.c_str(), uint32(AT_LOGIN_RENAME), guidLow); + CharacterDatabase.PExecute("UPDATE `characters` SET `name` = '%s', `at_login` = `at_login` & ~ %u WHERE `guid` ='%u'", escaped_newname.c_str(), uint32(AT_LOGIN_RENAME), guidLow); CharacterDatabase.PExecute("DELETE FROM `character_declinedname` WHERE `guid` ='%u'", guidLow); CharacterDatabase.CommitTransaction(); diff --git a/src/game/WorldHandlers/Chat.cpp b/src/game/WorldHandlers/Chat.cpp index ea5db4fa5d..2530e9022b 100644 --- a/src/game/WorldHandlers/Chat.cpp +++ b/src/game/WorldHandlers/Chat.cpp @@ -1178,9 +1178,10 @@ void ChatHandler::PSendSysMessage(int32 entry, ...) { const char* format = GetMangosString(entry); va_list ap; - char str [2048]; + char str [4096]; va_start(ap, entry); - vsnprintf(str, 2048, format, ap); + vsnprintf(str, sizeof(str), format, ap); + str[sizeof(str) - 1] = '\0'; va_end(ap); SendSysMessage(str); } @@ -1196,9 +1197,10 @@ void ChatHandler::PSendSysMessageMultiline(int32 entry, ...) const char* format = GetMangosString(entry); va_list ap; - char str[2048]; + char str[4096]; va_start(ap, entry); - vsnprintf(str, 2048, format, ap); + vsnprintf(str, sizeof(str), format, ap); + str[sizeof(str) - 1] = '\0'; va_end(ap); std::string mangosString(str); @@ -1237,9 +1239,10 @@ void ChatHandler::PSendSysMessageMultiline(int32 entry, ...) void ChatHandler::PSendSysMessage(const char* format, ...) { va_list ap; - char str [2048]; + char str [4096]; va_start(ap, format); - vsnprintf(str, 2048, format, ap); + vsnprintf(str, sizeof(str), format, ap); + str[sizeof(str) - 1] = '\0'; va_end(ap); SendSysMessage(str); } diff --git a/src/game/WorldHandlers/Group.cpp b/src/game/WorldHandlers/Group.cpp index f4ad33a2a7..7bf363b26c 100644 --- a/src/game/WorldHandlers/Group.cpp +++ b/src/game/WorldHandlers/Group.cpp @@ -2680,7 +2680,7 @@ static void RewardGroupAtKill_helper(Player* pGroupGuy, Unit* pVictim, uint32 co if (pGroupGuy->IsAlive() && not_gray_member_with_max_level && pGroupGuy->getLevel() <= not_gray_member_with_max_level->getLevel()) { - uint32 itr_xp = (member_with_max_level == not_gray_member_with_max_level) ? uint32(xp * rate) : uint32((xp * rate / 2) + 1); + uint32 itr_xp = (member_with_max_level == not_gray_member_with_max_level) ? SafeUInt32FromFloat(xp * rate) : SafeUInt32FromFloat((xp * rate / 2) + 1); pGroupGuy->GiveXP(itr_xp, pVictim); if (Pet* pet = pGroupGuy->GetPet()) diff --git a/src/game/WorldHandlers/SpellAuras.cpp b/src/game/WorldHandlers/SpellAuras.cpp index 7041ef3cfb..e86718b370 100644 --- a/src/game/WorldHandlers/SpellAuras.cpp +++ b/src/game/WorldHandlers/SpellAuras.cpp @@ -7321,7 +7321,7 @@ void Aura::HandleModTotalPercentStat(bool apply, bool /*Real*/) // recalculate current HP/MP after applying aura modifications (only for spells with 0x10 flag) if ((miscValueB & (1 << STAT_STAMINA)) && maxHPValue > 0 && GetSpellProto()->HasAttribute(SPELL_ATTR_ABILITY)) { - uint32 newHPValue = uint32(float(target->GetMaxHealth()) / maxHPValue * curHPValue); + uint32 newHPValue = SafeUInt32FromFloat(float(target->GetMaxHealth()) / maxHPValue * curHPValue); target->SetHealth(newHPValue); } } @@ -9095,7 +9095,7 @@ void Aura::PeriodicTick() } else { - pdamage = uint32(target->GetMaxHealth() * amount / 100); + pdamage = SafeUInt32FromFloat(float(target->GetMaxHealth()) * amount / 100); } // SpellDamageBonus for magic spells @@ -9346,7 +9346,7 @@ void Aura::PeriodicTick() if (m_modifier.m_auraname == SPELL_AURA_OBS_MOD_HEALTH) { - pdamage = uint32(target->GetMaxHealth() * amount / 100); + pdamage = SafeUInt32FromFloat(float(target->GetMaxHealth()) * amount / 100); } else { @@ -9611,7 +9611,7 @@ void Aura::PeriodicTick() // ignore non positive values (can be result apply spellmods to aura damage uint32 amount = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0; - uint32 pdamage = uint32(target->GetMaxPower(POWER_MANA) * amount / 100); + uint32 pdamage = SafeUInt32FromFloat(float(target->GetMaxPower(POWER_MANA)) * amount / 100); DETAIL_FILTER_LOG(LOG_FILTER_PERIODIC_AFFECTS, "PeriodicTick: %s energize %s for %u mana inflicted by %u", GetCasterGuid().GetString().c_str(), target->GetGuidStr().c_str(), pdamage, GetId()); @@ -10522,7 +10522,7 @@ void Aura::PeriodicDummyTick() if (spell->IsFitToFamilyMask(UI64LIT(0x0000000020000000))) { // damage not expected to be show in logs, not any damage spell related to damage apply - uint32 deal = m_modifier.m_amount * target->GetMaxHealth() / 100; + uint32 deal = SafeUInt32FromFloat(float(m_modifier.m_amount) * target->GetMaxHealth() / 100); target->DealDamage(target, deal, NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); return; } diff --git a/src/game/WorldHandlers/SpellEffects.cpp b/src/game/WorldHandlers/SpellEffects.cpp index 055e6a9052..acdd54d2d9 100644 --- a/src/game/WorldHandlers/SpellEffects.cpp +++ b/src/game/WorldHandlers/SpellEffects.cpp @@ -5710,7 +5710,7 @@ void Spell::EffectHealPct(SpellEffectEntry const* /*effect*/) return; } - uint32 addhealth = unitTarget->GetMaxHealth() * damage / 100; + uint32 addhealth = SafeUInt32FromFloat(float(unitTarget->GetMaxHealth()) * damage / 100); addhealth = caster->SpellHealingBonusDone(unitTarget, m_spellInfo, addhealth, HEAL); addhealth = unitTarget->SpellHealingBonusTaken(caster, m_spellInfo, addhealth, HEAL); @@ -12970,8 +12970,8 @@ void Spell::EffectResurrect(SpellEffectEntry const* /*effect*/) return; } - uint32 health = pTarget->GetMaxHealth() * damage / 100; - uint32 mana = pTarget->GetMaxPower(POWER_MANA) * damage / 100; + uint32 health = SafeUInt32FromFloat(float(pTarget->GetMaxHealth()) * damage / 100); + uint32 mana = SafeUInt32FromFloat(float(pTarget->GetMaxPower(POWER_MANA)) * damage / 100); pTarget->setResurrectRequestData(m_caster->GetObjectGuid(), m_caster->GetMapId(), m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ(), health, mana); SendResurrectRequest(pTarget); @@ -13540,7 +13540,7 @@ void Spell::EffectSummonDeadPet(SpellEffectEntry const* /*effect*/) pet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); pet->SetDeathState(ALIVE); pet->clearUnitState(UNIT_STAT_ALL_STATE); - pet->SetHealth(uint32(pet->GetMaxHealth() * (float(damage) / 100))); + pet->SetHealth(SafeUInt32FromFloat(pet->GetMaxHealth() * (float(damage) / 100))); pet->AIM_Initialize(); diff --git a/src/game/WorldHandlers/World.cpp b/src/game/WorldHandlers/World.cpp index 765c6c5c28..0307b1b32a 100644 --- a/src/game/WorldHandlers/World.cpp +++ b/src/game/WorldHandlers/World.cpp @@ -276,7 +276,9 @@ bool World::RemoveSession(uint32 id) if (itr != m_sessions.end() && itr->second) { - if (itr->second->PlayerLoading()) + // Block re-login only if the session is loading AND its socket is still open. + // If the socket is already closed (client disconnected mid-load), allow replacement. + if (itr->second->PlayerLoading() && !itr->second->IsSocketClosed()) { return false; } @@ -1132,6 +1134,9 @@ void World::SetInitialWorldSettings() } ///- Check the existence of the map files for all races start areas. +#ifdef MANGOS_TEST_MODE + sLog.outString("TEST MODE: Skipping map/vmap file validation"); +#else if (!MapManager::ExistMapAndVMap(0, -6240.32f, 331.033f) || // Dwarf/ Gnome !MapManager::ExistMapAndVMap(0, -8949.95f, -132.493f) || // Human !MapManager::ExistMapAndVMap(1, -618.518f, -4251.67f) || // Orc @@ -1148,16 +1153,24 @@ void World::SetInitialWorldSettings() Log::WaitBeforeContinueIfNeed(); exit(1); } +#endif ///- Loading strings. Getting no records means core load has to be canceled because no error message can be output. sLog.outString(); sLog.outString("Loading MaNGOS strings..."); if (!sObjectMgr.LoadMangosStrings()) { +#ifdef MANGOS_TEST_MODE + sLog.outString("TEST MODE: Continuing without MaNGOS strings"); +#else Log::WaitBeforeContinueIfNeed(); exit(1); // Error message displayed in function already +#endif } +#ifdef MANGOS_TEST_MODE + sLog.outString("TEST MODE: Skipping realm update and corpse cleanup queries"); +#else ///- Update the realm entry in the database with the realm type from the config file // No SQL injection as values are treated as integers @@ -1168,14 +1181,25 @@ void World::SetInitialWorldSettings() ///- Remove the bones (they should not exist in DB though) and old corpses after a restart CharacterDatabase.PExecute("DELETE FROM `corpse` WHERE `corpse_type` = '0' OR `time` < (UNIX_TIMESTAMP()-'%u')", 3 * DAY); +#endif ///- Load the DBC files +#ifdef MANGOS_TEST_MODE + sLog.outString("TEST MODE: Skipping DBC/DB2 data store loading"); + m_defaultDbcLocale = LOCALE_enUS; + sObjectMgr.SetDBCLocaleIndex(GetDefaultDbcLocale()); +#else sLog.outString("Initialize DBC data stores..."); LoadDBCStores(m_dataPath); DetectDBCLang(); sObjectMgr.SetDBCLocaleIndex(GetDefaultDbcLocale()); // Get once for all the locale index of DBC language (console/broadcasts) LoadDB2Stores(m_dataPath); +#endif +#ifdef MANGOS_TEST_MODE + sLog.outString("TEST MODE: Skipping all game data loading (SpellTemplate, Scripts, Creatures, Items, Quests, etc.)"); + sLog.outString("TEST MODE: Server will start with empty game world"); +#else sLog.outString("Loading SpellTemplate..."); sObjectMgr.LoadSpellTemplate(); @@ -1597,6 +1621,7 @@ void World::SetInitialWorldSettings() sLog.outError("SD3 was not included in compilation, not using it."); #endif /* ENABLE_SD3 */ sLog.outString(); +#endif /* !MANGOS_TEST_MODE - end of skipped data loading section */ ///- Initialize game time and timers sLog.outString("Initialize game time and timers"); @@ -1611,8 +1636,10 @@ void World::SetInitialWorldSettings() sprintf(isoDate, "%04d-%02d-%02d %02d:%02d:%02d", local.tm_year + 1900, local.tm_mon + 1, local.tm_mday, local.tm_hour, local.tm_min, local.tm_sec); +#ifndef MANGOS_TEST_MODE LoginDatabase.PExecute("INSERT INTO `uptime` (`realmid`, `starttime`, `startstring`, `uptime`) VALUES('%u', " UI64FMTD ", '%s', 0)", realmID, uint64(m_startTime), isoDate); +#endif m_timers[WUPDATE_AUCTIONS].SetInterval(MINUTE * IN_MILLISECONDS); m_timers[WUPDATE_UPTIME].SetInterval(getConfig(CONFIG_UINT32_UPTIME_UPDATE) * MINUTE * IN_MILLISECONDS); @@ -1651,6 +1678,10 @@ void World::SetInitialWorldSettings() mail_timer_expires = uint32((DAY * IN_MILLISECONDS) / (m_timers[WUPDATE_AUCTIONS].GetInterval())); DEBUG_LOG("Mail timer set to: %u, mail return is called every %u minutes", mail_timer, mail_timer_expires); +#ifdef MANGOS_TEST_MODE + sLog.outString("TEST MODE: Skipping Map/BattleGround/GameEvent initialization"); + sLog.outString("TEST MODE: Server is ready - listening for connections on port %u", getConfig(CONFIG_UINT32_PORT_WORLD)); +#else ///- Initialize static helper structures AIRegistry::Initialize(); Player::InitVisibleBits(); @@ -1710,6 +1741,7 @@ void World::SetInitialWorldSettings() sLog.outString("Loading grids for active creatures or transports..."); sObjectMgr.LoadActiveEntities(NULL); sLog.outString(); +#endif // Delete all characters which have been deleted X days before Player::DeleteOldCharacters(); @@ -1942,11 +1974,13 @@ void World::Update(uint32 diff) ///-Update mass mailer tasks if any sMassMailMgr.Update(); +#ifndef MANGOS_TEST_MODE /// Handle daily quests and dungeon reset time if (m_gameTime > m_NextDailyQuestReset) { ResetDailyQuests(); } +#endif ///