diff options
Diffstat (limited to 'src/common')
84 files changed, 13881 insertions, 0 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt new file mode 100644 index 00000000000..7f20fe36ab6 --- /dev/null +++ b/src/common/CMakeLists.txt @@ -0,0 +1,91 @@ +# Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +if( USE_COREPCH ) + include_directories(${CMAKE_CURRENT_BINARY_DIR}) +endif() + +file(GLOB_RECURSE sources_Common Common.cpp Common.h) +file(GLOB_RECURSE sources_Collision Collision/*.cpp Collision/*.h) +file(GLOB_RECURSE sources_Threading Threading/*.cpp Threading/*.h) +file(GLOB_RECURSE sources_Utilities Utilities/*.cpp Utilities/*.h) +file(GLOB_RECURSE sources_Configuration Configuration/*.cpp Configuration/*.h) +file(GLOB_RECURSE sources_Logging Logging/*.cpp Logging/*.h) +if (SERVERS) + file(GLOB_RECURSE sources_Cryptography Cryptography/*.cpp Cryptography/*.h) +endif (SERVERS) + +# Manually set sources for Debugging directory as we don't want to include WheatyExceptionReport in common project +# It needs to be included both in authserver and worldserver for the static global variable to be properly initialized +# and to handle crash logs on windows +set(sources_Debugging Debugging/Errors.cpp Debugging/Errors.h) +file(GLOB sources_localdir *.cpp *.h) + +if (USE_COREPCH) + set(common_STAT_PCH_HDR PrecompiledHeaders/commonPCH.h) + set(common_STAT_PCH_SRC PrecompiledHeaders/commonPCH.cpp) +endif (USE_COREPCH) + +set(common_STAT_SRCS + ${common_STAT_SRCS} + ${sources_Common} + ${sources_Collision} + ${sources_Threading} + ${sources_Utilities} + ${sources_Debugging} + ${sources_Configuration} + ${sources_Logging} + ${sources_Cryptography} + ${sources_localdir} +) + +# Do NOT add any extra include directory here, as we don't want the common +# library to depend on anything else than TC deps, and itself. +# This way we ensure that if either a PR does that without modifying this file, +# a compile error will be generated, either this file will be modified so it +# is detected more easily. +# While it is OK to include files from other libs as long as they don't require +# linkage (enums, defines...) it is discouraged to do so unless necessary, as it will pullute +# include_directories leading to further unnoticed dependency aditions +# Linker Depencency requirements: none +include_directories( + ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/Collision + ${CMAKE_CURRENT_SOURCE_DIR}/Collision/Management + ${CMAKE_CURRENT_SOURCE_DIR}/Collision/Maps + ${CMAKE_CURRENT_SOURCE_DIR}/Collision/Models + ${CMAKE_CURRENT_SOURCE_DIR}/Configuration + ${CMAKE_CURRENT_SOURCE_DIR}/Cryptography + ${CMAKE_CURRENT_SOURCE_DIR}/Debugging + ${CMAKE_CURRENT_SOURCE_DIR}/Logging + ${CMAKE_CURRENT_SOURCE_DIR}/Utilities + ${CMAKE_SOURCE_DIR}/dep/cppformat + ${CMAKE_SOURCE_DIR}/dep/g3dlite/include + ${CMAKE_SOURCE_DIR}/dep/recastnavigation/Detour/Include + ${CMAKE_SOURCE_DIR}/dep/SFMT + ${CMAKE_SOURCE_DIR}/dep/utf8cpp + ${OPENSSL_INCLUDE_DIR} + ${VALGRIND_INCLUDE_DIR} +) + +GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(common STATIC + ${common_STAT_SRCS} + ${common_STAT_PCH_SRC} +) + +add_dependencies(common revision_data.h) + +# Generate precompiled header +if (USE_COREPCH) + add_cxx_pch(common ${common_STAT_PCH_HDR} ${common_STAT_PCH_SRC}) +endif () diff --git a/src/common/Collision/BoundingIntervalHierarchy.cpp b/src/common/Collision/BoundingIntervalHierarchy.cpp new file mode 100644 index 00000000000..12af680712e --- /dev/null +++ b/src/common/Collision/BoundingIntervalHierarchy.cpp @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "BoundingIntervalHierarchy.h" + +#ifdef _MSC_VER + #define isnan _isnan +#else + #define isnan std::isnan +#endif + +void BIH::buildHierarchy(std::vector<uint32> &tempTree, buildData &dat, BuildStats &stats) +{ + // create space for the first node + tempTree.push_back(uint32(3 << 30)); // dummy leaf + tempTree.insert(tempTree.end(), 2, 0); + //tempTree.add(0); + + // seed bbox + AABound gridBox = { bounds.low(), bounds.high() }; + AABound nodeBox = gridBox; + // seed subdivide function + subdivide(0, dat.numPrims - 1, tempTree, dat, gridBox, nodeBox, 0, 1, stats); +} + +void BIH::subdivide(int left, int right, std::vector<uint32> &tempTree, buildData &dat, AABound &gridBox, AABound &nodeBox, int nodeIndex, int depth, BuildStats &stats) +{ + if ((right - left + 1) <= dat.maxPrims || depth >= MAX_STACK_SIZE) + { + // write leaf node + stats.updateLeaf(depth, right - left + 1); + createNode(tempTree, nodeIndex, left, right); + return; + } + // calculate extents + int axis = -1, prevAxis, rightOrig; + float clipL = G3D::fnan(), clipR = G3D::fnan(), prevClip = G3D::fnan(); + float split = G3D::fnan(), prevSplit; + bool wasLeft = true; + while (true) + { + prevAxis = axis; + prevSplit = split; + // perform quick consistency checks + G3D::Vector3 d( gridBox.hi - gridBox.lo ); + if (d.x < 0 || d.y < 0 || d.z < 0) + throw std::logic_error("negative node extents"); + for (int i = 0; i < 3; i++) + { + if (nodeBox.hi[i] < gridBox.lo[i] || nodeBox.lo[i] > gridBox.hi[i]) + { + //UI.printError(Module.ACCEL, "Reached tree area in error - discarding node with: %d objects", right - left + 1); + throw std::logic_error("invalid node overlap"); + } + } + // find longest axis + axis = d.primaryAxis(); + split = 0.5f * (gridBox.lo[axis] + gridBox.hi[axis]); + // partition L/R subsets + clipL = -G3D::finf(); + clipR = G3D::finf(); + rightOrig = right; // save this for later + float nodeL = G3D::finf(); + float nodeR = -G3D::finf(); + for (int i = left; i <= right;) + { + int obj = dat.indices[i]; + float minb = dat.primBound[obj].low()[axis]; + float maxb = dat.primBound[obj].high()[axis]; + float center = (minb + maxb) * 0.5f; + if (center <= split) + { + // stay left + i++; + if (clipL < maxb) + clipL = maxb; + } + else + { + // move to the right most + int t = dat.indices[i]; + dat.indices[i] = dat.indices[right]; + dat.indices[right] = t; + right--; + if (clipR > minb) + clipR = minb; + } + nodeL = std::min(nodeL, minb); + nodeR = std::max(nodeR, maxb); + } + // check for empty space + if (nodeL > nodeBox.lo[axis] && nodeR < nodeBox.hi[axis]) + { + float nodeBoxW = nodeBox.hi[axis] - nodeBox.lo[axis]; + float nodeNewW = nodeR - nodeL; + // node box is too big compare to space occupied by primitives? + if (1.3f * nodeNewW < nodeBoxW) + { + stats.updateBVH2(); + int nextIndex = tempTree.size(); + // allocate child + tempTree.push_back(0); + tempTree.push_back(0); + tempTree.push_back(0); + // write bvh2 clip node + stats.updateInner(); + tempTree[nodeIndex + 0] = (axis << 30) | (1 << 29) | nextIndex; + tempTree[nodeIndex + 1] = floatToRawIntBits(nodeL); + tempTree[nodeIndex + 2] = floatToRawIntBits(nodeR); + // update nodebox and recurse + nodeBox.lo[axis] = nodeL; + nodeBox.hi[axis] = nodeR; + subdivide(left, rightOrig, tempTree, dat, gridBox, nodeBox, nextIndex, depth + 1, stats); + return; + } + } + // ensure we are making progress in the subdivision + if (right == rightOrig) + { + // all left + if (prevAxis == axis && G3D::fuzzyEq(prevSplit, split)) { + // we are stuck here - create a leaf + stats.updateLeaf(depth, right - left + 1); + createNode(tempTree, nodeIndex, left, right); + return; + } + if (clipL <= split) { + // keep looping on left half + gridBox.hi[axis] = split; + prevClip = clipL; + wasLeft = true; + continue; + } + gridBox.hi[axis] = split; + prevClip = G3D::fnan(); + } + else if (left > right) + { + // all right + if (prevAxis == axis && G3D::fuzzyEq(prevSplit, split)) { + // we are stuck here - create a leaf + stats.updateLeaf(depth, right - left + 1); + createNode(tempTree, nodeIndex, left, right); + return; + } + right = rightOrig; + if (clipR >= split) { + // keep looping on right half + gridBox.lo[axis] = split; + prevClip = clipR; + wasLeft = false; + continue; + } + gridBox.lo[axis] = split; + prevClip = G3D::fnan(); + } + else + { + // we are actually splitting stuff + if (prevAxis != -1 && !isnan(prevClip)) + { + // second time through - lets create the previous split + // since it produced empty space + int nextIndex = tempTree.size(); + // allocate child node + tempTree.push_back(0); + tempTree.push_back(0); + tempTree.push_back(0); + if (wasLeft) { + // create a node with a left child + // write leaf node + stats.updateInner(); + tempTree[nodeIndex + 0] = (prevAxis << 30) | nextIndex; + tempTree[nodeIndex + 1] = floatToRawIntBits(prevClip); + tempTree[nodeIndex + 2] = floatToRawIntBits(G3D::finf()); + } else { + // create a node with a right child + // write leaf node + stats.updateInner(); + tempTree[nodeIndex + 0] = (prevAxis << 30) | (nextIndex - 3); + tempTree[nodeIndex + 1] = floatToRawIntBits(-G3D::finf()); + tempTree[nodeIndex + 2] = floatToRawIntBits(prevClip); + } + // count stats for the unused leaf + depth++; + stats.updateLeaf(depth, 0); + // now we keep going as we are, with a new nodeIndex: + nodeIndex = nextIndex; + } + break; + } + } + // compute index of child nodes + int nextIndex = tempTree.size(); + // allocate left node + int nl = right - left + 1; + int nr = rightOrig - (right + 1) + 1; + if (nl > 0) { + tempTree.push_back(0); + tempTree.push_back(0); + tempTree.push_back(0); + } else + nextIndex -= 3; + // allocate right node + if (nr > 0) { + tempTree.push_back(0); + tempTree.push_back(0); + tempTree.push_back(0); + } + // write leaf node + stats.updateInner(); + tempTree[nodeIndex + 0] = (axis << 30) | nextIndex; + tempTree[nodeIndex + 1] = floatToRawIntBits(clipL); + tempTree[nodeIndex + 2] = floatToRawIntBits(clipR); + // prepare L/R child boxes + AABound gridBoxL(gridBox), gridBoxR(gridBox); + AABound nodeBoxL(nodeBox), nodeBoxR(nodeBox); + gridBoxL.hi[axis] = gridBoxR.lo[axis] = split; + nodeBoxL.hi[axis] = clipL; + nodeBoxR.lo[axis] = clipR; + // recurse + if (nl > 0) + subdivide(left, right, tempTree, dat, gridBoxL, nodeBoxL, nextIndex, depth + 1, stats); + else + stats.updateLeaf(depth + 1, 0); + if (nr > 0) + subdivide(right + 1, rightOrig, tempTree, dat, gridBoxR, nodeBoxR, nextIndex + 3, depth + 1, stats); + else + stats.updateLeaf(depth + 1, 0); +} + +bool BIH::writeToFile(FILE* wf) const +{ + uint32 treeSize = tree.size(); + uint32 check=0, count; + check += fwrite(&bounds.low(), sizeof(float), 3, wf); + check += fwrite(&bounds.high(), sizeof(float), 3, wf); + check += fwrite(&treeSize, sizeof(uint32), 1, wf); + check += fwrite(&tree[0], sizeof(uint32), treeSize, wf); + count = objects.size(); + check += fwrite(&count, sizeof(uint32), 1, wf); + check += fwrite(&objects[0], sizeof(uint32), count, wf); + return check == (3 + 3 + 2 + treeSize + count); +} + +bool BIH::readFromFile(FILE* rf) +{ + uint32 treeSize; + G3D::Vector3 lo, hi; + uint32 check=0, count=0; + check += fread(&lo, sizeof(float), 3, rf); + check += fread(&hi, sizeof(float), 3, rf); + bounds = G3D::AABox(lo, hi); + check += fread(&treeSize, sizeof(uint32), 1, rf); + tree.resize(treeSize); + check += fread(&tree[0], sizeof(uint32), treeSize, rf); + check += fread(&count, sizeof(uint32), 1, rf); + objects.resize(count); // = new uint32[nObjects]; + check += fread(&objects[0], sizeof(uint32), count, rf); + return uint64(check) == uint64(3 + 3 + 1 + 1 + uint64(treeSize) + uint64(count)); +} + +void BIH::BuildStats::updateLeaf(int depth, int n) +{ + numLeaves++; + minDepth = std::min(depth, minDepth); + maxDepth = std::max(depth, maxDepth); + sumDepth += depth; + minObjects = std::min(n, minObjects); + maxObjects = std::max(n, maxObjects); + sumObjects += n; + int nl = std::min(n, 5); + ++numLeavesN[nl]; +} + +void BIH::BuildStats::printStats() +{ + printf("Tree stats:\n"); + printf(" * Nodes: %d\n", numNodes); + printf(" * Leaves: %d\n", numLeaves); + printf(" * Objects: min %d\n", minObjects); + printf(" avg %.2f\n", (float) sumObjects / numLeaves); + printf(" avg(n>0) %.2f\n", (float) sumObjects / (numLeaves - numLeavesN[0])); + printf(" max %d\n", maxObjects); + printf(" * Depth: min %d\n", minDepth); + printf(" avg %.2f\n", (float) sumDepth / numLeaves); + printf(" max %d\n", maxDepth); + printf(" * Leaves w/: N=0 %3d%%\n", 100 * numLeavesN[0] / numLeaves); + printf(" N=1 %3d%%\n", 100 * numLeavesN[1] / numLeaves); + printf(" N=2 %3d%%\n", 100 * numLeavesN[2] / numLeaves); + printf(" N=3 %3d%%\n", 100 * numLeavesN[3] / numLeaves); + printf(" N=4 %3d%%\n", 100 * numLeavesN[4] / numLeaves); + printf(" N>4 %3d%%\n", 100 * numLeavesN[5] / numLeaves); + printf(" * BVH2 nodes: %d (%3d%%)\n", numBVH2, 100 * numBVH2 / (numNodes + numLeaves - 2 * numBVH2)); +} diff --git a/src/common/Collision/BoundingIntervalHierarchy.h b/src/common/Collision/BoundingIntervalHierarchy.h new file mode 100644 index 00000000000..3a09772c41f --- /dev/null +++ b/src/common/Collision/BoundingIntervalHierarchy.h @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _BIH_H +#define _BIH_H + +#include "G3D/Vector3.h" +#include "G3D/Ray.h" +#include "G3D/AABox.h" + +#include "Define.h" + +#include <stdexcept> +#include <vector> +#include <algorithm> +#include <limits> +#include <cmath> + +#define MAX_STACK_SIZE 64 + +static inline uint32 floatToRawIntBits(float f) +{ + union + { + uint32 ival; + float fval; + } temp; + temp.fval=f; + return temp.ival; +} + +static inline float intBitsToFloat(uint32 i) +{ + union + { + uint32 ival; + float fval; + } temp; + temp.ival=i; + return temp.fval; +} + +struct AABound +{ + G3D::Vector3 lo, hi; +}; + +/** Bounding Interval Hierarchy Class. + Building and Ray-Intersection functions based on BIH from + Sunflow, a Java Raytracer, released under MIT/X11 License + http://sunflow.sourceforge.net/ + Copyright (c) 2003-2007 Christopher Kulla +*/ + +class BIH +{ + private: + void init_empty() + { + tree.clear(); + objects.clear(); + // create space for the first node + tree.push_back(3u << 30u); // dummy leaf + tree.insert(tree.end(), 2, 0); + } + public: + BIH() { init_empty(); } + template< class BoundsFunc, class PrimArray > + void build(const PrimArray &primitives, BoundsFunc &getBounds, uint32 leafSize = 3, bool printStats=false) + { + if (primitives.size() == 0) + { + init_empty(); + return; + } + + buildData dat; + dat.maxPrims = leafSize; + dat.numPrims = primitives.size(); + dat.indices = new uint32[dat.numPrims]; + dat.primBound = new G3D::AABox[dat.numPrims]; + getBounds(primitives[0], bounds); + for (uint32 i=0; i<dat.numPrims; ++i) + { + dat.indices[i] = i; + getBounds(primitives[i], dat.primBound[i]); + bounds.merge(dat.primBound[i]); + } + std::vector<uint32> tempTree; + BuildStats stats; + buildHierarchy(tempTree, dat, stats); + if (printStats) + stats.printStats(); + + objects.resize(dat.numPrims); + for (uint32 i=0; i<dat.numPrims; ++i) + objects[i] = dat.indices[i]; + //nObjects = dat.numPrims; + tree = tempTree; + delete[] dat.primBound; + delete[] dat.indices; + } + uint32 primCount() const { return objects.size(); } + + template<typename RayCallback> + void intersectRay(const G3D::Ray &r, RayCallback& intersectCallback, float &maxDist, bool stopAtFirst=false) const + { + float intervalMin = -1.f; + float intervalMax = -1.f; + G3D::Vector3 org = r.origin(); + G3D::Vector3 dir = r.direction(); + G3D::Vector3 invDir; + for (int i=0; i<3; ++i) + { + invDir[i] = 1.f / dir[i]; + if (G3D::fuzzyNe(dir[i], 0.0f)) + { + float t1 = (bounds.low()[i] - org[i]) * invDir[i]; + float t2 = (bounds.high()[i] - org[i]) * invDir[i]; + if (t1 > t2) + std::swap(t1, t2); + if (t1 > intervalMin) + intervalMin = t1; + if (t2 < intervalMax || intervalMax < 0.f) + intervalMax = t2; + // intervalMax can only become smaller for other axis, + // and intervalMin only larger respectively, so stop early + if (intervalMax <= 0 || intervalMin >= maxDist) + return; + } + } + + if (intervalMin > intervalMax) + return; + intervalMin = std::max(intervalMin, 0.f); + intervalMax = std::min(intervalMax, maxDist); + + uint32 offsetFront[3]; + uint32 offsetBack[3]; + uint32 offsetFront3[3]; + uint32 offsetBack3[3]; + // compute custom offsets from direction sign bit + + for (int i=0; i<3; ++i) + { + offsetFront[i] = floatToRawIntBits(dir[i]) >> 31; + offsetBack[i] = offsetFront[i] ^ 1; + offsetFront3[i] = offsetFront[i] * 3; + offsetBack3[i] = offsetBack[i] * 3; + + // avoid always adding 1 during the inner loop + ++offsetFront[i]; + ++offsetBack[i]; + } + + StackNode stack[MAX_STACK_SIZE]; + int stackPos = 0; + int node = 0; + + while (true) { + while (true) + { + uint32 tn = tree[node]; + uint32 axis = (tn & (3 << 30)) >> 30; + bool BVH2 = (tn & (1 << 29)) != 0; + int offset = tn & ~(7 << 29); + if (!BVH2) + { + if (axis < 3) + { + // "normal" interior node + float tf = (intBitsToFloat(tree[node + offsetFront[axis]]) - org[axis]) * invDir[axis]; + float tb = (intBitsToFloat(tree[node + offsetBack[axis]]) - org[axis]) * invDir[axis]; + // ray passes between clip zones + if (tf < intervalMin && tb > intervalMax) + break; + int back = offset + offsetBack3[axis]; + node = back; + // ray passes through far node only + if (tf < intervalMin) { + intervalMin = (tb >= intervalMin) ? tb : intervalMin; + continue; + } + node = offset + offsetFront3[axis]; // front + // ray passes through near node only + if (tb > intervalMax) { + intervalMax = (tf <= intervalMax) ? tf : intervalMax; + continue; + } + // ray passes through both nodes + // push back node + stack[stackPos].node = back; + stack[stackPos].tnear = (tb >= intervalMin) ? tb : intervalMin; + stack[stackPos].tfar = intervalMax; + stackPos++; + // update ray interval for front node + intervalMax = (tf <= intervalMax) ? tf : intervalMax; + continue; + } + else + { + // leaf - test some objects + int n = tree[node + 1]; + while (n > 0) { + bool hit = intersectCallback(r, objects[offset], maxDist, stopAtFirst); + if (stopAtFirst && hit) return; + --n; + ++offset; + } + break; + } + } + else + { + if (axis>2) + return; // should not happen + float tf = (intBitsToFloat(tree[node + offsetFront[axis]]) - org[axis]) * invDir[axis]; + float tb = (intBitsToFloat(tree[node + offsetBack[axis]]) - org[axis]) * invDir[axis]; + node = offset; + intervalMin = (tf >= intervalMin) ? tf : intervalMin; + intervalMax = (tb <= intervalMax) ? tb : intervalMax; + if (intervalMin > intervalMax) + break; + continue; + } + } // traversal loop + do + { + // stack is empty? + if (stackPos == 0) + return; + // move back up the stack + stackPos--; + intervalMin = stack[stackPos].tnear; + if (maxDist < intervalMin) + continue; + node = stack[stackPos].node; + intervalMax = stack[stackPos].tfar; + break; + } while (true); + } + } + + template<typename IsectCallback> + void intersectPoint(const G3D::Vector3 &p, IsectCallback& intersectCallback) const + { + if (!bounds.contains(p)) + return; + + StackNode stack[MAX_STACK_SIZE]; + int stackPos = 0; + int node = 0; + + while (true) { + while (true) + { + uint32 tn = tree[node]; + uint32 axis = (tn & (3 << 30)) >> 30; + bool BVH2 = (tn & (1 << 29)) != 0; + int offset = tn & ~(7 << 29); + if (!BVH2) + { + if (axis < 3) + { + // "normal" interior node + float tl = intBitsToFloat(tree[node + 1]); + float tr = intBitsToFloat(tree[node + 2]); + // point is between clip zones + if (tl < p[axis] && tr > p[axis]) + break; + int right = offset + 3; + node = right; + // point is in right node only + if (tl < p[axis]) { + continue; + } + node = offset; // left + // point is in left node only + if (tr > p[axis]) { + continue; + } + // point is in both nodes + // push back right node + stack[stackPos].node = right; + stackPos++; + continue; + } + else + { + // leaf - test some objects + int n = tree[node + 1]; + while (n > 0) { + intersectCallback(p, objects[offset]); // !!! + --n; + ++offset; + } + break; + } + } + else // BVH2 node (empty space cut off left and right) + { + if (axis>2) + return; // should not happen + float tl = intBitsToFloat(tree[node + 1]); + float tr = intBitsToFloat(tree[node + 2]); + node = offset; + if (tl > p[axis] || tr < p[axis]) + break; + continue; + } + } // traversal loop + + // stack is empty? + if (stackPos == 0) + return; + // move back up the stack + stackPos--; + node = stack[stackPos].node; + } + } + + bool writeToFile(FILE* wf) const; + bool readFromFile(FILE* rf); + + protected: + std::vector<uint32> tree; + std::vector<uint32> objects; + G3D::AABox bounds; + + struct buildData + { + uint32 *indices; + G3D::AABox *primBound; + uint32 numPrims; + int maxPrims; + }; + struct StackNode + { + uint32 node; + float tnear; + float tfar; + }; + + class BuildStats + { + private: + int numNodes; + int numLeaves; + int sumObjects; + int minObjects; + int maxObjects; + int sumDepth; + int minDepth; + int maxDepth; + int numLeavesN[6]; + int numBVH2; + + public: + BuildStats(): + numNodes(0), numLeaves(0), sumObjects(0), minObjects(0x0FFFFFFF), + maxObjects(0xFFFFFFFF), sumDepth(0), minDepth(0x0FFFFFFF), + maxDepth(0xFFFFFFFF), numBVH2(0) + { + for (int i=0; i<6; ++i) numLeavesN[i] = 0; + } + + void updateInner() { numNodes++; } + void updateBVH2() { numBVH2++; } + void updateLeaf(int depth, int n); + void printStats(); + }; + + void buildHierarchy(std::vector<uint32> &tempTree, buildData &dat, BuildStats &stats); + + void createNode(std::vector<uint32> &tempTree, int nodeIndex, uint32 left, uint32 right) const + { + // write leaf node + tempTree[nodeIndex + 0] = (3 << 30) | left; + tempTree[nodeIndex + 1] = right - left + 1; + } + + void subdivide(int left, int right, std::vector<uint32> &tempTree, buildData &dat, AABound &gridBox, AABound &nodeBox, int nodeIndex, int depth, BuildStats &stats); +}; + +#endif // _BIH_H diff --git a/src/common/Collision/BoundingIntervalHierarchyWrapper.h b/src/common/Collision/BoundingIntervalHierarchyWrapper.h new file mode 100644 index 00000000000..60bb6a569df --- /dev/null +++ b/src/common/Collision/BoundingIntervalHierarchyWrapper.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _BIH_WRAP +#define _BIH_WRAP + +#include "G3D/Table.h" +#include "G3D/Array.h" +#include "G3D/Set.h" +#include "BoundingIntervalHierarchy.h" + + +template<class T, class BoundsFunc = BoundsTrait<T> > +class BIHWrap +{ + template<class RayCallback> + struct MDLCallback + { + const T* const* objects; + RayCallback& _callback; + uint32 objects_size; + + MDLCallback(RayCallback& callback, const T* const* objects_array, uint32 objects_size ) : objects(objects_array), _callback(callback), objects_size(objects_size) { } + + /// Intersect ray + bool operator() (const G3D::Ray& ray, uint32 idx, float& maxDist, bool /*stopAtFirst*/) + { + if (idx >= objects_size) + return false; + if (const T* obj = objects[idx]) + return _callback(ray, *obj, maxDist/*, stopAtFirst*/); + return false; + } + + /// Intersect point + void operator() (const G3D::Vector3& p, uint32 idx) + { + if (idx >= objects_size) + return; + if (const T* obj = objects[idx]) + _callback(p, *obj); + } + }; + + typedef G3D::Array<const T*> ObjArray; + + BIH m_tree; + ObjArray m_objects; + G3D::Table<const T*, uint32> m_obj2Idx; + G3D::Set<const T*> m_objects_to_push; + int unbalanced_times; + +public: + BIHWrap() : unbalanced_times(0) { } + + void insert(const T& obj) + { + ++unbalanced_times; + m_objects_to_push.insert(&obj); + } + + void remove(const T& obj) + { + ++unbalanced_times; + uint32 Idx = 0; + const T * temp; + if (m_obj2Idx.getRemove(&obj, temp, Idx)) + m_objects[Idx] = NULL; + else + m_objects_to_push.remove(&obj); + } + + void balance() + { + if (unbalanced_times == 0) + return; + + unbalanced_times = 0; + m_objects.fastClear(); + m_obj2Idx.getKeys(m_objects); + m_objects_to_push.getMembers(m_objects); + //assert that m_obj2Idx has all the keys + + m_tree.build(m_objects, BoundsFunc::getBounds2); + } + + template<typename RayCallback> + void intersectRay(const G3D::Ray& ray, RayCallback& intersectCallback, float& maxDist) + { + balance(); + MDLCallback<RayCallback> temp_cb(intersectCallback, m_objects.getCArray(), m_objects.size()); + m_tree.intersectRay(ray, temp_cb, maxDist, true); + } + + template<typename IsectCallback> + void intersectPoint(const G3D::Vector3& point, IsectCallback& intersectCallback) + { + balance(); + MDLCallback<IsectCallback> callback(intersectCallback, m_objects.getCArray(), m_objects.size()); + m_tree.intersectPoint(point, callback); + } +}; + +#endif // _BIH_WRAP diff --git a/src/common/Collision/DynamicTree.cpp b/src/common/Collision/DynamicTree.cpp new file mode 100644 index 00000000000..1de2543543d --- /dev/null +++ b/src/common/Collision/DynamicTree.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "DynamicTree.h" +//#include "QuadTree.h" +//#include "RegularGrid.h" +#include "BoundingIntervalHierarchyWrapper.h" + +#include "Log.h" +#include "RegularGrid.h" +#include "Timer.h" +#include "GameObjectModel.h" +#include "ModelInstance.h" + +#include <G3D/AABox.h> +#include <G3D/Ray.h> +#include <G3D/Vector3.h> + +using VMAP::ModelInstance; + +namespace { + +int CHECK_TREE_PERIOD = 200; + +} // namespace + +template<> struct HashTrait< GameObjectModel>{ + static size_t hashCode(const GameObjectModel& g) { return (size_t)(void*)&g; } +}; + +template<> struct PositionTrait< GameObjectModel> { + static void getPosition(const GameObjectModel& g, G3D::Vector3& p) { p = g.getPosition(); } +}; + +template<> struct BoundsTrait< GameObjectModel> { + static void getBounds(const GameObjectModel& g, G3D::AABox& out) { out = g.getBounds();} + static void getBounds2(const GameObjectModel* g, G3D::AABox& out) { out = g->getBounds();} +}; + +/* +static bool operator == (const GameObjectModel& mdl, const GameObjectModel& mdl2){ + return &mdl == &mdl2; +} +*/ + +typedef RegularGrid2D<GameObjectModel, BIHWrap<GameObjectModel> > ParentTree; + +struct DynTreeImpl : public ParentTree/*, public Intersectable*/ +{ + typedef GameObjectModel Model; + typedef ParentTree base; + + DynTreeImpl() : + rebalance_timer(CHECK_TREE_PERIOD), + unbalanced_times(0) + { + } + + void insert(const Model& mdl) + { + base::insert(mdl); + ++unbalanced_times; + } + + void remove(const Model& mdl) + { + base::remove(mdl); + ++unbalanced_times; + } + + void balance() + { + base::balance(); + unbalanced_times = 0; + } + + void update(uint32 difftime) + { + if (!size()) + return; + + rebalance_timer.Update(difftime); + if (rebalance_timer.Passed()) + { + rebalance_timer.Reset(CHECK_TREE_PERIOD); + if (unbalanced_times > 0) + balance(); + } + } + + TimeTrackerSmall rebalance_timer; + int unbalanced_times; +}; + +DynamicMapTree::DynamicMapTree() : impl(new DynTreeImpl()) { } + +DynamicMapTree::~DynamicMapTree() +{ + delete impl; +} + +void DynamicMapTree::insert(const GameObjectModel& mdl) +{ + impl->insert(mdl); +} + +void DynamicMapTree::remove(const GameObjectModel& mdl) +{ + impl->remove(mdl); +} + +bool DynamicMapTree::contains(const GameObjectModel& mdl) const +{ + return impl->contains(mdl); +} + +void DynamicMapTree::balance() +{ + impl->balance(); +} + +int DynamicMapTree::size() const +{ + return impl->size(); +} + +void DynamicMapTree::update(uint32 t_diff) +{ + impl->update(t_diff); +} + +struct DynamicTreeIntersectionCallback +{ + bool did_hit; + uint32 phase_mask; + DynamicTreeIntersectionCallback(uint32 phasemask) : did_hit(false), phase_mask(phasemask) { } + bool operator()(const G3D::Ray& r, const GameObjectModel& obj, float& distance) + { + did_hit = obj.intersectRay(r, distance, true, phase_mask); + return did_hit; + } + bool didHit() const { return did_hit;} +}; + +struct DynamicTreeIntersectionCallback_WithLogger +{ + bool did_hit; + uint32 phase_mask; + DynamicTreeIntersectionCallback_WithLogger(uint32 phasemask) : did_hit(false), phase_mask(phasemask) + { + TC_LOG_DEBUG("maps", "Dynamic Intersection log"); + } + bool operator()(const G3D::Ray& r, const GameObjectModel& obj, float& distance) + { + TC_LOG_DEBUG("maps", "testing intersection with %s", obj.name.c_str()); + bool hit = obj.intersectRay(r, distance, true, phase_mask); + if (hit) + { + did_hit = true; + TC_LOG_DEBUG("maps", "result: intersects"); + } + return hit; + } + bool didHit() const { return did_hit;} +}; + +bool DynamicMapTree::getIntersectionTime(const uint32 phasemask, const G3D::Ray& ray, + const G3D::Vector3& endPos, float& maxDist) const +{ + float distance = maxDist; + DynamicTreeIntersectionCallback callback(phasemask); + impl->intersectRay(ray, callback, distance, endPos); + if (callback.didHit()) + maxDist = distance; + return callback.didHit(); +} + +bool DynamicMapTree::getObjectHitPos(const uint32 phasemask, const G3D::Vector3& startPos, + const G3D::Vector3& endPos, G3D::Vector3& resultHit, + float modifyDist) const +{ + bool result = false; + float maxDist = (endPos - startPos).magnitude(); + // valid map coords should *never ever* produce float overflow, but this would produce NaNs too + ASSERT(maxDist < std::numeric_limits<float>::max()); + // prevent NaN values which can cause BIH intersection to enter infinite loop + if (maxDist < 1e-10f) + { + resultHit = endPos; + return false; + } + G3D::Vector3 dir = (endPos - startPos)/maxDist; // direction with length of 1 + G3D::Ray ray(startPos, dir); + float dist = maxDist; + if (getIntersectionTime(phasemask, ray, endPos, dist)) + { + resultHit = startPos + dir * dist; + if (modifyDist < 0) + { + if ((resultHit - startPos).magnitude() > -modifyDist) + resultHit = resultHit + dir*modifyDist; + else + resultHit = startPos; + } + else + resultHit = resultHit + dir*modifyDist; + + result = true; + } + else + { + resultHit = endPos; + result = false; + } + return result; +} + +bool DynamicMapTree::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask) const +{ + G3D::Vector3 v1(x1, y1, z1), v2(x2, y2, z2); + + float maxDist = (v2 - v1).magnitude(); + + if (!G3D::fuzzyGt(maxDist, 0) ) + return true; + + G3D::Ray r(v1, (v2-v1) / maxDist); + DynamicTreeIntersectionCallback callback(phasemask); + impl->intersectRay(r, callback, maxDist, v2); + + return !callback.did_hit; +} + +float DynamicMapTree::getHeight(float x, float y, float z, float maxSearchDist, uint32 phasemask) const +{ + G3D::Vector3 v(x, y, z); + G3D::Ray r(v, G3D::Vector3(0, 0, -1)); + DynamicTreeIntersectionCallback callback(phasemask); + impl->intersectZAllignedRay(r, callback, maxSearchDist); + + if (callback.didHit()) + return v.z - maxSearchDist; + else + return -G3D::finf(); +} diff --git a/src/common/Collision/DynamicTree.h b/src/common/Collision/DynamicTree.h new file mode 100644 index 00000000000..5e905323640 --- /dev/null +++ b/src/common/Collision/DynamicTree.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#ifndef _DYNTREE_H +#define _DYNTREE_H + +#include "Define.h" + +namespace G3D +{ + class Ray; + class Vector3; +} + +class GameObjectModel; +struct DynTreeImpl; + +class DynamicMapTree +{ + DynTreeImpl *impl; + +public: + + DynamicMapTree(); + ~DynamicMapTree(); + + bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2, + float z2, uint32 phasemask) const; + + bool getIntersectionTime(uint32 phasemask, const G3D::Ray& ray, + const G3D::Vector3& endPos, float& maxDist) const; + + bool getObjectHitPos(uint32 phasemask, const G3D::Vector3& pPos1, + const G3D::Vector3& pPos2, G3D::Vector3& pResultHitPos, + float pModifyDist) const; + + float getHeight(float x, float y, float z, float maxSearchDist, uint32 phasemask) const; + + void insert(const GameObjectModel&); + void remove(const GameObjectModel&); + bool contains(const GameObjectModel&) const; + int size() const; + + void balance(); + void update(uint32 diff); +}; + +#endif // _DYNTREE_H diff --git a/src/common/Collision/Management/IVMapManager.h b/src/common/Collision/Management/IVMapManager.h new file mode 100644 index 00000000000..b890554257c --- /dev/null +++ b/src/common/Collision/Management/IVMapManager.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _IVMAPMANAGER_H +#define _IVMAPMANAGER_H + +#include <string> +#include "Define.h" + +//=========================================================== + +/** +This is the minimum interface to the VMapMamager. +*/ + +namespace VMAP +{ + + enum VMAP_LOAD_RESULT + { + VMAP_LOAD_RESULT_ERROR, + VMAP_LOAD_RESULT_OK, + VMAP_LOAD_RESULT_IGNORED + }; + + #define VMAP_INVALID_HEIGHT -100000.0f // for check + #define VMAP_INVALID_HEIGHT_VALUE -200000.0f // real assigned value in unknown height case + + //=========================================================== + class IVMapManager + { + private: + bool iEnableLineOfSightCalc; + bool iEnableHeightCalc; + + public: + IVMapManager() : iEnableLineOfSightCalc(true), iEnableHeightCalc(true) { } + + virtual ~IVMapManager(void) { } + + virtual int loadMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 0; + + virtual bool existsMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 0; + + virtual void unloadMap(unsigned int pMapId, int x, int y) = 0; + virtual void unloadMap(unsigned int pMapId) = 0; + + virtual bool isInLineOfSight(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2) = 0; + virtual float getHeight(unsigned int pMapId, float x, float y, float z, float maxSearchDist) = 0; + /** + test if we hit an object. return true if we hit one. rx, ry, rz will hold the hit position or the dest position, if no intersection was found + return a position, that is pReduceDist closer to the origin + */ + virtual bool getObjectHitPos(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float &ry, float& rz, float pModifyDist) = 0; + /** + send debug commands + */ + virtual bool processCommand(char *pCommand)= 0; + + /** + Enable/disable LOS calculation + It is enabled by default. If it is enabled in mid game the maps have to loaded manualy + */ + void setEnableLineOfSightCalc(bool pVal) { iEnableLineOfSightCalc = pVal; } + /** + Enable/disable model height calculation + It is enabled by default. If it is enabled in mid game the maps have to loaded manualy + */ + void setEnableHeightCalc(bool pVal) { iEnableHeightCalc = pVal; } + + bool isLineOfSightCalcEnabled() const { return(iEnableLineOfSightCalc); } + bool isHeightCalcEnabled() const { return(iEnableHeightCalc); } + bool isMapLoadingEnabled() const { return(iEnableLineOfSightCalc || iEnableHeightCalc ); } + + virtual std::string getDirFileName(unsigned int pMapId, int x, int y) const =0; + /** + Query world model area info. + \param z gets adjusted to the ground height for which this are info is valid + */ + virtual bool getAreaInfo(unsigned int pMapId, float x, float y, float &z, uint32 &flags, int32 &adtId, int32 &rootId, int32 &groupId) const=0; + virtual bool GetLiquidLevel(uint32 pMapId, float x, float y, float z, uint8 ReqLiquidType, float &level, float &floor, uint32 &type) const=0; + }; + +} +#endif diff --git a/src/common/Collision/Management/MMapFactory.cpp b/src/common/Collision/Management/MMapFactory.cpp new file mode 100644 index 00000000000..667b8378c56 --- /dev/null +++ b/src/common/Collision/Management/MMapFactory.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "MMapFactory.h" +#include "Config.h" + +namespace MMAP +{ + // ######################## MMapFactory ######################## + // our global singleton copy + MMapManager* g_MMapManager = NULL; + + MMapManager* MMapFactory::createOrGetMMapManager() + { + if (g_MMapManager == NULL) + g_MMapManager = new MMapManager(); + + return g_MMapManager; + } + + void MMapFactory::clear() + { + if (g_MMapManager) + { + delete g_MMapManager; + g_MMapManager = NULL; + } + } +}
\ No newline at end of file diff --git a/src/common/Collision/Management/MMapFactory.h b/src/common/Collision/Management/MMapFactory.h new file mode 100644 index 00000000000..773983f81eb --- /dev/null +++ b/src/common/Collision/Management/MMapFactory.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _MMAP_FACTORY_H +#define _MMAP_FACTORY_H + +#include "Define.h" +#include "MMapManager.h" +#include "DetourAlloc.h" +#include "DetourNavMesh.h" +#include "DetourNavMeshQuery.h" +#include <unordered_map> + +namespace MMAP +{ + enum MMAP_LOAD_RESULT + { + MMAP_LOAD_RESULT_ERROR, + MMAP_LOAD_RESULT_OK, + MMAP_LOAD_RESULT_IGNORED + }; + + // static class + // holds all mmap global data + // access point to MMapManager singleton + class MMapFactory + { + public: + static MMapManager* createOrGetMMapManager(); + static void clear(); + static bool IsPathfindingEnabled(uint32 mapId); + }; +} + +#endif + diff --git a/src/common/Collision/Management/MMapManager.cpp b/src/common/Collision/Management/MMapManager.cpp new file mode 100644 index 00000000000..00985e4fa1d --- /dev/null +++ b/src/common/Collision/Management/MMapManager.cpp @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "MMapManager.h" +#include "Log.h" +#include "Config.h" +#include "MapDefines.h" + +namespace MMAP +{ + // ######################## MMapManager ######################## + MMapManager::~MMapManager() + { + for (MMapDataSet::iterator i = loadedMMaps.begin(); i != loadedMMaps.end(); ++i) + delete i->second; + + // by now we should not have maps loaded + // if we had, tiles in MMapData->mmapLoadedTiles, their actual data is lost! + } + + void MMapManager::InitializeThreadUnsafe(const std::vector<uint32>& mapIds) + { + // the caller must pass the list of all mapIds that will be used in the VMapManager2 lifetime + for (const uint32& mapId : mapIds) + loadedMMaps.insert(MMapDataSet::value_type(mapId, nullptr)); + + thread_safe_environment = false; + } + + MMapDataSet::const_iterator MMapManager::GetMMapData(uint32 mapId) const + { + // return the iterator if found or end() if not found/NULL + MMapDataSet::const_iterator itr = loadedMMaps.find(mapId); + if (itr != loadedMMaps.cend() && !itr->second) + itr = loadedMMaps.cend(); + + return itr; + } + + bool MMapManager::loadMapData(uint32 mapId) + { + // we already have this map loaded? + MMapDataSet::iterator itr = loadedMMaps.find(mapId); + if (itr != loadedMMaps.end()) + { + if (itr->second) + return true; + } + else + { + if (thread_safe_environment) + itr = loadedMMaps.insert(MMapDataSet::value_type(mapId, nullptr)).first; + else + ASSERT(false, "Invalid mapId %u passed to MMapManager after startup in thread unsafe environment", mapId); + } + + // load and init dtNavMesh - read parameters from file + std::string dataDir = sConfigMgr->GetStringDefault("DataDir", "./"); + uint32 pathLen = dataDir.length() + strlen("/mmaps/%03i.mmap") + 1; + char *fileName = new char[pathLen]; + snprintf(fileName, pathLen, (dataDir + "/mmaps/%03i.mmap").c_str(), mapId); + + FILE* file = fopen(fileName, "rb"); + if (!file) + { + TC_LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not open mmap file '%s'", fileName); + delete [] fileName; + return false; + } + + dtNavMeshParams params; + int count = fread(¶ms, sizeof(dtNavMeshParams), 1, file); + fclose(file); + if (count != 1) + { + TC_LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not read params from file '%s'", fileName); + delete [] fileName; + return false; + } + + dtNavMesh* mesh = dtAllocNavMesh(); + ASSERT(mesh); + if (dtStatusFailed(mesh->init(¶ms))) + { + dtFreeNavMesh(mesh); + TC_LOG_ERROR("maps", "MMAP:loadMapData: Failed to initialize dtNavMesh for mmap %03u from file %s", mapId, fileName); + delete [] fileName; + return false; + } + + delete [] fileName; + + TC_LOG_DEBUG("maps", "MMAP:loadMapData: Loaded %03i.mmap", mapId); + + // store inside our map list + MMapData* mmap_data = new MMapData(mesh); + mmap_data->mmapLoadedTiles.clear(); + + itr->second = mmap_data; + return true; + } + + uint32 MMapManager::packTileID(int32 x, int32 y) + { + return uint32(x << 16 | y); + } + + bool MMapManager::loadMap(const std::string& basePath, uint32 mapId, int32 x, int32 y) + { + // make sure the mmap is loaded and ready to load tiles + if (!loadMapData(mapId)) + return false; + + // get this mmap data + MMapData* mmap = loadedMMaps[mapId]; + ASSERT(mmap->navMesh); + + // check if we already have this tile loaded + uint32 packedGridPos = packTileID(x, y); + if (mmap->mmapLoadedTiles.find(packedGridPos) != mmap->mmapLoadedTiles.end()) + return false; + + // load this tile :: mmaps/MMMXXYY.mmtile + uint32 pathLen = basePath.length() + strlen("/%03i%02i%02i.mmtile") + 1; + char *fileName = new char[pathLen]; + + snprintf(fileName, pathLen, (basePath + "/%03i%02i%02i.mmtile").c_str(), mapId, x, y); + + FILE* file = fopen(fileName, "rb"); + if (!file) + { + TC_LOG_DEBUG("maps", "MMAP:loadMap: Could not open mmtile file '%s'", fileName); + delete [] fileName; + return false; + } + delete [] fileName; + + // read header + MmapTileHeader fileHeader; + if (fread(&fileHeader, sizeof(MmapTileHeader), 1, file) != 1 || fileHeader.mmapMagic != MMAP_MAGIC) + { + TC_LOG_ERROR("maps", "MMAP:loadMap: Bad header in mmap %03u%02i%02i.mmtile", mapId, x, y); + fclose(file); + return false; + } + + if (fileHeader.mmapVersion != MMAP_VERSION) + { + TC_LOG_ERROR("maps", "MMAP:loadMap: %03u%02i%02i.mmtile was built with generator v%i, expected v%i", + mapId, x, y, fileHeader.mmapVersion, MMAP_VERSION); + fclose(file); + return false; + } + + unsigned char* data = (unsigned char*)dtAlloc(fileHeader.size, DT_ALLOC_PERM); + ASSERT(data); + + size_t result = fread(data, fileHeader.size, 1, file); + if (!result) + { + TC_LOG_ERROR("maps", "MMAP:loadMap: Bad header or data in mmap %03u%02i%02i.mmtile", mapId, x, y); + fclose(file); + return false; + } + + fclose(file); + + dtMeshHeader* header = (dtMeshHeader*)data; + dtTileRef tileRef = 0; + + // memory allocated for data is now managed by detour, and will be deallocated when the tile is removed + if (dtStatusSucceed(mmap->navMesh->addTile(data, fileHeader.size, DT_TILE_FREE_DATA, 0, &tileRef))) + { + mmap->mmapLoadedTiles.insert(std::pair<uint32, dtTileRef>(packedGridPos, tileRef)); + ++loadedTiles; + TC_LOG_DEBUG("maps", "MMAP:loadMap: Loaded mmtile %03i[%02i, %02i] into %03i[%02i, %02i]", mapId, x, y, mapId, header->x, header->y); + return true; + } + else + { + TC_LOG_ERROR("maps", "MMAP:loadMap: Could not load %03u%02i%02i.mmtile into navmesh", mapId, x, y); + dtFree(data); + return false; + } + } + + bool MMapManager::unloadMap(uint32 mapId, int32 x, int32 y) + { + // check if we have this map loaded + MMapDataSet::const_iterator itr = GetMMapData(mapId); + if (itr == loadedMMaps.end()) + { + // file may not exist, therefore not loaded + TC_LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh map. %03u%02i%02i.mmtile", mapId, x, y); + return false; + } + + MMapData* mmap = itr->second; + + // check if we have this tile loaded + uint32 packedGridPos = packTileID(x, y); + if (mmap->mmapLoadedTiles.find(packedGridPos) == mmap->mmapLoadedTiles.end()) + { + // file may not exist, therefore not loaded + TC_LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh tile. %03u%02i%02i.mmtile", mapId, x, y); + return false; + } + + dtTileRef tileRef = mmap->mmapLoadedTiles[packedGridPos]; + + // unload, and mark as non loaded + if (dtStatusFailed(mmap->navMesh->removeTile(tileRef, NULL, NULL))) + { + // this is technically a memory leak + // if the grid is later reloaded, dtNavMesh::addTile will return error but no extra memory is used + // we cannot recover from this error - assert out + TC_LOG_ERROR("maps", "MMAP:unloadMap: Could not unload %03u%02i%02i.mmtile from navmesh", mapId, x, y); + ABORT(); + } + else + { + mmap->mmapLoadedTiles.erase(packedGridPos); + --loadedTiles; + TC_LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile %03i[%02i, %02i] from %03i", mapId, x, y, mapId); + return true; + } + + return false; + } + + bool MMapManager::unloadMap(uint32 mapId) + { + MMapDataSet::iterator itr = loadedMMaps.find(mapId); + if (itr == loadedMMaps.end() || !itr->second) + { + // file may not exist, therefore not loaded + TC_LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh map %03u", mapId); + return false; + } + + // unload all tiles from given map + MMapData* mmap = itr->second; + for (MMapTileSet::iterator i = mmap->mmapLoadedTiles.begin(); i != mmap->mmapLoadedTiles.end(); ++i) + { + uint32 x = (i->first >> 16); + uint32 y = (i->first & 0x0000FFFF); + if (dtStatusFailed(mmap->navMesh->removeTile(i->second, NULL, NULL))) + TC_LOG_ERROR("maps", "MMAP:unloadMap: Could not unload %03u%02i%02i.mmtile from navmesh", mapId, x, y); + else + { + --loadedTiles; + TC_LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile %03i[%02i, %02i] from %03i", mapId, x, y, mapId); + } + } + + delete mmap; + itr->second = nullptr; + TC_LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded %03i.mmap", mapId); + + return true; + } + + bool MMapManager::unloadMapInstance(uint32 mapId, uint32 instanceId) + { + // check if we have this map loaded + MMapDataSet::const_iterator itr = GetMMapData(mapId); + if (itr == loadedMMaps.end()) + { + // file may not exist, therefore not loaded + TC_LOG_DEBUG("maps", "MMAP:unloadMapInstance: Asked to unload not loaded navmesh map %03u", mapId); + return false; + } + + MMapData* mmap = itr->second; + if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end()) + { + TC_LOG_DEBUG("maps", "MMAP:unloadMapInstance: Asked to unload not loaded dtNavMeshQuery mapId %03u instanceId %u", mapId, instanceId); + return false; + } + + dtNavMeshQuery* query = mmap->navMeshQueries[instanceId]; + + dtFreeNavMeshQuery(query); + mmap->navMeshQueries.erase(instanceId); + TC_LOG_DEBUG("maps", "MMAP:unloadMapInstance: Unloaded mapId %03u instanceId %u", mapId, instanceId); + + return true; + } + + dtNavMesh const* MMapManager::GetNavMesh(uint32 mapId) + { + MMapDataSet::const_iterator itr = GetMMapData(mapId); + if (itr == loadedMMaps.end()) + return NULL; + + return itr->second->navMesh; + } + + dtNavMeshQuery const* MMapManager::GetNavMeshQuery(uint32 mapId, uint32 instanceId) + { + MMapDataSet::const_iterator itr = GetMMapData(mapId); + if (itr == loadedMMaps.end()) + return NULL; + + MMapData* mmap = itr->second; + if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end()) + { + // allocate mesh query + dtNavMeshQuery* query = dtAllocNavMeshQuery(); + ASSERT(query); + if (dtStatusFailed(query->init(mmap->navMesh, 1024))) + { + dtFreeNavMeshQuery(query); + TC_LOG_ERROR("maps", "MMAP:GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId %03u instanceId %u", mapId, instanceId); + return NULL; + } + + TC_LOG_DEBUG("maps", "MMAP:GetNavMeshQuery: created dtNavMeshQuery for mapId %03u instanceId %u", mapId, instanceId); + mmap->navMeshQueries.insert(std::pair<uint32, dtNavMeshQuery*>(instanceId, query)); + } + + return mmap->navMeshQueries[instanceId]; + } +} diff --git a/src/common/Collision/Management/MMapManager.h b/src/common/Collision/Management/MMapManager.h new file mode 100644 index 00000000000..42292b76942 --- /dev/null +++ b/src/common/Collision/Management/MMapManager.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _MMAP_MANAGER_H +#define _MMAP_MANAGER_H + +#include "Define.h" +#include "DetourNavMesh.h" +#include "DetourNavMeshQuery.h" + +#include <string> +#include <unordered_map> +#include <vector> + +// move map related classes +namespace MMAP +{ + typedef std::unordered_map<uint32, dtTileRef> MMapTileSet; + typedef std::unordered_map<uint32, dtNavMeshQuery*> NavMeshQuerySet; + + // dummy struct to hold map's mmap data + struct MMapData + { + MMapData(dtNavMesh* mesh) : navMesh(mesh) { } + ~MMapData() + { + for (NavMeshQuerySet::iterator i = navMeshQueries.begin(); i != navMeshQueries.end(); ++i) + dtFreeNavMeshQuery(i->second); + + if (navMesh) + dtFreeNavMesh(navMesh); + } + + dtNavMesh* navMesh; + + // we have to use single dtNavMeshQuery for every instance, since those are not thread safe + NavMeshQuerySet navMeshQueries; // instanceId to query + MMapTileSet mmapLoadedTiles; // maps [map grid coords] to [dtTile] + }; + + + typedef std::unordered_map<uint32, MMapData*> MMapDataSet; + + // singleton class + // holds all all access to mmap loading unloading and meshes + class MMapManager + { + public: + MMapManager() : loadedTiles(0), thread_safe_environment(true) {} + ~MMapManager(); + + void InitializeThreadUnsafe(const std::vector<uint32>& mapIds); + bool loadMap(const std::string& basePath, uint32 mapId, int32 x, int32 y); + bool unloadMap(uint32 mapId, int32 x, int32 y); + bool unloadMap(uint32 mapId); + bool unloadMapInstance(uint32 mapId, uint32 instanceId); + + // the returned [dtNavMeshQuery const*] is NOT threadsafe + dtNavMeshQuery const* GetNavMeshQuery(uint32 mapId, uint32 instanceId); + dtNavMesh const* GetNavMesh(uint32 mapId); + + uint32 getLoadedTilesCount() const { return loadedTiles; } + uint32 getLoadedMapsCount() const { return loadedMMaps.size(); } + private: + bool loadMapData(uint32 mapId); + uint32 packTileID(int32 x, int32 y); + + MMapDataSet::const_iterator GetMMapData(uint32 mapId) const; + MMapDataSet loadedMMaps; + uint32 loadedTiles; + bool thread_safe_environment; + }; +} + +#endif
\ No newline at end of file diff --git a/src/common/Collision/Management/VMapFactory.cpp b/src/common/Collision/Management/VMapFactory.cpp new file mode 100644 index 00000000000..4c2750a9e5c --- /dev/null +++ b/src/common/Collision/Management/VMapFactory.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "VMapFactory.h" +#include "VMapManager2.h" + +namespace VMAP +{ + IVMapManager* gVMapManager = NULL; + + //=============================================== + // just return the instance + IVMapManager* VMapFactory::createOrGetVMapManager() + { + if (gVMapManager == nullptr) + gVMapManager= new VMapManager2(); // should be taken from config ... Please change if you like :-) + return gVMapManager; + } + + //=============================================== + // delete all internal data structures + void VMapFactory::clear() + { + delete gVMapManager; + gVMapManager = NULL; + } +} diff --git a/src/common/Collision/Management/VMapFactory.h b/src/common/Collision/Management/VMapFactory.h new file mode 100644 index 00000000000..3067c2919d5 --- /dev/null +++ b/src/common/Collision/Management/VMapFactory.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _VMAPFACTORY_H +#define _VMAPFACTORY_H + +#include "IVMapManager.h" + +/** +This is the access point to the VMapManager. +*/ + +namespace VMAP +{ + //=========================================================== + + class VMapFactory + { + public: + static IVMapManager* createOrGetVMapManager(); + static void clear(); + }; + +} +#endif diff --git a/src/common/Collision/Management/VMapManager2.cpp b/src/common/Collision/Management/VMapManager2.cpp new file mode 100644 index 00000000000..a63eac1b935 --- /dev/null +++ b/src/common/Collision/Management/VMapManager2.cpp @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include <iomanip> +#include <string> +#include <sstream> +#include "VMapManager2.h" +#include "MapTree.h" +#include "ModelInstance.h" +#include "WorldModel.h" +#include <G3D/Vector3.h> +#include "Log.h" +#include "VMapDefinitions.h" +#include "Errors.h" + +using G3D::Vector3; + +namespace VMAP +{ + VMapManager2::VMapManager2() + { + GetLiquidFlagsPtr = &GetLiquidFlagsDummy; + IsVMAPDisabledForPtr = &IsVMAPDisabledForDummy; + thread_safe_environment = true; + } + + VMapManager2::~VMapManager2(void) + { + for (InstanceTreeMap::iterator i = iInstanceMapTrees.begin(); i != iInstanceMapTrees.end(); ++i) + { + delete i->second; + } + for (ModelFileMap::iterator i = iLoadedModelFiles.begin(); i != iLoadedModelFiles.end(); ++i) + { + delete i->second.getModel(); + } + } + + void VMapManager2::InitializeThreadUnsafe(const std::vector<uint32>& mapIds) + { + // the caller must pass the list of all mapIds that will be used in the VMapManager2 lifetime + for (const uint32& mapId : mapIds) + iInstanceMapTrees.insert(InstanceTreeMap::value_type(mapId, nullptr)); + + thread_safe_environment = false; + } + + Vector3 VMapManager2::convertPositionToInternalRep(float x, float y, float z) const + { + Vector3 pos; + const float mid = 0.5f * 64.0f * 533.33333333f; + pos.x = mid - x; + pos.y = mid - y; + pos.z = z; + + return pos; + } + + // move to MapTree too? + std::string VMapManager2::getMapFileName(unsigned int mapId) + { + std::stringstream fname; + fname.width(3); + fname << std::setfill('0') << mapId << std::string(MAP_FILENAME_EXTENSION2); + + return fname.str(); + } + + int VMapManager2::loadMap(const char* basePath, unsigned int mapId, int x, int y) + { + int result = VMAP_LOAD_RESULT_IGNORED; + if (isMapLoadingEnabled()) + { + if (_loadMap(mapId, basePath, x, y)) + result = VMAP_LOAD_RESULT_OK; + else + result = VMAP_LOAD_RESULT_ERROR; + } + + return result; + } + + InstanceTreeMap::const_iterator VMapManager2::GetMapTree(uint32 mapId) const + { + // return the iterator if found or end() if not found/NULL + InstanceTreeMap::const_iterator itr = iInstanceMapTrees.find(mapId); + if (itr != iInstanceMapTrees.cend() && !itr->second) + itr = iInstanceMapTrees.cend(); + + return itr; + } + + // load one tile (internal use only) + bool VMapManager2::_loadMap(uint32 mapId, const std::string& basePath, uint32 tileX, uint32 tileY) + { + InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId); + if (instanceTree == iInstanceMapTrees.end()) + { + if (thread_safe_environment) + instanceTree = iInstanceMapTrees.insert(InstanceTreeMap::value_type(mapId, nullptr)).first; + else + ASSERT(false, "Invalid mapId %u tile [%u, %u] passed to VMapManager2 after startup in thread unsafe environment", + mapId, tileX, tileY); + } + + if (!instanceTree->second) + { + std::string mapFileName = getMapFileName(mapId); + StaticMapTree* newTree = new StaticMapTree(mapId, basePath); + if (!newTree->InitMap(mapFileName, this)) + { + delete newTree; + return false; + } + instanceTree->second = newTree; + } + + return instanceTree->second->LoadMapTile(tileX, tileY, this); + } + + void VMapManager2::unloadMap(unsigned int mapId) + { + InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId); + if (instanceTree != iInstanceMapTrees.end() && instanceTree->second) + { + instanceTree->second->UnloadMap(this); + if (instanceTree->second->numLoadedTiles() == 0) + { + delete instanceTree->second; + instanceTree->second = nullptr; + } + } + } + + void VMapManager2::unloadMap(unsigned int mapId, int x, int y) + { + InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId); + if (instanceTree != iInstanceMapTrees.end() && instanceTree->second) + { + instanceTree->second->UnloadMapTile(x, y, this); + if (instanceTree->second->numLoadedTiles() == 0) + { + delete instanceTree->second; + instanceTree->second = nullptr; + } + } + } + + bool VMapManager2::isInLineOfSight(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2) + { + if (!isLineOfSightCalcEnabled() || IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LOS)) + return true; + + InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId); + if (instanceTree != iInstanceMapTrees.end()) + { + Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1); + Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2); + if (pos1 != pos2) + { + return instanceTree->second->isInLineOfSight(pos1, pos2); + } + } + + return true; + } + + /** + get the hit position and return true if we hit something + otherwise the result pos will be the dest pos + */ + bool VMapManager2::getObjectHitPos(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float &ry, float& rz, float modifyDist) + { + if (isLineOfSightCalcEnabled() && !IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LOS)) + { + InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId); + if (instanceTree != iInstanceMapTrees.end()) + { + Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1); + Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2); + Vector3 resultPos; + bool result = instanceTree->second->getObjectHitPos(pos1, pos2, resultPos, modifyDist); + resultPos = convertPositionToInternalRep(resultPos.x, resultPos.y, resultPos.z); + rx = resultPos.x; + ry = resultPos.y; + rz = resultPos.z; + return result; + } + } + + rx = x2; + ry = y2; + rz = z2; + + return false; + } + + /** + get height or INVALID_HEIGHT if no height available + */ + + float VMapManager2::getHeight(unsigned int mapId, float x, float y, float z, float maxSearchDist) + { + if (isHeightCalcEnabled() && !IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_HEIGHT)) + { + InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId); + if (instanceTree != iInstanceMapTrees.end()) + { + Vector3 pos = convertPositionToInternalRep(x, y, z); + float height = instanceTree->second->getHeight(pos, maxSearchDist); + if (!(height < G3D::finf())) + return height = VMAP_INVALID_HEIGHT_VALUE; // No height + + return height; + } + } + + return VMAP_INVALID_HEIGHT_VALUE; + } + + bool VMapManager2::getAreaInfo(unsigned int mapId, float x, float y, float& z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const + { + if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_AREAFLAG)) + { + InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId); + if (instanceTree != iInstanceMapTrees.end()) + { + Vector3 pos = convertPositionToInternalRep(x, y, z); + bool result = instanceTree->second->getAreaInfo(pos, flags, adtId, rootId, groupId); + // z is not touched by convertPositionToInternalRep(), so just copy + z = pos.z; + return result; + } + } + + return false; + } + + bool VMapManager2::GetLiquidLevel(uint32 mapId, float x, float y, float z, uint8 reqLiquidType, float& level, float& floor, uint32& type) const + { + if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LIQUIDSTATUS)) + { + InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId); + if (instanceTree != iInstanceMapTrees.end()) + { + LocationInfo info; + Vector3 pos = convertPositionToInternalRep(x, y, z); + if (instanceTree->second->GetLocationInfo(pos, info)) + { + floor = info.ground_Z; + ASSERT(floor < std::numeric_limits<float>::max()); + ASSERT(info.hitModel); + type = info.hitModel->GetLiquidType(); // entry from LiquidType.dbc + if (reqLiquidType && !(GetLiquidFlagsPtr(type) & reqLiquidType)) + return false; + ASSERT(info.hitInstance); + if (info.hitInstance->GetLiquidLevel(pos, info, level)) + return true; + } + } + } + + return false; + } + + WorldModel* VMapManager2::acquireModelInstance(const std::string& basepath, const std::string& filename) + { + //! Critical section, thread safe access to iLoadedModelFiles + std::lock_guard<std::mutex> lock(LoadedModelFilesLock); + + ModelFileMap::iterator model = iLoadedModelFiles.find(filename); + if (model == iLoadedModelFiles.end()) + { + WorldModel* worldmodel = new WorldModel(); + if (!worldmodel->readFile(basepath + filename + ".vmo")) + { + VMAP_ERROR_LOG("misc", "VMapManager2: could not load '%s%s.vmo'", basepath.c_str(), filename.c_str()); + delete worldmodel; + return NULL; + } + VMAP_DEBUG_LOG("maps", "VMapManager2: loading file '%s%s'", basepath.c_str(), filename.c_str()); + model = iLoadedModelFiles.insert(std::pair<std::string, ManagedModel>(filename, ManagedModel())).first; + model->second.setModel(worldmodel); + } + model->second.incRefCount(); + return model->second.getModel(); + } + + void VMapManager2::releaseModelInstance(const std::string &filename) + { + //! Critical section, thread safe access to iLoadedModelFiles + std::lock_guard<std::mutex> lock(LoadedModelFilesLock); + + ModelFileMap::iterator model = iLoadedModelFiles.find(filename); + if (model == iLoadedModelFiles.end()) + { + VMAP_ERROR_LOG("misc", "VMapManager2: trying to unload non-loaded file '%s'", filename.c_str()); + return; + } + if (model->second.decRefCount() == 0) + { + VMAP_DEBUG_LOG("maps", "VMapManager2: unloading file '%s'", filename.c_str()); + delete model->second.getModel(); + iLoadedModelFiles.erase(model); + } + } + + bool VMapManager2::existsMap(const char* basePath, unsigned int mapId, int x, int y) + { + return StaticMapTree::CanLoadMap(std::string(basePath), mapId, x, y); + } + + void VMapManager2::getInstanceMapTree(InstanceTreeMap &instanceMapTree) + { + instanceMapTree = iInstanceMapTrees; + } + +} // namespace VMAP diff --git a/src/common/Collision/Management/VMapManager2.h b/src/common/Collision/Management/VMapManager2.h new file mode 100644 index 00000000000..c2e1aee1ff7 --- /dev/null +++ b/src/common/Collision/Management/VMapManager2.h @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _VMAPMANAGER2_H +#define _VMAPMANAGER2_H + +#include <mutex> +#include <unordered_map> +#include <vector> +#include "Define.h" +#include "IVMapManager.h" + +//=========================================================== + +#define MAP_FILENAME_EXTENSION2 ".vmtree" + +#define FILENAMEBUFFER_SIZE 500 + +/** +This is the main Class to manage loading and unloading of maps, line of sight, height calculation and so on. +For each map or map tile to load it reads a directory file that contains the ModelContainer files used by this map or map tile. +Each global map or instance has its own dynamic BSP-Tree. +The loaded ModelContainers are included in one of these BSP-Trees. +Additionally a table to match map ids and map names is used. +*/ + +//=========================================================== + +namespace G3D +{ + class Vector3; +} + +namespace VMAP +{ + class StaticMapTree; + class WorldModel; + + class ManagedModel + { + public: + ManagedModel() : iModel(nullptr), iRefCount(0) { } + void setModel(WorldModel* model) { iModel = model; } + WorldModel* getModel() { return iModel; } + void incRefCount() { ++iRefCount; } + int decRefCount() { return --iRefCount; } + protected: + WorldModel* iModel; + int iRefCount; + }; + + typedef std::unordered_map<uint32, StaticMapTree*> InstanceTreeMap; + typedef std::unordered_map<std::string, ManagedModel> ModelFileMap; + + enum DisableTypes + { + VMAP_DISABLE_AREAFLAG = 0x1, + VMAP_DISABLE_HEIGHT = 0x2, + VMAP_DISABLE_LOS = 0x4, + VMAP_DISABLE_LIQUIDSTATUS = 0x8 + }; + + class VMapManager2 : public IVMapManager + { + protected: + // Tree to check collision + ModelFileMap iLoadedModelFiles; + InstanceTreeMap iInstanceMapTrees; + bool thread_safe_environment; + // Mutex for iLoadedModelFiles + std::mutex LoadedModelFilesLock; + + bool _loadMap(uint32 mapId, const std::string& basePath, uint32 tileX, uint32 tileY); + /* void _unloadMap(uint32 pMapId, uint32 x, uint32 y); */ + + static uint32 GetLiquidFlagsDummy(uint32) { return 0; } + static bool IsVMAPDisabledForDummy(uint32 /*entry*/, uint8 /*flags*/) { return false; } + + InstanceTreeMap::const_iterator GetMapTree(uint32 mapId) const; + + public: + // public for debug + G3D::Vector3 convertPositionToInternalRep(float x, float y, float z) const; + static std::string getMapFileName(unsigned int mapId); + + VMapManager2(); + ~VMapManager2(void); + + void InitializeThreadUnsafe(const std::vector<uint32>& mapIds); + int loadMap(const char* pBasePath, unsigned int mapId, int x, int y) override; + + void unloadMap(unsigned int mapId, int x, int y) override; + void unloadMap(unsigned int mapId) override; + + bool isInLineOfSight(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2) override ; + /** + fill the hit pos and return true, if an object was hit + */ + bool getObjectHitPos(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) override; + float getHeight(unsigned int mapId, float x, float y, float z, float maxSearchDist) override; + + bool processCommand(char* /*command*/) override { return false; } // for debug and extensions + + bool getAreaInfo(unsigned int pMapId, float x, float y, float& z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const override; + bool GetLiquidLevel(uint32 pMapId, float x, float y, float z, uint8 reqLiquidType, float& level, float& floor, uint32& type) const override; + + WorldModel* acquireModelInstance(const std::string& basepath, const std::string& filename); + void releaseModelInstance(const std::string& filename); + + // what's the use of this? o.O + virtual std::string getDirFileName(unsigned int mapId, int /*x*/, int /*y*/) const override + { + return getMapFileName(mapId); + } + virtual bool existsMap(const char* basePath, unsigned int mapId, int x, int y) override; + + void getInstanceMapTree(InstanceTreeMap &instanceMapTree); + + typedef uint32(*GetLiquidFlagsFn)(uint32 liquidType); + GetLiquidFlagsFn GetLiquidFlagsPtr; + + typedef bool(*IsVMAPDisabledForFn)(uint32 entry, uint8 flags); + IsVMAPDisabledForFn IsVMAPDisabledForPtr; + }; +} + +#endif diff --git a/src/common/Collision/Maps/MapDefines.h b/src/common/Collision/Maps/MapDefines.h new file mode 100644 index 00000000000..538a78a38d4 --- /dev/null +++ b/src/common/Collision/Maps/MapDefines.h @@ -0,0 +1,36 @@ +#ifndef _MAPDEFINES_H +#define _MAPDEFINES_H + +#include "Define.h" +#include "DetourNavMesh.h" + +const uint32 MMAP_MAGIC = 0x4d4d4150; // 'MMAP' +#define MMAP_VERSION 5 + +struct MmapTileHeader +{ + uint32 mmapMagic; + uint32 dtVersion; + uint32 mmapVersion; + uint32 size; + bool usesLiquids : 1; + + MmapTileHeader() : mmapMagic(MMAP_MAGIC), dtVersion(DT_NAVMESH_VERSION), + mmapVersion(MMAP_VERSION), size(0), usesLiquids(true) { } +}; + +enum NavTerrain +{ + NAV_EMPTY = 0x00, + NAV_GROUND = 0x01, + NAV_MAGMA = 0x02, + NAV_SLIME = 0x04, + NAV_WATER = 0x08, + NAV_UNUSED1 = 0x10, + NAV_UNUSED2 = 0x20, + NAV_UNUSED3 = 0x40, + NAV_UNUSED4 = 0x80 + // we only have 8 bits +}; + +#endif diff --git a/src/common/Collision/Maps/MapTree.cpp b/src/common/Collision/Maps/MapTree.cpp new file mode 100644 index 00000000000..e374da4f1b9 --- /dev/null +++ b/src/common/Collision/Maps/MapTree.cpp @@ -0,0 +1,483 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "MapTree.h" +#include "ModelInstance.h" +#include "VMapManager2.h" +#include "VMapDefinitions.h" +#include "Log.h" +#include "Errors.h" + +#include <string> +#include <sstream> +#include <iomanip> +#include <limits> + +using G3D::Vector3; + +namespace VMAP +{ + + class MapRayCallback + { + public: + MapRayCallback(ModelInstance* val): prims(val), hit(false) { } + bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool pStopAtFirstHit=true) + { + bool result = prims[entry].intersectRay(ray, distance, pStopAtFirstHit); + if (result) + hit = true; + return result; + } + bool didHit() { return hit; } + protected: + ModelInstance* prims; + bool hit; + }; + + class AreaInfoCallback + { + public: + AreaInfoCallback(ModelInstance* val): prims(val) { } + void operator()(const Vector3& point, uint32 entry) + { +#ifdef VMAP_DEBUG + TC_LOG_DEBUG("maps", "AreaInfoCallback: trying to intersect '%s'", prims[entry].name.c_str()); +#endif + prims[entry].intersectPoint(point, aInfo); + } + + ModelInstance* prims; + AreaInfo aInfo; + }; + + class LocationInfoCallback + { + public: + LocationInfoCallback(ModelInstance* val, LocationInfo &info): prims(val), locInfo(info), result(false) { } + void operator()(const Vector3& point, uint32 entry) + { +#ifdef VMAP_DEBUG + TC_LOG_DEBUG("maps", "LocationInfoCallback: trying to intersect '%s'", prims[entry].name.c_str()); +#endif + if (prims[entry].GetLocationInfo(point, locInfo)) + result = true; + } + + ModelInstance* prims; + LocationInfo &locInfo; + bool result; + }; + + //========================================================= + + std::string StaticMapTree::getTileFileName(uint32 mapID, uint32 tileX, uint32 tileY) + { + std::stringstream tilefilename; + tilefilename.fill('0'); + tilefilename << std::setw(3) << mapID << '_'; + //tilefilename << std::setw(2) << tileX << '_' << std::setw(2) << tileY << ".vmtile"; + tilefilename << std::setw(2) << tileY << '_' << std::setw(2) << tileX << ".vmtile"; + return tilefilename.str(); + } + + bool StaticMapTree::getAreaInfo(Vector3 &pos, uint32 &flags, int32 &adtId, int32 &rootId, int32 &groupId) const + { + AreaInfoCallback intersectionCallBack(iTreeValues); + iTree.intersectPoint(pos, intersectionCallBack); + if (intersectionCallBack.aInfo.result) + { + flags = intersectionCallBack.aInfo.flags; + adtId = intersectionCallBack.aInfo.adtId; + rootId = intersectionCallBack.aInfo.rootId; + groupId = intersectionCallBack.aInfo.groupId; + pos.z = intersectionCallBack.aInfo.ground_Z; + return true; + } + return false; + } + + bool StaticMapTree::GetLocationInfo(const Vector3 &pos, LocationInfo &info) const + { + LocationInfoCallback intersectionCallBack(iTreeValues, info); + iTree.intersectPoint(pos, intersectionCallBack); + return intersectionCallBack.result; + } + + StaticMapTree::StaticMapTree(uint32 mapID, const std::string &basePath) : + iMapID(mapID), iIsTiled(false), iTreeValues(NULL), + iNTreeValues(0), iBasePath(basePath) + { + if (iBasePath.length() > 0 && iBasePath[iBasePath.length()-1] != '/' && iBasePath[iBasePath.length()-1] != '\\') + { + iBasePath.push_back('/'); + } + } + + //========================================================= + //! Make sure to call unloadMap() to unregister acquired model references before destroying + StaticMapTree::~StaticMapTree() + { + delete[] iTreeValues; + } + + //========================================================= + /** + If intersection is found within pMaxDist, sets pMaxDist to intersection distance and returns true. + Else, pMaxDist is not modified and returns false; + */ + + bool StaticMapTree::getIntersectionTime(const G3D::Ray& pRay, float &pMaxDist, bool pStopAtFirstHit) const + { + float distance = pMaxDist; + MapRayCallback intersectionCallBack(iTreeValues); + iTree.intersectRay(pRay, intersectionCallBack, distance, pStopAtFirstHit); + if (intersectionCallBack.didHit()) + pMaxDist = distance; + return intersectionCallBack.didHit(); + } + //========================================================= + + bool StaticMapTree::isInLineOfSight(const Vector3& pos1, const Vector3& pos2) const + { + float maxDist = (pos2 - pos1).magnitude(); + // return false if distance is over max float, in case of cheater teleporting to the end of the universe + if (maxDist == std::numeric_limits<float>::max() || !std::isfinite(maxDist)) + return false; + + // valid map coords should *never ever* produce float overflow, but this would produce NaNs too + ASSERT(maxDist < std::numeric_limits<float>::max()); + // prevent NaN values which can cause BIH intersection to enter infinite loop + if (maxDist < 1e-10f) + return true; + // direction with length of 1 + G3D::Ray ray = G3D::Ray::fromOriginAndDirection(pos1, (pos2 - pos1)/maxDist); + if (getIntersectionTime(ray, maxDist, true)) + return false; + + return true; + } + //========================================================= + /** + When moving from pos1 to pos2 check if we hit an object. Return true and the position if we hit one + Return the hit pos or the original dest pos + */ + + bool StaticMapTree::getObjectHitPos(const Vector3& pPos1, const Vector3& pPos2, Vector3& pResultHitPos, float pModifyDist) const + { + bool result=false; + float maxDist = (pPos2 - pPos1).magnitude(); + // valid map coords should *never ever* produce float overflow, but this would produce NaNs too + ASSERT(maxDist < std::numeric_limits<float>::max()); + // prevent NaN values which can cause BIH intersection to enter infinite loop + if (maxDist < 1e-10f) + { + pResultHitPos = pPos2; + return false; + } + Vector3 dir = (pPos2 - pPos1)/maxDist; // direction with length of 1 + G3D::Ray ray(pPos1, dir); + float dist = maxDist; + if (getIntersectionTime(ray, dist, false)) + { + pResultHitPos = pPos1 + dir * dist; + if (pModifyDist < 0) + { + if ((pResultHitPos - pPos1).magnitude() > -pModifyDist) + { + pResultHitPos = pResultHitPos + dir*pModifyDist; + } + else + { + pResultHitPos = pPos1; + } + } + else + { + pResultHitPos = pResultHitPos + dir*pModifyDist; + } + result = true; + } + else + { + pResultHitPos = pPos2; + result = false; + } + return result; + } + + //========================================================= + + float StaticMapTree::getHeight(const Vector3& pPos, float maxSearchDist) const + { + float height = G3D::finf(); + Vector3 dir = Vector3(0, 0, -1); + G3D::Ray ray(pPos, dir); // direction with length of 1 + float maxDist = maxSearchDist; + if (getIntersectionTime(ray, maxDist, false)) + { + height = pPos.z - maxDist; + } + return(height); + } + + //========================================================= + + bool StaticMapTree::CanLoadMap(const std::string &vmapPath, uint32 mapID, uint32 tileX, uint32 tileY) + { + std::string basePath = vmapPath; + if (basePath.length() > 0 && basePath[basePath.length()-1] != '/' && basePath[basePath.length()-1] != '\\') + basePath.push_back('/'); + std::string fullname = basePath + VMapManager2::getMapFileName(mapID); + bool success = true; + FILE* rf = fopen(fullname.c_str(), "rb"); + if (!rf) + return false; + /// @todo check magic number when implemented... + char tiled; + char chunk[8]; + if (!readChunk(rf, chunk, VMAP_MAGIC, 8) || fread(&tiled, sizeof(char), 1, rf) != 1) + { + fclose(rf); + return false; + } + if (tiled) + { + std::string tilefile = basePath + getTileFileName(mapID, tileX, tileY); + FILE* tf = fopen(tilefile.c_str(), "rb"); + if (!tf) + success = false; + else + { + if (!readChunk(tf, chunk, VMAP_MAGIC, 8)) + success = false; + fclose(tf); + } + } + fclose(rf); + return success; + } + + //========================================================= + + bool StaticMapTree::InitMap(const std::string &fname, VMapManager2* vm) + { + VMAP_DEBUG_LOG("maps", "StaticMapTree::InitMap() : initializing StaticMapTree '%s'", fname.c_str()); + bool success = false; + std::string fullname = iBasePath + fname; + FILE* rf = fopen(fullname.c_str(), "rb"); + if (!rf) + return false; + + char chunk[8]; + char tiled = '\0'; + + if (readChunk(rf, chunk, VMAP_MAGIC, 8) && fread(&tiled, sizeof(char), 1, rf) == 1 && + readChunk(rf, chunk, "NODE", 4) && iTree.readFromFile(rf)) + { + iNTreeValues = iTree.primCount(); + iTreeValues = new ModelInstance[iNTreeValues]; + success = readChunk(rf, chunk, "GOBJ", 4); + } + + iIsTiled = tiled != '\0'; + + // global model spawns + // only non-tiled maps have them, and if so exactly one (so far at least...) + ModelSpawn spawn; +#ifdef VMAP_DEBUG + TC_LOG_DEBUG("maps", "StaticMapTree::InitMap() : map isTiled: %u", static_cast<uint32>(iIsTiled)); +#endif + if (!iIsTiled && ModelSpawn::readFromFile(rf, spawn)) + { + WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name); + VMAP_DEBUG_LOG("maps", "StaticMapTree::InitMap() : loading %s", spawn.name.c_str()); + if (model) + { + // assume that global model always is the first and only tree value (could be improved...) + iTreeValues[0] = ModelInstance(spawn, model); + iLoadedSpawns[0] = 1; + } + else + { + success = false; + VMAP_ERROR_LOG("misc", "StaticMapTree::InitMap() : could not acquire WorldModel pointer for '%s'", spawn.name.c_str()); + } + } + + fclose(rf); + return success; + } + + //========================================================= + + void StaticMapTree::UnloadMap(VMapManager2* vm) + { + for (loadedSpawnMap::iterator i = iLoadedSpawns.begin(); i != iLoadedSpawns.end(); ++i) + { + iTreeValues[i->first].setUnloaded(); + for (uint32 refCount = 0; refCount < i->second; ++refCount) + vm->releaseModelInstance(iTreeValues[i->first].name); + } + iLoadedSpawns.clear(); + iLoadedTiles.clear(); + } + + //========================================================= + + bool StaticMapTree::LoadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm) + { + if (!iIsTiled) + { + // currently, core creates grids for all maps, whether it has terrain tiles or not + // so we need "fake" tile loads to know when we can unload map geometry + iLoadedTiles[packTileID(tileX, tileY)] = false; + return true; + } + if (!iTreeValues) + { + VMAP_ERROR_LOG("misc", "StaticMapTree::LoadMapTile() : tree has not been initialized [%u, %u]", tileX, tileY); + return false; + } + bool result = true; + + std::string tilefile = iBasePath + getTileFileName(iMapID, tileX, tileY); + FILE* tf = fopen(tilefile.c_str(), "rb"); + if (tf) + { + char chunk[8]; + + if (!readChunk(tf, chunk, VMAP_MAGIC, 8)) + result = false; + uint32 numSpawns = 0; + if (result && fread(&numSpawns, sizeof(uint32), 1, tf) != 1) + result = false; + for (uint32 i=0; i<numSpawns && result; ++i) + { + // read model spawns + ModelSpawn spawn; + result = ModelSpawn::readFromFile(tf, spawn); + if (result) + { + // acquire model instance + WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name); + if (!model) + VMAP_ERROR_LOG("misc", "StaticMapTree::LoadMapTile() : could not acquire WorldModel pointer [%u, %u]", tileX, tileY); + + // update tree + uint32 referencedVal; + + if (fread(&referencedVal, sizeof(uint32), 1, tf) == 1) + { + if (!iLoadedSpawns.count(referencedVal)) + { + if (referencedVal > iNTreeValues) + { + VMAP_ERROR_LOG("maps", "StaticMapTree::LoadMapTile() : invalid tree element (%u/%u) referenced in tile %s", referencedVal, iNTreeValues, tilefile.c_str()); + continue; + } + + iTreeValues[referencedVal] = ModelInstance(spawn, model); + iLoadedSpawns[referencedVal] = 1; + } + else + { + ++iLoadedSpawns[referencedVal]; +#ifdef VMAP_DEBUG + if (iTreeValues[referencedVal].ID != spawn.ID) + TC_LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : trying to load wrong spawn in node"); + else if (iTreeValues[referencedVal].name != spawn.name) + TC_LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : name collision on GUID=%u", spawn.ID); +#endif + } + } + else + result = false; + } + } + iLoadedTiles[packTileID(tileX, tileY)] = true; + fclose(tf); + } + else + iLoadedTiles[packTileID(tileX, tileY)] = false; + return result; + } + + //========================================================= + + void StaticMapTree::UnloadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm) + { + uint32 tileID = packTileID(tileX, tileY); + loadedTileMap::iterator tile = iLoadedTiles.find(tileID); + if (tile == iLoadedTiles.end()) + { + VMAP_ERROR_LOG("misc", "StaticMapTree::UnloadMapTile() : trying to unload non-loaded tile - Map:%u X:%u Y:%u", iMapID, tileX, tileY); + return; + } + if (tile->second) // file associated with tile + { + std::string tilefile = iBasePath + getTileFileName(iMapID, tileX, tileY); + FILE* tf = fopen(tilefile.c_str(), "rb"); + if (tf) + { + bool result=true; + char chunk[8]; + if (!readChunk(tf, chunk, VMAP_MAGIC, 8)) + result = false; + uint32 numSpawns; + if (fread(&numSpawns, sizeof(uint32), 1, tf) != 1) + result = false; + for (uint32 i=0; i<numSpawns && result; ++i) + { + // read model spawns + ModelSpawn spawn; + result = ModelSpawn::readFromFile(tf, spawn); + if (result) + { + // release model instance + vm->releaseModelInstance(spawn.name); + + // update tree + uint32 referencedNode; + + if (fread(&referencedNode, sizeof(uint32), 1, tf) != 1) + result = false; + else + { + if (!iLoadedSpawns.count(referencedNode)) + VMAP_ERROR_LOG("misc", "StaticMapTree::UnloadMapTile() : trying to unload non-referenced model '%s' (ID:%u)", spawn.name.c_str(), spawn.ID); + else if (--iLoadedSpawns[referencedNode] == 0) + { + iTreeValues[referencedNode].setUnloaded(); + iLoadedSpawns.erase(referencedNode); + } + } + } + } + fclose(tf); + } + } + iLoadedTiles.erase(tile); + } + + void StaticMapTree::getModelInstances(ModelInstance* &models, uint32 &count) + { + models = iTreeValues; + count = iNTreeValues; + } +} diff --git a/src/common/Collision/Maps/MapTree.h b/src/common/Collision/Maps/MapTree.h new file mode 100644 index 00000000000..08bd5c3d3a1 --- /dev/null +++ b/src/common/Collision/Maps/MapTree.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _MAPTREE_H +#define _MAPTREE_H + +#include "Define.h" +#include "BoundingIntervalHierarchy.h" +#include <unordered_map> + +namespace VMAP +{ + class ModelInstance; + class GroupModel; + class VMapManager2; + + struct LocationInfo + { + LocationInfo(): hitInstance(nullptr), hitModel(nullptr), ground_Z(-G3D::finf()) { } + const ModelInstance* hitInstance; + const GroupModel* hitModel; + float ground_Z; + }; + + class StaticMapTree + { + typedef std::unordered_map<uint32, bool> loadedTileMap; + typedef std::unordered_map<uint32, uint32> loadedSpawnMap; + private: + uint32 iMapID; + bool iIsTiled; + BIH iTree; + ModelInstance* iTreeValues; // the tree entries + uint32 iNTreeValues; + + // Store all the map tile idents that are loaded for that map + // some maps are not splitted into tiles and we have to make sure, not removing the map before all tiles are removed + // empty tiles have no tile file, hence map with bool instead of just a set (consistency check) + loadedTileMap iLoadedTiles; + // stores <tree_index, reference_count> to invalidate tree values, unload map, and to be able to report errors + loadedSpawnMap iLoadedSpawns; + std::string iBasePath; + + private: + bool getIntersectionTime(const G3D::Ray& pRay, float &pMaxDist, bool pStopAtFirstHit) const; + //bool containsLoadedMapTile(unsigned int pTileIdent) const { return(iLoadedMapTiles.containsKey(pTileIdent)); } + public: + static std::string getTileFileName(uint32 mapID, uint32 tileX, uint32 tileY); + static uint32 packTileID(uint32 tileX, uint32 tileY) { return tileX<<16 | tileY; } + static void unpackTileID(uint32 ID, uint32 &tileX, uint32 &tileY) { tileX = ID>>16; tileY = ID&0xFF; } + static bool CanLoadMap(const std::string &basePath, uint32 mapID, uint32 tileX, uint32 tileY); + + StaticMapTree(uint32 mapID, const std::string &basePath); + ~StaticMapTree(); + + bool isInLineOfSight(const G3D::Vector3& pos1, const G3D::Vector3& pos2) const; + bool getObjectHitPos(const G3D::Vector3& pos1, const G3D::Vector3& pos2, G3D::Vector3& pResultHitPos, float pModifyDist) const; + float getHeight(const G3D::Vector3& pPos, float maxSearchDist) const; + bool getAreaInfo(G3D::Vector3 &pos, uint32 &flags, int32 &adtId, int32 &rootId, int32 &groupId) const; + bool GetLocationInfo(const G3D::Vector3 &pos, LocationInfo &info) const; + + bool InitMap(const std::string &fname, VMapManager2* vm); + void UnloadMap(VMapManager2* vm); + bool LoadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm); + void UnloadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm); + bool isTiled() const { return iIsTiled; } + uint32 numLoadedTiles() const { return iLoadedTiles.size(); } + void getModelInstances(ModelInstance* &models, uint32 &count); + + private: + StaticMapTree(StaticMapTree const& right) = delete; + StaticMapTree& operator=(StaticMapTree const& right) = delete; + }; + + struct AreaInfo + { + AreaInfo(): result(false), ground_Z(-G3D::finf()), flags(0), adtId(0), + rootId(0), groupId(0) { } + bool result; + float ground_Z; + uint32 flags; + int32 adtId; + int32 rootId; + int32 groupId; + }; +} // VMAP + +#endif // _MAPTREE_H diff --git a/src/common/Collision/Maps/TileAssembler.cpp b/src/common/Collision/Maps/TileAssembler.cpp new file mode 100644 index 00000000000..ec7b759f975 --- /dev/null +++ b/src/common/Collision/Maps/TileAssembler.cpp @@ -0,0 +1,550 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "TileAssembler.h" +#include "MapTree.h" +#include "BoundingIntervalHierarchy.h" +#include "VMapDefinitions.h" + +#include <set> +#include <iomanip> +#include <sstream> + +using G3D::Vector3; +using G3D::AABox; +using G3D::inf; +using std::pair; + +template<> struct BoundsTrait<VMAP::ModelSpawn*> +{ + static void getBounds(const VMAP::ModelSpawn* const &obj, G3D::AABox& out) { out = obj->getBounds(); } +}; + +namespace VMAP +{ + bool readChunk(FILE* rf, char *dest, const char *compare, uint32 len) + { + if (fread(dest, sizeof(char), len, rf) != len) return false; + return memcmp(dest, compare, len) == 0; + } + + Vector3 ModelPosition::transform(const Vector3& pIn) const + { + Vector3 out = pIn * iScale; + out = iRotation * out; + return(out); + } + + //================================================================= + + TileAssembler::TileAssembler(const std::string& pSrcDirName, const std::string& pDestDirName) + : iDestDir(pDestDirName), iSrcDir(pSrcDirName), iFilterMethod(NULL), iCurrentUniqueNameId(0) + { + //mkdir(iDestDir); + //init(); + } + + TileAssembler::~TileAssembler() + { + //delete iCoordModelMapping; + } + + bool TileAssembler::convertWorld2() + { + bool success = readMapSpawns(); + if (!success) + return false; + + // export Map data + for (MapData::iterator map_iter = mapData.begin(); map_iter != mapData.end() && success; ++map_iter) + { + // build global map tree + std::vector<ModelSpawn*> mapSpawns; + UniqueEntryMap::iterator entry; + printf("Calculating model bounds for map %u...\n", map_iter->first); + for (entry = map_iter->second->UniqueEntries.begin(); entry != map_iter->second->UniqueEntries.end(); ++entry) + { + // M2 models don't have a bound set in WDT/ADT placement data, i still think they're not used for LoS at all on retail + if (entry->second.flags & MOD_M2) + { + if (!calculateTransformedBound(entry->second)) + break; + } + else if (entry->second.flags & MOD_WORLDSPAWN) // WMO maps and terrain maps use different origin, so we need to adapt :/ + { + /// @todo remove extractor hack and uncomment below line: + //entry->second.iPos += Vector3(533.33333f*32, 533.33333f*32, 0.f); + entry->second.iBound = entry->second.iBound + Vector3(533.33333f*32, 533.33333f*32, 0.f); + } + mapSpawns.push_back(&(entry->second)); + spawnedModelFiles.insert(entry->second.name); + } + + printf("Creating map tree for map %u...\n", map_iter->first); + BIH pTree; + + try + { + pTree.build(mapSpawns, BoundsTrait<ModelSpawn*>::getBounds); + } + catch (std::exception& e) + { + printf("Exception ""%s"" when calling pTree.build", e.what()); + return false; + } + + // ===> possibly move this code to StaticMapTree class + std::map<uint32, uint32> modelNodeIdx; + for (uint32 i=0; i<mapSpawns.size(); ++i) + modelNodeIdx.insert(pair<uint32, uint32>(mapSpawns[i]->ID, i)); + + // write map tree file + std::stringstream mapfilename; + mapfilename << iDestDir << '/' << std::setfill('0') << std::setw(3) << map_iter->first << ".vmtree"; + FILE* mapfile = fopen(mapfilename.str().c_str(), "wb"); + if (!mapfile) + { + success = false; + printf("Cannot open %s\n", mapfilename.str().c_str()); + break; + } + + //general info + if (success && fwrite(VMAP_MAGIC, 1, 8, mapfile) != 8) success = false; + uint32 globalTileID = StaticMapTree::packTileID(65, 65); + pair<TileMap::iterator, TileMap::iterator> globalRange = map_iter->second->TileEntries.equal_range(globalTileID); + char isTiled = globalRange.first == globalRange.second; // only maps without terrain (tiles) have global WMO + if (success && fwrite(&isTiled, sizeof(char), 1, mapfile) != 1) success = false; + // Nodes + if (success && fwrite("NODE", 4, 1, mapfile) != 1) success = false; + if (success) success = pTree.writeToFile(mapfile); + // global map spawns (WDT), if any (most instances) + if (success && fwrite("GOBJ", 4, 1, mapfile) != 1) success = false; + + for (TileMap::iterator glob=globalRange.first; glob != globalRange.second && success; ++glob) + { + success = ModelSpawn::writeToFile(mapfile, map_iter->second->UniqueEntries[glob->second]); + } + + fclose(mapfile); + + // <==== + + // write map tile files, similar to ADT files, only with extra BSP tree node info + TileMap &tileEntries = map_iter->second->TileEntries; + TileMap::iterator tile; + for (tile = tileEntries.begin(); tile != tileEntries.end(); ++tile) + { + const ModelSpawn &spawn = map_iter->second->UniqueEntries[tile->second]; + if (spawn.flags & MOD_WORLDSPAWN) // WDT spawn, saved as tile 65/65 currently... + continue; + uint32 nSpawns = tileEntries.count(tile->first); + std::stringstream tilefilename; + tilefilename.fill('0'); + tilefilename << iDestDir << '/' << std::setw(3) << map_iter->first << '_'; + uint32 x, y; + StaticMapTree::unpackTileID(tile->first, x, y); + tilefilename << std::setw(2) << x << '_' << std::setw(2) << y << ".vmtile"; + if (FILE* tilefile = fopen(tilefilename.str().c_str(), "wb")) + { + // file header + if (success && fwrite(VMAP_MAGIC, 1, 8, tilefile) != 8) success = false; + // write number of tile spawns + if (success && fwrite(&nSpawns, sizeof(uint32), 1, tilefile) != 1) success = false; + // write tile spawns + for (uint32 s=0; s<nSpawns; ++s) + { + if (s) + ++tile; + const ModelSpawn &spawn2 = map_iter->second->UniqueEntries[tile->second]; + success = success && ModelSpawn::writeToFile(tilefile, spawn2); + // MapTree nodes to update when loading tile: + std::map<uint32, uint32>::iterator nIdx = modelNodeIdx.find(spawn2.ID); + if (success && fwrite(&nIdx->second, sizeof(uint32), 1, tilefile) != 1) success = false; + } + fclose(tilefile); + } + } + // break; //test, extract only first map; TODO: remvoe this line + } + + // add an object models, listed in temp_gameobject_models file + exportGameobjectModels(); + // export objects + std::cout << "\nConverting Model Files" << std::endl; + for (std::set<std::string>::iterator mfile = spawnedModelFiles.begin(); mfile != spawnedModelFiles.end(); ++mfile) + { + std::cout << "Converting " << *mfile << std::endl; + if (!convertRawFile(*mfile)) + { + std::cout << "error converting " << *mfile << std::endl; + success = false; + break; + } + } + + //cleanup: + for (MapData::iterator map_iter = mapData.begin(); map_iter != mapData.end(); ++map_iter) + { + delete map_iter->second; + } + return success; + } + + bool TileAssembler::readMapSpawns() + { + std::string fname = iSrcDir + "/dir_bin"; + FILE* dirf = fopen(fname.c_str(), "rb"); + if (!dirf) + { + printf("Could not read dir_bin file!\n"); + return false; + } + printf("Read coordinate mapping...\n"); + uint32 mapID, tileX, tileY, check=0; + G3D::Vector3 v1, v2; + ModelSpawn spawn; + while (!feof(dirf)) + { + check = 0; + // read mapID, tileX, tileY, Flags, adtID, ID, Pos, Rot, Scale, Bound_lo, Bound_hi, name + check += fread(&mapID, sizeof(uint32), 1, dirf); + if (check == 0) // EoF... + break; + check += fread(&tileX, sizeof(uint32), 1, dirf); + check += fread(&tileY, sizeof(uint32), 1, dirf); + if (!ModelSpawn::readFromFile(dirf, spawn)) + break; + + MapSpawns *current; + MapData::iterator map_iter = mapData.find(mapID); + if (map_iter == mapData.end()) + { + printf("spawning Map %u\n", mapID); + mapData[mapID] = current = new MapSpawns(); + } + else current = (*map_iter).second; + current->UniqueEntries.insert(pair<uint32, ModelSpawn>(spawn.ID, spawn)); + current->TileEntries.insert(pair<uint32, uint32>(StaticMapTree::packTileID(tileX, tileY), spawn.ID)); + } + bool success = (ferror(dirf) == 0); + fclose(dirf); + return success; + } + + bool TileAssembler::calculateTransformedBound(ModelSpawn &spawn) + { + std::string modelFilename(iSrcDir); + modelFilename.push_back('/'); + modelFilename.append(spawn.name); + + ModelPosition modelPosition; + modelPosition.iDir = spawn.iRot; + modelPosition.iScale = spawn.iScale; + modelPosition.init(); + + WorldModel_Raw raw_model; + if (!raw_model.Read(modelFilename.c_str())) + return false; + + uint32 groups = raw_model.groupsArray.size(); + if (groups != 1) + printf("Warning: '%s' does not seem to be a M2 model!\n", modelFilename.c_str()); + + AABox modelBound; + bool boundEmpty=true; + + for (uint32 g=0; g<groups; ++g) // should be only one for M2 files... + { + std::vector<Vector3>& vertices = raw_model.groupsArray[g].vertexArray; + + if (vertices.empty()) + { + std::cout << "error: model '" << spawn.name << "' has no geometry!" << std::endl; + continue; + } + + uint32 nvectors = vertices.size(); + for (uint32 i = 0; i < nvectors; ++i) + { + Vector3 v = modelPosition.transform(vertices[i]); + + if (boundEmpty) + modelBound = AABox(v, v), boundEmpty=false; + else + modelBound.merge(v); + } + } + spawn.iBound = modelBound + spawn.iPos; + spawn.flags |= MOD_HAS_BOUND; + return true; + } + + struct WMOLiquidHeader + { + int xverts, yverts, xtiles, ytiles; + float pos_x; + float pos_y; + float pos_z; + short type; + }; + //================================================================= + bool TileAssembler::convertRawFile(const std::string& pModelFilename) + { + bool success = true; + std::string filename = iSrcDir; + if (filename.length() >0) + filename.push_back('/'); + filename.append(pModelFilename); + + WorldModel_Raw raw_model; + if (!raw_model.Read(filename.c_str())) + return false; + + // write WorldModel + WorldModel model; + model.setRootWmoID(raw_model.RootWMOID); + if (!raw_model.groupsArray.empty()) + { + std::vector<GroupModel> groupsArray; + + uint32 groups = raw_model.groupsArray.size(); + for (uint32 g = 0; g < groups; ++g) + { + GroupModel_Raw& raw_group = raw_model.groupsArray[g]; + groupsArray.push_back(GroupModel(raw_group.mogpflags, raw_group.GroupWMOID, raw_group.bounds )); + groupsArray.back().setMeshData(raw_group.vertexArray, raw_group.triangles); + groupsArray.back().setLiquidData(raw_group.liquid); + } + + model.setGroupModels(groupsArray); + } + + success = model.writeFile(iDestDir + "/" + pModelFilename + ".vmo"); + //std::cout << "readRawFile2: '" << pModelFilename << "' tris: " << nElements << " nodes: " << nNodes << std::endl; + return success; + } + + void TileAssembler::exportGameobjectModels() + { + FILE* model_list = fopen((iSrcDir + "/" + "temp_gameobject_models").c_str(), "rb"); + if (!model_list) + return; + + FILE* model_list_copy = fopen((iDestDir + "/" + GAMEOBJECT_MODELS).c_str(), "wb"); + if (!model_list_copy) + { + fclose(model_list); + return; + } + + uint32 name_length, displayId; + char buff[500]; + while (!feof(model_list)) + { + if (fread(&displayId, sizeof(uint32), 1, model_list) != 1 + || fread(&name_length, sizeof(uint32), 1, model_list) != 1 + || name_length >= sizeof(buff) + || fread(&buff, sizeof(char), name_length, model_list) != name_length) + { + std::cout << "\nFile 'temp_gameobject_models' seems to be corrupted" << std::endl; + break; + } + + std::string model_name(buff, name_length); + + WorldModel_Raw raw_model; + if ( !raw_model.Read((iSrcDir + "/" + model_name).c_str()) ) + continue; + + spawnedModelFiles.insert(model_name); + AABox bounds; + bool boundEmpty = true; + for (uint32 g = 0; g < raw_model.groupsArray.size(); ++g) + { + std::vector<Vector3>& vertices = raw_model.groupsArray[g].vertexArray; + + uint32 nvectors = vertices.size(); + for (uint32 i = 0; i < nvectors; ++i) + { + Vector3& v = vertices[i]; + if (boundEmpty) + bounds = AABox(v, v), boundEmpty = false; + else + bounds.merge(v); + } + } + + if (bounds.isEmpty()) + { + std::cout << "\nModel " << std::string(buff, name_length) << " has empty bounding box" << std::endl; + continue; + } + + if (!bounds.isFinite()) + { + std::cout << "\nModel " << std::string(buff, name_length) << " has invalid bounding box" << std::endl; + continue; + } + + fwrite(&displayId, sizeof(uint32), 1, model_list_copy); + fwrite(&name_length, sizeof(uint32), 1, model_list_copy); + fwrite(&buff, sizeof(char), name_length, model_list_copy); + fwrite(&bounds.low(), sizeof(Vector3), 1, model_list_copy); + fwrite(&bounds.high(), sizeof(Vector3), 1, model_list_copy); + } + + fclose(model_list); + fclose(model_list_copy); + } + // temporary use defines to simplify read/check code (close file and return at fail) + #define READ_OR_RETURN(V, S) if (fread((V), (S), 1, rf) != 1) { \ + fclose(rf); printf("readfail, op = %i\n", readOperation); return(false); } + #define READ_OR_RETURN_WITH_DELETE(V, S) if (fread((V), (S), 1, rf) != 1) { \ + fclose(rf); printf("readfail, op = %i\n", readOperation); delete[] V; return(false); }; + #define CMP_OR_RETURN(V, S) if (strcmp((V), (S)) != 0) { \ + fclose(rf); printf("cmpfail, %s!=%s\n", V, S);return(false); } + + bool GroupModel_Raw::Read(FILE* rf) + { + char blockId[5]; + blockId[4] = 0; + int blocksize; + int readOperation = 0; + + READ_OR_RETURN(&mogpflags, sizeof(uint32)); + READ_OR_RETURN(&GroupWMOID, sizeof(uint32)); + + + Vector3 vec1, vec2; + READ_OR_RETURN(&vec1, sizeof(Vector3)); + + READ_OR_RETURN(&vec2, sizeof(Vector3)); + bounds.set(vec1, vec2); + + READ_OR_RETURN(&liquidflags, sizeof(uint32)); + + // will this ever be used? what is it good for anyway?? + uint32 branches; + READ_OR_RETURN(&blockId, 4); + CMP_OR_RETURN(blockId, "GRP "); + READ_OR_RETURN(&blocksize, sizeof(int)); + READ_OR_RETURN(&branches, sizeof(uint32)); + for (uint32 b=0; b<branches; ++b) + { + uint32 indexes; + // indexes for each branch (not used jet) + READ_OR_RETURN(&indexes, sizeof(uint32)); + } + + // ---- indexes + READ_OR_RETURN(&blockId, 4); + CMP_OR_RETURN(blockId, "INDX"); + READ_OR_RETURN(&blocksize, sizeof(int)); + uint32 nindexes; + READ_OR_RETURN(&nindexes, sizeof(uint32)); + if (nindexes >0) + { + uint16 *indexarray = new uint16[nindexes]; + READ_OR_RETURN_WITH_DELETE(indexarray, nindexes*sizeof(uint16)); + triangles.reserve(nindexes / 3); + for (uint32 i=0; i<nindexes; i+=3) + triangles.push_back(MeshTriangle(indexarray[i], indexarray[i+1], indexarray[i+2])); + + delete[] indexarray; + } + + // ---- vectors + READ_OR_RETURN(&blockId, 4); + CMP_OR_RETURN(blockId, "VERT"); + READ_OR_RETURN(&blocksize, sizeof(int)); + uint32 nvectors; + READ_OR_RETURN(&nvectors, sizeof(uint32)); + + if (nvectors >0) + { + float *vectorarray = new float[nvectors*3]; + READ_OR_RETURN_WITH_DELETE(vectorarray, nvectors*sizeof(float)*3); + for (uint32 i=0; i<nvectors; ++i) + vertexArray.push_back( Vector3(vectorarray + 3*i) ); + + delete[] vectorarray; + } + // ----- liquid + liquid = 0; + if (liquidflags& 1) + { + WMOLiquidHeader hlq; + READ_OR_RETURN(&blockId, 4); + CMP_OR_RETURN(blockId, "LIQU"); + READ_OR_RETURN(&blocksize, sizeof(int)); + READ_OR_RETURN(&hlq, sizeof(WMOLiquidHeader)); + liquid = new WmoLiquid(hlq.xtiles, hlq.ytiles, Vector3(hlq.pos_x, hlq.pos_y, hlq.pos_z), hlq.type); + uint32 size = hlq.xverts*hlq.yverts; + READ_OR_RETURN(liquid->GetHeightStorage(), size*sizeof(float)); + size = hlq.xtiles*hlq.ytiles; + READ_OR_RETURN(liquid->GetFlagsStorage(), size); + } + + return true; + } + + + GroupModel_Raw::~GroupModel_Raw() + { + delete liquid; + } + + bool WorldModel_Raw::Read(const char * path) + { + FILE* rf = fopen(path, "rb"); + if (!rf) + { + printf("ERROR: Can't open raw model file: %s\n", path); + return false; + } + + char ident[9]; + ident[8] = '\0'; + int readOperation = 0; + + READ_OR_RETURN(&ident, 8); + CMP_OR_RETURN(ident, RAW_VMAP_MAGIC); + + // we have to read one int. This is needed during the export and we have to skip it here + uint32 tempNVectors; + READ_OR_RETURN(&tempNVectors, sizeof(tempNVectors)); + + uint32 groups; + READ_OR_RETURN(&groups, sizeof(uint32)); + READ_OR_RETURN(&RootWMOID, sizeof(uint32)); + + groupsArray.resize(groups); + bool succeed = true; + for (uint32 g = 0; g < groups && succeed; ++g) + succeed = groupsArray[g].Read(rf); + + if (succeed) /// rf will be freed inside Read if the function had any errors. + fclose(rf); + return succeed; + } + + // drop of temporary use defines + #undef READ_OR_RETURN + #undef CMP_OR_RETURN +} diff --git a/src/common/Collision/Maps/TileAssembler.h b/src/common/Collision/Maps/TileAssembler.h new file mode 100644 index 00000000000..581622c6b73 --- /dev/null +++ b/src/common/Collision/Maps/TileAssembler.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _TILEASSEMBLER_H_ +#define _TILEASSEMBLER_H_ + +#include <G3D/Vector3.h> +#include <G3D/Matrix3.h> +#include <map> +#include <set> + +#include "ModelInstance.h" +#include "WorldModel.h" + +namespace VMAP +{ + /** + This Class is used to convert raw vector data into balanced BSP-Trees. + To start the conversion call convertWorld(). + */ + //=============================================== + + class ModelPosition + { + private: + G3D::Matrix3 iRotation; + public: + ModelPosition(): iScale(0.0f) { } + G3D::Vector3 iPos; + G3D::Vector3 iDir; + float iScale; + void init() + { + iRotation = G3D::Matrix3::fromEulerAnglesZYX(G3D::pif()*iDir.y/180.f, G3D::pif()*iDir.x/180.f, G3D::pif()*iDir.z/180.f); + } + G3D::Vector3 transform(const G3D::Vector3& pIn) const; + void moveToBasePos(const G3D::Vector3& pBasePos) { iPos -= pBasePos; } + }; + + typedef std::map<uint32, ModelSpawn> UniqueEntryMap; + typedef std::multimap<uint32, uint32> TileMap; + + struct MapSpawns + { + UniqueEntryMap UniqueEntries; + TileMap TileEntries; + }; + + typedef std::map<uint32, MapSpawns*> MapData; + //=============================================== + + struct GroupModel_Raw + { + uint32 mogpflags; + uint32 GroupWMOID; + + G3D::AABox bounds; + uint32 liquidflags; + std::vector<MeshTriangle> triangles; + std::vector<G3D::Vector3> vertexArray; + class WmoLiquid* liquid; + + GroupModel_Raw() : mogpflags(0), GroupWMOID(0), liquidflags(0), + liquid(NULL) { } + ~GroupModel_Raw(); + + bool Read(FILE* f); + }; + + struct WorldModel_Raw + { + uint32 RootWMOID; + std::vector<GroupModel_Raw> groupsArray; + + bool Read(const char * path); + }; + + class TileAssembler + { + private: + std::string iDestDir; + std::string iSrcDir; + bool (*iFilterMethod)(char *pName); + G3D::Table<std::string, unsigned int > iUniqueNameIds; + unsigned int iCurrentUniqueNameId; + MapData mapData; + std::set<std::string> spawnedModelFiles; + + public: + TileAssembler(const std::string& pSrcDirName, const std::string& pDestDirName); + virtual ~TileAssembler(); + + bool convertWorld2(); + bool readMapSpawns(); + bool calculateTransformedBound(ModelSpawn &spawn); + void exportGameobjectModels(); + + bool convertRawFile(const std::string& pModelFilename); + void setModelNameFilterMethod(bool (*pFilterMethod)(char *pName)) { iFilterMethod = pFilterMethod; } + std::string getDirEntryNameFromModName(unsigned int pMapId, const std::string& pModPosName); + }; + +} // VMAP +#endif /*_TILEASSEMBLER_H_*/ diff --git a/src/common/Collision/Models/GameObjectModel.cpp b/src/common/Collision/Models/GameObjectModel.cpp new file mode 100644 index 00000000000..dbdc0554e06 --- /dev/null +++ b/src/common/Collision/Models/GameObjectModel.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "VMapFactory.h" +#include "VMapManager2.h" +#include "VMapDefinitions.h" +#include "WorldModel.h" +#include "GameObjectModel.h" +#include "Log.h" +#include "Timer.h" + +using G3D::Vector3; +using G3D::Ray; +using G3D::AABox; + +struct GameobjectModelData +{ + GameobjectModelData(const std::string& name_, const AABox& box) : + bound(box), name(name_) { } + + AABox bound; + std::string name; +}; + +typedef std::unordered_map<uint32, GameobjectModelData> ModelList; +ModelList model_list; + +void LoadGameObjectModelList(std::string const& dataPath) +{ +#ifndef NO_CORE_FUNCS + uint32 oldMSTime = getMSTime(); +#endif + + FILE* model_list_file = fopen((dataPath + "vmaps/" + VMAP::GAMEOBJECT_MODELS).c_str(), "rb"); + if (!model_list_file) + { + VMAP_ERROR_LOG("misc", "Unable to open '%s' file.", VMAP::GAMEOBJECT_MODELS); + return; + } + + uint32 name_length, displayId; + char buff[500]; + while (true) + { + Vector3 v1, v2; + if (fread(&displayId, sizeof(uint32), 1, model_list_file) != 1) + if (feof(model_list_file)) // EOF flag is only set after failed reading attempt + break; + + if (fread(&name_length, sizeof(uint32), 1, model_list_file) != 1 + || name_length >= sizeof(buff) + || fread(&buff, sizeof(char), name_length, model_list_file) != name_length + || fread(&v1, sizeof(Vector3), 1, model_list_file) != 1 + || fread(&v2, sizeof(Vector3), 1, model_list_file) != 1) + { + VMAP_ERROR_LOG("misc", "File '%s' seems to be corrupted!", VMAP::GAMEOBJECT_MODELS); + break; + } + + if (v1.isNaN() || v2.isNaN()) + { + VMAP_ERROR_LOG("misc", "File '%s' Model '%s' has invalid v1%s v2%s values!", VMAP::GAMEOBJECT_MODELS, std::string(buff, name_length).c_str(), v1.toString().c_str(), v2.toString().c_str()); + continue; + } + + model_list.insert + ( + ModelList::value_type(displayId, GameobjectModelData(std::string(buff, name_length), AABox(v1, v2))) + ); + } + + fclose(model_list_file); + VMAP_INFO_LOG("server.loading", ">> Loaded %u GameObject models in %u ms", uint32(model_list.size()), GetMSTimeDiffToNow(oldMSTime)); +} + +GameObjectModel::~GameObjectModel() +{ + if (iModel) + ((VMAP::VMapManager2*)VMAP::VMapFactory::createOrGetVMapManager())->releaseModelInstance(name); +} + +bool GameObjectModel::initialize(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath) +{ + ModelList::const_iterator it = model_list.find(modelOwner->GetDisplayId()); + if (it == model_list.end()) + return false; + + G3D::AABox mdl_box(it->second.bound); + // ignore models with no bounds + if (mdl_box == G3D::AABox::zero()) + { + VMAP_ERROR_LOG("misc", "GameObject model %s has zero bounds, loading skipped", it->second.name.c_str()); + return false; + } + + iModel = ((VMAP::VMapManager2*)VMAP::VMapFactory::createOrGetVMapManager())->acquireModelInstance(dataPath + "vmaps/", it->second.name); + + if (!iModel) + return false; + + name = it->second.name; + iPos = modelOwner->GetPosition(); + phasemask = modelOwner->GetPhaseMask(); + iScale = modelOwner->GetScale(); + iInvScale = 1.f / iScale; + + G3D::Matrix3 iRotation = G3D::Matrix3::fromEulerAnglesZYX(modelOwner->GetOrientation(), 0, 0); + iInvRot = iRotation.inverse(); + // transform bounding box: + mdl_box = AABox(mdl_box.low() * iScale, mdl_box.high() * iScale); + AABox rotated_bounds; + for (int i = 0; i < 8; ++i) + rotated_bounds.merge(iRotation * mdl_box.corner(i)); + + iBound = rotated_bounds + iPos; +#ifdef SPAWN_CORNERS + // test: + for (int i = 0; i < 8; ++i) + { + Vector3 pos(iBound.corner(i)); + modelOwner->DebugVisualizeCorner(pos); + } +#endif + + owner = std::move(modelOwner); + return true; +} + +GameObjectModel* GameObjectModel::Create(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath) +{ + GameObjectModel* mdl = new GameObjectModel(); + if (!mdl->initialize(std::move(modelOwner), dataPath)) + { + delete mdl; + return NULL; + } + + return mdl; +} + +bool GameObjectModel::intersectRay(const G3D::Ray& ray, float& MaxDist, bool StopAtFirstHit, uint32 ph_mask) const +{ + if (!(phasemask & ph_mask) || !owner->IsSpawned()) + return false; + + float time = ray.intersectionTime(iBound); + if (time == G3D::finf()) + return false; + + // child bounds are defined in object space: + Vector3 p = iInvRot * (ray.origin() - iPos) * iInvScale; + Ray modRay(p, iInvRot * ray.direction()); + float distance = MaxDist * iInvScale; + bool hit = iModel->IntersectRay(modRay, distance, StopAtFirstHit); + if (hit) + { + distance *= iScale; + MaxDist = distance; + } + return hit; +} + +bool GameObjectModel::UpdatePosition() +{ + if (!iModel) + return false; + + ModelList::const_iterator it = model_list.find(owner->GetDisplayId()); + if (it == model_list.end()) + return false; + + G3D::AABox mdl_box(it->second.bound); + // ignore models with no bounds + if (mdl_box == G3D::AABox::zero()) + { + VMAP_ERROR_LOG("misc", "GameObject model %s has zero bounds, loading skipped", it->second.name.c_str()); + return false; + } + + iPos = owner->GetPosition(); + + G3D::Matrix3 iRotation = G3D::Matrix3::fromEulerAnglesZYX(owner->GetOrientation(), 0, 0); + iInvRot = iRotation.inverse(); + // transform bounding box: + mdl_box = AABox(mdl_box.low() * iScale, mdl_box.high() * iScale); + AABox rotated_bounds; + for (int i = 0; i < 8; ++i) + rotated_bounds.merge(iRotation * mdl_box.corner(i)); + + iBound = rotated_bounds + iPos; +#ifdef SPAWN_CORNERS + // test: + for (int i = 0; i < 8; ++i) + { + Vector3 pos(iBound.corner(i)); + owner->DebugVisualizeCorner(pos); + } +#endif + + return true; +} diff --git a/src/common/Collision/Models/GameObjectModel.h b/src/common/Collision/Models/GameObjectModel.h new file mode 100644 index 00000000000..17669189af5 --- /dev/null +++ b/src/common/Collision/Models/GameObjectModel.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _GAMEOBJECT_MODEL_H +#define _GAMEOBJECT_MODEL_H + +#include <G3D/Matrix3.h> +#include <G3D/Vector3.h> +#include <G3D/AABox.h> +#include <G3D/Ray.h> + +#include "Define.h" +#include <memory> + +namespace VMAP +{ + class WorldModel; +} + +class GameObject; +struct GameObjectDisplayInfoEntry; + +class GameObjectModelOwnerBase +{ +public: + virtual bool IsSpawned() const { return false; } + virtual uint32 GetDisplayId() const { return 0; } + virtual uint32 GetPhaseMask() const { return 0; } + virtual G3D::Vector3 GetPosition() const { return G3D::Vector3::zero(); } + virtual float GetOrientation() const { return 0.0f; } + virtual float GetScale() const { return 1.0f; } + virtual void DebugVisualizeCorner(G3D::Vector3 const& /*corner*/) const { } +}; + +class GameObjectModel /*, public Intersectable*/ +{ + GameObjectModel() : phasemask(0), iInvScale(0), iScale(0), iModel(NULL) { } +public: + std::string name; + + const G3D::AABox& getBounds() const { return iBound; } + + ~GameObjectModel(); + + const G3D::Vector3& getPosition() const { return iPos;} + + /** Enables\disables collision. */ + void disable() { phasemask = 0;} + void enable(uint32 ph_mask) { phasemask = ph_mask;} + + bool isEnabled() const {return phasemask != 0;} + + bool intersectRay(const G3D::Ray& Ray, float& MaxDist, bool StopAtFirstHit, uint32 ph_mask) const; + + static GameObjectModel* Create(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath); + + bool UpdatePosition(); + +private: + bool initialize(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath); + + uint32 phasemask; + G3D::AABox iBound; + G3D::Matrix3 iInvRot; + G3D::Vector3 iPos; + float iInvScale; + float iScale; + VMAP::WorldModel* iModel; + std::unique_ptr<GameObjectModelOwnerBase> owner; +}; + +#endif // _GAMEOBJECT_MODEL_H diff --git a/src/common/Collision/Models/ModelInstance.cpp b/src/common/Collision/Models/ModelInstance.cpp new file mode 100644 index 00000000000..45440a99666 --- /dev/null +++ b/src/common/Collision/Models/ModelInstance.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ModelInstance.h" +#include "WorldModel.h" +#include "MapTree.h" + +using G3D::Vector3; +using G3D::Ray; + +namespace VMAP +{ + ModelInstance::ModelInstance(const ModelSpawn &spawn, WorldModel* model): ModelSpawn(spawn), iModel(model) + { + iInvRot = G3D::Matrix3::fromEulerAnglesZYX(G3D::pif()*iRot.y/180.f, G3D::pif()*iRot.x/180.f, G3D::pif()*iRot.z/180.f).inverse(); + iInvScale = 1.f/iScale; + } + + bool ModelInstance::intersectRay(const G3D::Ray& pRay, float& pMaxDist, bool pStopAtFirstHit) const + { + if (!iModel) + { + //std::cout << "<object not loaded>\n"; + return false; + } + float time = pRay.intersectionTime(iBound); + if (time == G3D::finf()) + { +// std::cout << "Ray does not hit '" << name << "'\n"; + + return false; + } +// std::cout << "Ray crosses bound of '" << name << "'\n"; +/* std::cout << "ray from:" << pRay.origin().x << ", " << pRay.origin().y << ", " << pRay.origin().z + << " dir:" << pRay.direction().x << ", " << pRay.direction().y << ", " << pRay.direction().z + << " t/tmax:" << time << '/' << pMaxDist; + std::cout << "\nBound lo:" << iBound.low().x << ", " << iBound.low().y << ", " << iBound.low().z << " hi: " + << iBound.high().x << ", " << iBound.high().y << ", " << iBound.high().z << std::endl; */ + // child bounds are defined in object space: + Vector3 p = iInvRot * (pRay.origin() - iPos) * iInvScale; + Ray modRay(p, iInvRot * pRay.direction()); + float distance = pMaxDist * iInvScale; + bool hit = iModel->IntersectRay(modRay, distance, pStopAtFirstHit); + if (hit) + { + distance *= iScale; + pMaxDist = distance; + } + return hit; + } + + void ModelInstance::intersectPoint(const G3D::Vector3& p, AreaInfo &info) const + { + if (!iModel) + { +#ifdef VMAP_DEBUG + std::cout << "<object not loaded>\n"; +#endif + return; + } + + // M2 files don't contain area info, only WMO files + if (flags & MOD_M2) + return; + if (!iBound.contains(p)) + return; + // child bounds are defined in object space: + Vector3 pModel = iInvRot * (p - iPos) * iInvScale; + Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f); + float zDist; + if (iModel->IntersectPoint(pModel, zDirModel, zDist, info)) + { + Vector3 modelGround = pModel + zDist * zDirModel; + // Transform back to world space. Note that: + // Mat * vec == vec * Mat.transpose() + // and for rotation matrices: Mat.inverse() == Mat.transpose() + float world_Z = ((modelGround * iInvRot) * iScale + iPos).z; + if (info.ground_Z < world_Z) + { + info.ground_Z = world_Z; + info.adtId = adtId; + } + } + } + + bool ModelInstance::GetLocationInfo(const G3D::Vector3& p, LocationInfo &info) const + { + if (!iModel) + { +#ifdef VMAP_DEBUG + std::cout << "<object not loaded>\n"; +#endif + return false; + } + + // M2 files don't contain area info, only WMO files + if (flags & MOD_M2) + return false; + if (!iBound.contains(p)) + return false; + // child bounds are defined in object space: + Vector3 pModel = iInvRot * (p - iPos) * iInvScale; + Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f); + float zDist; + if (iModel->GetLocationInfo(pModel, zDirModel, zDist, info)) + { + Vector3 modelGround = pModel + zDist * zDirModel; + // Transform back to world space. Note that: + // Mat * vec == vec * Mat.transpose() + // and for rotation matrices: Mat.inverse() == Mat.transpose() + float world_Z = ((modelGround * iInvRot) * iScale + iPos).z; + if (info.ground_Z < world_Z) // hm...could it be handled automatically with zDist at intersection? + { + info.ground_Z = world_Z; + info.hitInstance = this; + return true; + } + } + return false; + } + + bool ModelInstance::GetLiquidLevel(const G3D::Vector3& p, LocationInfo &info, float &liqHeight) const + { + // child bounds are defined in object space: + Vector3 pModel = iInvRot * (p - iPos) * iInvScale; + //Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f); + float zDist; + if (info.hitModel->GetLiquidLevel(pModel, zDist)) + { + // calculate world height (zDist in model coords): + // assume WMO not tilted (wouldn't make much sense anyway) + liqHeight = zDist * iScale + iPos.z; + return true; + } + return false; + } + + bool ModelSpawn::readFromFile(FILE* rf, ModelSpawn &spawn) + { + uint32 check = 0, nameLen; + check += fread(&spawn.flags, sizeof(uint32), 1, rf); + // EoF? + if (!check) + { + if (ferror(rf)) + std::cout << "Error reading ModelSpawn!\n"; + return false; + } + check += fread(&spawn.adtId, sizeof(uint16), 1, rf); + check += fread(&spawn.ID, sizeof(uint32), 1, rf); + check += fread(&spawn.iPos, sizeof(float), 3, rf); + check += fread(&spawn.iRot, sizeof(float), 3, rf); + check += fread(&spawn.iScale, sizeof(float), 1, rf); + bool has_bound = (spawn.flags & MOD_HAS_BOUND) != 0; + if (has_bound) // only WMOs have bound in MPQ, only available after computation + { + Vector3 bLow, bHigh; + check += fread(&bLow, sizeof(float), 3, rf); + check += fread(&bHigh, sizeof(float), 3, rf); + spawn.iBound = G3D::AABox(bLow, bHigh); + } + check += fread(&nameLen, sizeof(uint32), 1, rf); + if (check != uint32(has_bound ? 17 : 11)) + { + std::cout << "Error reading ModelSpawn!\n"; + return false; + } + char nameBuff[500]; + if (nameLen > 500) // file names should never be that long, must be file error + { + std::cout << "Error reading ModelSpawn, file name too long!\n"; + return false; + } + check = fread(nameBuff, sizeof(char), nameLen, rf); + if (check != nameLen) + { + std::cout << "Error reading ModelSpawn!\n"; + return false; + } + spawn.name = std::string(nameBuff, nameLen); + return true; + } + + bool ModelSpawn::writeToFile(FILE* wf, const ModelSpawn &spawn) + { + uint32 check=0; + check += fwrite(&spawn.flags, sizeof(uint32), 1, wf); + check += fwrite(&spawn.adtId, sizeof(uint16), 1, wf); + check += fwrite(&spawn.ID, sizeof(uint32), 1, wf); + check += fwrite(&spawn.iPos, sizeof(float), 3, wf); + check += fwrite(&spawn.iRot, sizeof(float), 3, wf); + check += fwrite(&spawn.iScale, sizeof(float), 1, wf); + bool has_bound = (spawn.flags & MOD_HAS_BOUND) != 0; + if (has_bound) // only WMOs have bound in MPQ, only available after computation + { + check += fwrite(&spawn.iBound.low(), sizeof(float), 3, wf); + check += fwrite(&spawn.iBound.high(), sizeof(float), 3, wf); + } + uint32 nameLen = spawn.name.length(); + check += fwrite(&nameLen, sizeof(uint32), 1, wf); + if (check != uint32(has_bound ? 17 : 11)) return false; + check = fwrite(spawn.name.c_str(), sizeof(char), nameLen, wf); + if (check != nameLen) return false; + return true; + } + +} diff --git a/src/common/Collision/Models/ModelInstance.h b/src/common/Collision/Models/ModelInstance.h new file mode 100644 index 00000000000..f8bbfa4fa73 --- /dev/null +++ b/src/common/Collision/Models/ModelInstance.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _MODELINSTANCE_H_ +#define _MODELINSTANCE_H_ + +#include <G3D/Matrix3.h> +#include <G3D/Vector3.h> +#include <G3D/AABox.h> +#include <G3D/Ray.h> + +#include "Define.h" + +namespace VMAP +{ + class WorldModel; + struct AreaInfo; + struct LocationInfo; + + enum ModelFlags + { + MOD_M2 = 1, + MOD_WORLDSPAWN = 1<<1, + MOD_HAS_BOUND = 1<<2 + }; + + class ModelSpawn + { + public: + //mapID, tileX, tileY, Flags, ID, Pos, Rot, Scale, Bound_lo, Bound_hi, name + uint32 flags; + uint16 adtId; + uint32 ID; + G3D::Vector3 iPos; + G3D::Vector3 iRot; + float iScale; + G3D::AABox iBound; + std::string name; + bool operator==(const ModelSpawn &other) const { return ID == other.ID; } + //uint32 hashCode() const { return ID; } + // temp? + const G3D::AABox& getBounds() const { return iBound; } + + static bool readFromFile(FILE* rf, ModelSpawn &spawn); + static bool writeToFile(FILE* rw, const ModelSpawn &spawn); + }; + + class ModelInstance: public ModelSpawn + { + public: + ModelInstance(): iInvScale(0.0f), iModel(nullptr) { } + ModelInstance(const ModelSpawn &spawn, WorldModel* model); + void setUnloaded() { iModel = nullptr; } + bool intersectRay(const G3D::Ray& pRay, float& pMaxDist, bool pStopAtFirstHit) const; + void intersectPoint(const G3D::Vector3& p, AreaInfo &info) const; + bool GetLocationInfo(const G3D::Vector3& p, LocationInfo &info) const; + bool GetLiquidLevel(const G3D::Vector3& p, LocationInfo &info, float &liqHeight) const; + WorldModel* getWorldModel() { return iModel; } + protected: + G3D::Matrix3 iInvRot; + float iInvScale; + WorldModel* iModel; + }; +} // namespace VMAP + +#endif // _MODELINSTANCE diff --git a/src/common/Collision/Models/WorldModel.cpp b/src/common/Collision/Models/WorldModel.cpp new file mode 100644 index 00000000000..7f4d76b244a --- /dev/null +++ b/src/common/Collision/Models/WorldModel.cpp @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "WorldModel.h" +#include "VMapDefinitions.h" +#include "MapTree.h" + +using G3D::Vector3; +using G3D::Ray; + +template<> struct BoundsTrait<VMAP::GroupModel> +{ + static void getBounds(const VMAP::GroupModel& obj, G3D::AABox& out) { out = obj.GetBound(); } +}; + +namespace VMAP +{ + bool IntersectTriangle(const MeshTriangle &tri, std::vector<Vector3>::const_iterator points, const G3D::Ray &ray, float &distance) + { + static const float EPS = 1e-5f; + + // See RTR2 ch. 13.7 for the algorithm. + + const Vector3 e1 = points[tri.idx1] - points[tri.idx0]; + const Vector3 e2 = points[tri.idx2] - points[tri.idx0]; + const Vector3 p(ray.direction().cross(e2)); + const float a = e1.dot(p); + + if (std::fabs(a) < EPS) { + // Determinant is ill-conditioned; abort early + return false; + } + + const float f = 1.0f / a; + const Vector3 s(ray.origin() - points[tri.idx0]); + const float u = f * s.dot(p); + + if ((u < 0.0f) || (u > 1.0f)) { + // We hit the plane of the m_geometry, but outside the m_geometry + return false; + } + + const Vector3 q(s.cross(e1)); + const float v = f * ray.direction().dot(q); + + if ((v < 0.0f) || ((u + v) > 1.0f)) { + // We hit the plane of the triangle, but outside the triangle + return false; + } + + const float t = f * e2.dot(q); + + if ((t > 0.0f) && (t < distance)) + { + // This is a new hit, closer than the previous one + distance = t; + + /* baryCoord[0] = 1.0 - u - v; + baryCoord[1] = u; + baryCoord[2] = v; */ + + return true; + } + // This hit is after the previous hit, so ignore it + return false; + } + + class TriBoundFunc + { + public: + TriBoundFunc(std::vector<Vector3> &vert): vertices(vert.begin()) { } + void operator()(const MeshTriangle &tri, G3D::AABox &out) const + { + G3D::Vector3 lo = vertices[tri.idx0]; + G3D::Vector3 hi = lo; + + lo = (lo.min(vertices[tri.idx1])).min(vertices[tri.idx2]); + hi = (hi.max(vertices[tri.idx1])).max(vertices[tri.idx2]); + + out = G3D::AABox(lo, hi); + } + protected: + const std::vector<Vector3>::const_iterator vertices; + }; + + // ===================== WmoLiquid ================================== + + WmoLiquid::WmoLiquid(uint32 width, uint32 height, const Vector3 &corner, uint32 type): + iTilesX(width), iTilesY(height), iCorner(corner), iType(type) + { + iHeight = new float[(width+1)*(height+1)]; + iFlags = new uint8[width*height]; + } + + WmoLiquid::WmoLiquid(const WmoLiquid &other): iHeight(nullptr), iFlags(nullptr) + { + *this = other; // use assignment operator... + } + + WmoLiquid::~WmoLiquid() + { + delete[] iHeight; + delete[] iFlags; + } + + WmoLiquid& WmoLiquid::operator=(const WmoLiquid &other) + { + if (this == &other) + return *this; + iTilesX = other.iTilesX; + iTilesY = other.iTilesY; + iCorner = other.iCorner; + iType = other.iType; + delete iHeight; + delete iFlags; + if (other.iHeight) + { + iHeight = new float[(iTilesX+1)*(iTilesY+1)]; + memcpy(iHeight, other.iHeight, (iTilesX+1)*(iTilesY+1)*sizeof(float)); + } + else + iHeight = nullptr; + if (other.iFlags) + { + iFlags = new uint8[iTilesX * iTilesY]; + memcpy(iFlags, other.iFlags, iTilesX * iTilesY); + } + else + iFlags = nullptr; + return *this; + } + + bool WmoLiquid::GetLiquidHeight(const Vector3 &pos, float &liqHeight) const + { + float tx_f = (pos.x - iCorner.x)/LIQUID_TILE_SIZE; + uint32 tx = uint32(tx_f); + if (tx_f < 0.0f || tx >= iTilesX) + return false; + float ty_f = (pos.y - iCorner.y)/LIQUID_TILE_SIZE; + uint32 ty = uint32(ty_f); + if (ty_f < 0.0f || ty >= iTilesY) + return false; + + // check if tile shall be used for liquid level + // checking for 0x08 *might* be enough, but disabled tiles always are 0x?F: + if ((iFlags[tx + ty*iTilesX] & 0x0F) == 0x0F) + return false; + + // (dx, dy) coordinates inside tile, in [0, 1]^2 + float dx = tx_f - (float)tx; + float dy = ty_f - (float)ty; + + /* Tesselate tile to two triangles (not sure if client does it exactly like this) + + ^ dy + | + 1 x---------x (1, 1) + | (b) / | + | / | + | / | + | / (a) | + x---------x---> dx + 0 1 + */ + + const uint32 rowOffset = iTilesX + 1; + if (dx > dy) // case (a) + { + float sx = iHeight[tx+1 + ty * rowOffset] - iHeight[tx + ty * rowOffset]; + float sy = iHeight[tx+1 + (ty+1) * rowOffset] - iHeight[tx+1 + ty * rowOffset]; + liqHeight = iHeight[tx + ty * rowOffset] + dx * sx + dy * sy; + } + else // case (b) + { + float sx = iHeight[tx+1 + (ty+1) * rowOffset] - iHeight[tx + (ty+1) * rowOffset]; + float sy = iHeight[tx + (ty+1) * rowOffset] - iHeight[tx + ty * rowOffset]; + liqHeight = iHeight[tx + ty * rowOffset] + dx * sx + dy * sy; + } + return true; + } + + uint32 WmoLiquid::GetFileSize() + { + return 2 * sizeof(uint32) + + sizeof(Vector3) + + (iTilesX + 1)*(iTilesY + 1) * sizeof(float) + + iTilesX * iTilesY; + } + + bool WmoLiquid::writeToFile(FILE* wf) + { + bool result = false; + if (fwrite(&iTilesX, sizeof(uint32), 1, wf) == 1 && + fwrite(&iTilesY, sizeof(uint32), 1, wf) == 1 && + fwrite(&iCorner, sizeof(Vector3), 1, wf) == 1 && + fwrite(&iType, sizeof(uint32), 1, wf) == 1) + { + uint32 size = (iTilesX + 1) * (iTilesY + 1); + if (fwrite(iHeight, sizeof(float), size, wf) == size) + { + size = iTilesX*iTilesY; + result = fwrite(iFlags, sizeof(uint8), size, wf) == size; + } + } + + return result; + } + + bool WmoLiquid::readFromFile(FILE* rf, WmoLiquid* &out) + { + bool result = false; + WmoLiquid* liquid = new WmoLiquid(); + + if (fread(&liquid->iTilesX, sizeof(uint32), 1, rf) == 1 && + fread(&liquid->iTilesY, sizeof(uint32), 1, rf) == 1 && + fread(&liquid->iCorner, sizeof(Vector3), 1, rf) == 1 && + fread(&liquid->iType, sizeof(uint32), 1, rf) == 1) + { + uint32 size = (liquid->iTilesX + 1) * (liquid->iTilesY + 1); + liquid->iHeight = new float[size]; + if (fread(liquid->iHeight, sizeof(float), size, rf) == size) + { + size = liquid->iTilesX * liquid->iTilesY; + liquid->iFlags = new uint8[size]; + result = fread(liquid->iFlags, sizeof(uint8), size, rf) == size; + } + } + + if (!result) + delete liquid; + else + out = liquid; + + return result; + } + + void WmoLiquid::getPosInfo(uint32 &tilesX, uint32 &tilesY, G3D::Vector3 &corner) const + { + tilesX = iTilesX; + tilesY = iTilesY; + corner = iCorner; + } + + // ===================== GroupModel ================================== + + GroupModel::GroupModel(const GroupModel &other): + iBound(other.iBound), iMogpFlags(other.iMogpFlags), iGroupWMOID(other.iGroupWMOID), + vertices(other.vertices), triangles(other.triangles), meshTree(other.meshTree), iLiquid(nullptr) + { + if (other.iLiquid) + iLiquid = new WmoLiquid(*other.iLiquid); + } + + void GroupModel::setMeshData(std::vector<Vector3> &vert, std::vector<MeshTriangle> &tri) + { + vertices.swap(vert); + triangles.swap(tri); + TriBoundFunc bFunc(vertices); + meshTree.build(triangles, bFunc); + } + + bool GroupModel::writeToFile(FILE* wf) + { + bool result = true; + uint32 chunkSize, count; + + if (result && fwrite(&iBound, sizeof(G3D::AABox), 1, wf) != 1) result = false; + if (result && fwrite(&iMogpFlags, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&iGroupWMOID, sizeof(uint32), 1, wf) != 1) result = false; + + // write vertices + if (result && fwrite("VERT", 1, 4, wf) != 4) result = false; + count = vertices.size(); + chunkSize = sizeof(uint32)+ sizeof(Vector3)*count; + if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&count, sizeof(uint32), 1, wf) != 1) result = false; + if (!count) // models without (collision) geometry end here, unsure if they are useful + return result; + if (result && fwrite(&vertices[0], sizeof(Vector3), count, wf) != count) result = false; + + // write triangle mesh + if (result && fwrite("TRIM", 1, 4, wf) != 4) result = false; + count = triangles.size(); + chunkSize = sizeof(uint32)+ sizeof(MeshTriangle)*count; + if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&count, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&triangles[0], sizeof(MeshTriangle), count, wf) != count) result = false; + + // write mesh BIH + if (result && fwrite("MBIH", 1, 4, wf) != 4) result = false; + if (result) result = meshTree.writeToFile(wf); + + // write liquid data + if (result && fwrite("LIQU", 1, 4, wf) != 4) result = false; + if (!iLiquid) + { + chunkSize = 0; + if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false; + return result; + } + chunkSize = iLiquid->GetFileSize(); + if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false; + if (result) result = iLiquid->writeToFile(wf); + + return result; + } + + bool GroupModel::readFromFile(FILE* rf) + { + char chunk[8]; + bool result = true; + uint32 chunkSize = 0; + uint32 count = 0; + triangles.clear(); + vertices.clear(); + delete iLiquid; + iLiquid = NULL; + + if (result && fread(&iBound, sizeof(G3D::AABox), 1, rf) != 1) result = false; + if (result && fread(&iMogpFlags, sizeof(uint32), 1, rf) != 1) result = false; + if (result && fread(&iGroupWMOID, sizeof(uint32), 1, rf) != 1) result = false; + + // read vertices + if (result && !readChunk(rf, chunk, "VERT", 4)) result = false; + if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false; + if (result && fread(&count, sizeof(uint32), 1, rf) != 1) result = false; + if (!count) // models without (collision) geometry end here, unsure if they are useful + return result; + if (result) vertices.resize(count); + if (result && fread(&vertices[0], sizeof(Vector3), count, rf) != count) result = false; + + // read triangle mesh + if (result && !readChunk(rf, chunk, "TRIM", 4)) result = false; + if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false; + if (result && fread(&count, sizeof(uint32), 1, rf) != 1) result = false; + if (result) triangles.resize(count); + if (result && fread(&triangles[0], sizeof(MeshTriangle), count, rf) != count) result = false; + + // read mesh BIH + if (result && !readChunk(rf, chunk, "MBIH", 4)) result = false; + if (result) result = meshTree.readFromFile(rf); + + // write liquid data + if (result && !readChunk(rf, chunk, "LIQU", 4)) result = false; + if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false; + if (result && chunkSize > 0) + result = WmoLiquid::readFromFile(rf, iLiquid); + return result; + } + + struct GModelRayCallback + { + GModelRayCallback(const std::vector<MeshTriangle> &tris, const std::vector<Vector3> &vert): + vertices(vert.begin()), triangles(tris.begin()), hit(false) { } + bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool /*pStopAtFirstHit*/) + { + bool result = IntersectTriangle(triangles[entry], vertices, ray, distance); + if (result) hit=true; + return hit; + } + std::vector<Vector3>::const_iterator vertices; + std::vector<MeshTriangle>::const_iterator triangles; + bool hit; + }; + + bool GroupModel::IntersectRay(const G3D::Ray &ray, float &distance, bool stopAtFirstHit) const + { + if (triangles.empty()) + return false; + + GModelRayCallback callback(triangles, vertices); + meshTree.intersectRay(ray, callback, distance, stopAtFirstHit); + return callback.hit; + } + + bool GroupModel::IsInsideObject(const Vector3 &pos, const Vector3 &down, float &z_dist) const + { + if (triangles.empty() || !iBound.contains(pos)) + return false; + GModelRayCallback callback(triangles, vertices); + Vector3 rPos = pos - 0.1f * down; + float dist = G3D::finf(); + G3D::Ray ray(rPos, down); + bool hit = IntersectRay(ray, dist, false); + if (hit) + z_dist = dist - 0.1f; + return hit; + } + + bool GroupModel::GetLiquidLevel(const Vector3 &pos, float &liqHeight) const + { + if (iLiquid) + return iLiquid->GetLiquidHeight(pos, liqHeight); + return false; + } + + uint32 GroupModel::GetLiquidType() const + { + if (iLiquid) + return iLiquid->GetType(); + return 0; + } + + void GroupModel::getMeshData(std::vector<G3D::Vector3>& outVertices, std::vector<MeshTriangle>& outTriangles, WmoLiquid*& liquid) + { + outVertices = vertices; + outTriangles = triangles; + liquid = iLiquid; + } + + // ===================== WorldModel ================================== + + void WorldModel::setGroupModels(std::vector<GroupModel> &models) + { + groupModels.swap(models); + groupTree.build(groupModels, BoundsTrait<GroupModel>::getBounds, 1); + } + + struct WModelRayCallBack + { + WModelRayCallBack(const std::vector<GroupModel> &mod): models(mod.begin()), hit(false) { } + bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool pStopAtFirstHit) + { + bool result = models[entry].IntersectRay(ray, distance, pStopAtFirstHit); + if (result) hit=true; + return hit; + } + std::vector<GroupModel>::const_iterator models; + bool hit; + }; + + bool WorldModel::IntersectRay(const G3D::Ray &ray, float &distance, bool stopAtFirstHit) const + { + // small M2 workaround, maybe better make separate class with virtual intersection funcs + // in any case, there's no need to use a bound tree if we only have one submodel + if (groupModels.size() == 1) + return groupModels[0].IntersectRay(ray, distance, stopAtFirstHit); + + WModelRayCallBack isc(groupModels); + groupTree.intersectRay(ray, isc, distance, stopAtFirstHit); + return isc.hit; + } + + class WModelAreaCallback { + public: + WModelAreaCallback(const std::vector<GroupModel> &vals, const Vector3 &down): + prims(vals.begin()), hit(vals.end()), minVol(G3D::finf()), zDist(G3D::finf()), zVec(down) { } + std::vector<GroupModel>::const_iterator prims; + std::vector<GroupModel>::const_iterator hit; + float minVol; + float zDist; + Vector3 zVec; + void operator()(const Vector3& point, uint32 entry) + { + float group_Z; + //float pVol = prims[entry].GetBound().volume(); + //if (pVol < minVol) + //{ + /* if (prims[entry].iBound.contains(point)) */ + if (prims[entry].IsInsideObject(point, zVec, group_Z)) + { + //minVol = pVol; + //hit = prims + entry; + if (group_Z < zDist) + { + zDist = group_Z; + hit = prims + entry; + } +#ifdef VMAP_DEBUG + const GroupModel &gm = prims[entry]; + printf("%10u %8X %7.3f, %7.3f, %7.3f | %7.3f, %7.3f, %7.3f | z=%f, p_z=%f\n", gm.GetWmoID(), gm.GetMogpFlags(), + gm.GetBound().low().x, gm.GetBound().low().y, gm.GetBound().low().z, + gm.GetBound().high().x, gm.GetBound().high().y, gm.GetBound().high().z, group_Z, point.z); +#endif + } + //} + //std::cout << "trying to intersect '" << prims[entry].name << "'\n"; + } + }; + + bool WorldModel::IntersectPoint(const G3D::Vector3 &p, const G3D::Vector3 &down, float &dist, AreaInfo &info) const + { + if (groupModels.empty()) + return false; + + WModelAreaCallback callback(groupModels, down); + groupTree.intersectPoint(p, callback); + if (callback.hit != groupModels.end()) + { + info.rootId = RootWMOID; + info.groupId = callback.hit->GetWmoID(); + info.flags = callback.hit->GetMogpFlags(); + info.result = true; + dist = callback.zDist; + return true; + } + return false; + } + + bool WorldModel::GetLocationInfo(const G3D::Vector3 &p, const G3D::Vector3 &down, float &dist, LocationInfo &info) const + { + if (groupModels.empty()) + return false; + + WModelAreaCallback callback(groupModels, down); + groupTree.intersectPoint(p, callback); + if (callback.hit != groupModels.end()) + { + info.hitModel = &(*callback.hit); + dist = callback.zDist; + return true; + } + return false; + } + + bool WorldModel::writeFile(const std::string &filename) + { + FILE* wf = fopen(filename.c_str(), "wb"); + if (!wf) + return false; + + uint32 chunkSize, count; + bool result = fwrite(VMAP_MAGIC, 1, 8, wf) == 8; + if (result && fwrite("WMOD", 1, 4, wf) != 4) result = false; + chunkSize = sizeof(uint32) + sizeof(uint32); + if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&RootWMOID, sizeof(uint32), 1, wf) != 1) result = false; + + // write group models + count=groupModels.size(); + if (count) + { + if (result && fwrite("GMOD", 1, 4, wf) != 4) result = false; + //chunkSize = sizeof(uint32)+ sizeof(GroupModel)*count; + //if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&count, sizeof(uint32), 1, wf) != 1) result = false; + for (uint32 i=0; i<groupModels.size() && result; ++i) + result = groupModels[i].writeToFile(wf); + + // write group BIH + if (result && fwrite("GBIH", 1, 4, wf) != 4) result = false; + if (result) result = groupTree.writeToFile(wf); + } + + fclose(wf); + return result; + } + + bool WorldModel::readFile(const std::string &filename) + { + FILE* rf = fopen(filename.c_str(), "rb"); + if (!rf) + return false; + + bool result = true; + uint32 chunkSize = 0; + uint32 count = 0; + char chunk[8]; // Ignore the added magic header + if (!readChunk(rf, chunk, VMAP_MAGIC, 8)) result = false; + + if (result && !readChunk(rf, chunk, "WMOD", 4)) result = false; + if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false; + if (result && fread(&RootWMOID, sizeof(uint32), 1, rf) != 1) result = false; + + // read group models + if (result && readChunk(rf, chunk, "GMOD", 4)) + { + //if (fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false; + + if (result && fread(&count, sizeof(uint32), 1, rf) != 1) result = false; + if (result) groupModels.resize(count); + //if (result && fread(&groupModels[0], sizeof(GroupModel), count, rf) != count) result = false; + for (uint32 i=0; i<count && result; ++i) + result = groupModels[i].readFromFile(rf); + + // read group BIH + if (result && !readChunk(rf, chunk, "GBIH", 4)) result = false; + if (result) result = groupTree.readFromFile(rf); + } + + fclose(rf); + return result; + } + + void WorldModel::getGroupModels(std::vector<GroupModel>& outGroupModels) + { + outGroupModels = groupModels; + } +} diff --git a/src/common/Collision/Models/WorldModel.h b/src/common/Collision/Models/WorldModel.h new file mode 100644 index 00000000000..afa9d15b264 --- /dev/null +++ b/src/common/Collision/Models/WorldModel.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _WORLDMODEL_H +#define _WORLDMODEL_H + +#include <G3D/HashTrait.h> +#include <G3D/Vector3.h> +#include <G3D/AABox.h> +#include <G3D/Ray.h> +#include "BoundingIntervalHierarchy.h" + +#include "Define.h" + +namespace VMAP +{ + class TreeNode; + struct AreaInfo; + struct LocationInfo; + + class MeshTriangle + { + public: + MeshTriangle() : idx0(0), idx1(0), idx2(0) { } + MeshTriangle(uint32 na, uint32 nb, uint32 nc): idx0(na), idx1(nb), idx2(nc) { } + + uint32 idx0; + uint32 idx1; + uint32 idx2; + }; + + class WmoLiquid + { + public: + WmoLiquid(uint32 width, uint32 height, const G3D::Vector3 &corner, uint32 type); + WmoLiquid(const WmoLiquid &other); + ~WmoLiquid(); + WmoLiquid& operator=(const WmoLiquid &other); + bool GetLiquidHeight(const G3D::Vector3 &pos, float &liqHeight) const; + uint32 GetType() const { return iType; } + float *GetHeightStorage() { return iHeight; } + uint8 *GetFlagsStorage() { return iFlags; } + uint32 GetFileSize(); + bool writeToFile(FILE* wf); + static bool readFromFile(FILE* rf, WmoLiquid* &liquid); + void getPosInfo(uint32 &tilesX, uint32 &tilesY, G3D::Vector3 &corner) const; + private: + WmoLiquid() : iTilesX(0), iTilesY(0), iCorner(), iType(0), iHeight(NULL), iFlags(NULL) { } + uint32 iTilesX; //!< number of tiles in x direction, each + uint32 iTilesY; + G3D::Vector3 iCorner; //!< the lower corner + uint32 iType; //!< liquid type + float *iHeight; //!< (tilesX + 1)*(tilesY + 1) height values + uint8 *iFlags; //!< info if liquid tile is used + }; + + /*! holding additional info for WMO group files */ + class GroupModel + { + public: + GroupModel() : iBound(), iMogpFlags(0), iGroupWMOID(0), iLiquid(NULL) { } + GroupModel(const GroupModel &other); + GroupModel(uint32 mogpFlags, uint32 groupWMOID, const G3D::AABox &bound): + iBound(bound), iMogpFlags(mogpFlags), iGroupWMOID(groupWMOID), iLiquid(NULL) { } + ~GroupModel() { delete iLiquid; } + + //! pass mesh data to object and create BIH. Passed vectors get get swapped with old geometry! + void setMeshData(std::vector<G3D::Vector3> &vert, std::vector<MeshTriangle> &tri); + void setLiquidData(WmoLiquid*& liquid) { iLiquid = liquid; liquid = NULL; } + bool IntersectRay(const G3D::Ray &ray, float &distance, bool stopAtFirstHit) const; + bool IsInsideObject(const G3D::Vector3 &pos, const G3D::Vector3 &down, float &z_dist) const; + bool GetLiquidLevel(const G3D::Vector3 &pos, float &liqHeight) const; + uint32 GetLiquidType() const; + bool writeToFile(FILE* wf); + bool readFromFile(FILE* rf); + const G3D::AABox& GetBound() const { return iBound; } + uint32 GetMogpFlags() const { return iMogpFlags; } + uint32 GetWmoID() const { return iGroupWMOID; } + void getMeshData(std::vector<G3D::Vector3>& outVertices, std::vector<MeshTriangle>& outTriangles, WmoLiquid*& liquid); + protected: + G3D::AABox iBound; + uint32 iMogpFlags;// 0x8 outdor; 0x2000 indoor + uint32 iGroupWMOID; + std::vector<G3D::Vector3> vertices; + std::vector<MeshTriangle> triangles; + BIH meshTree; + WmoLiquid* iLiquid; + }; + + /*! Holds a model (converted M2 or WMO) in its original coordinate space */ + class WorldModel + { + public: + WorldModel(): RootWMOID(0) { } + + //! pass group models to WorldModel and create BIH. Passed vector is swapped with old geometry! + void setGroupModels(std::vector<GroupModel> &models); + void setRootWmoID(uint32 id) { RootWMOID = id; } + bool IntersectRay(const G3D::Ray &ray, float &distance, bool stopAtFirstHit) const; + bool IntersectPoint(const G3D::Vector3 &p, const G3D::Vector3 &down, float &dist, AreaInfo &info) const; + bool GetLocationInfo(const G3D::Vector3 &p, const G3D::Vector3 &down, float &dist, LocationInfo &info) const; + bool writeFile(const std::string &filename); + bool readFile(const std::string &filename); + void getGroupModels(std::vector<GroupModel>& outGroupModels); + protected: + uint32 RootWMOID; + std::vector<GroupModel> groupModels; + BIH groupTree; + }; +} // namespace VMAP + +#endif // _WORLDMODEL_H diff --git a/src/common/Collision/RegularGrid.h b/src/common/Collision/RegularGrid.h new file mode 100644 index 00000000000..6a2a07968ad --- /dev/null +++ b/src/common/Collision/RegularGrid.h @@ -0,0 +1,212 @@ +#ifndef _REGULAR_GRID_H +#define _REGULAR_GRID_H + + +#include <G3D/Ray.h> +#include <G3D/Table.h> +#include <G3D/BoundsTrait.h> +#include <G3D/PositionTrait.h> + +#include "Errors.h" + +template<class Node> +struct NodeCreator{ + static Node * makeNode(int /*x*/, int /*y*/) { return new Node();} +}; + +template<class T, +class Node, +class NodeCreatorFunc = NodeCreator<Node>, + /*class BoundsFunc = BoundsTrait<T>,*/ +class PositionFunc = PositionTrait<T> +> +class RegularGrid2D +{ +public: + + enum{ + CELL_NUMBER = 64, + }; + + #define HGRID_MAP_SIZE (533.33333f * 64.f) // shouldn't be changed + #define CELL_SIZE float(HGRID_MAP_SIZE/(float)CELL_NUMBER) + + typedef G3D::Table<const T*, Node*> MemberTable; + + MemberTable memberTable; + Node* nodes[CELL_NUMBER][CELL_NUMBER]; + + RegularGrid2D(){ + memset(nodes, 0, sizeof(nodes)); + } + + ~RegularGrid2D(){ + for (int x = 0; x < CELL_NUMBER; ++x) + for (int y = 0; y < CELL_NUMBER; ++y) + delete nodes[x][y]; + } + + void insert(const T& value) + { + G3D::Vector3 pos; + PositionFunc::getPosition(value, pos); + Node& node = getGridFor(pos.x, pos.y); + node.insert(value); + memberTable.set(&value, &node); + } + + void remove(const T& value) + { + memberTable[&value]->remove(value); + // Remove the member + memberTable.remove(&value); + } + + void balance() + { + for (int x = 0; x < CELL_NUMBER; ++x) + for (int y = 0; y < CELL_NUMBER; ++y) + if (Node* n = nodes[x][y]) + n->balance(); + } + + bool contains(const T& value) const { return memberTable.containsKey(&value); } + int size() const { return memberTable.size(); } + + struct Cell + { + int x, y; + bool operator == (const Cell& c2) const { return x == c2.x && y == c2.y;} + + static Cell ComputeCell(float fx, float fy) + { + Cell c = { int(fx * (1.f/CELL_SIZE) + (CELL_NUMBER/2)), int(fy * (1.f/CELL_SIZE) + (CELL_NUMBER/2)) }; + return c; + } + + bool isValid() const { return x >= 0 && x < CELL_NUMBER && y >= 0 && y < CELL_NUMBER;} + }; + + + Node& getGridFor(float fx, float fy) + { + Cell c = Cell::ComputeCell(fx, fy); + return getGrid(c.x, c.y); + } + + Node& getGrid(int x, int y) + { + ASSERT(x < CELL_NUMBER && y < CELL_NUMBER); + if (!nodes[x][y]) + nodes[x][y] = NodeCreatorFunc::makeNode(x, y); + return *nodes[x][y]; + } + + template<typename RayCallback> + void intersectRay(const G3D::Ray& ray, RayCallback& intersectCallback, float max_dist) + { + intersectRay(ray, intersectCallback, max_dist, ray.origin() + ray.direction() * max_dist); + } + + template<typename RayCallback> + void intersectRay(const G3D::Ray& ray, RayCallback& intersectCallback, float& max_dist, const G3D::Vector3& end) + { + Cell cell = Cell::ComputeCell(ray.origin().x, ray.origin().y); + if (!cell.isValid()) + return; + + Cell last_cell = Cell::ComputeCell(end.x, end.y); + + if (cell == last_cell) + { + if (Node* node = nodes[cell.x][cell.y]) + node->intersectRay(ray, intersectCallback, max_dist); + return; + } + + float voxel = (float)CELL_SIZE; + float kx_inv = ray.invDirection().x, bx = ray.origin().x; + float ky_inv = ray.invDirection().y, by = ray.origin().y; + + int stepX, stepY; + float tMaxX, tMaxY; + if (kx_inv >= 0) + { + stepX = 1; + float x_border = (cell.x+1) * voxel; + tMaxX = (x_border - bx) * kx_inv; + } + else + { + stepX = -1; + float x_border = (cell.x-1) * voxel; + tMaxX = (x_border - bx) * kx_inv; + } + + if (ky_inv >= 0) + { + stepY = 1; + float y_border = (cell.y+1) * voxel; + tMaxY = (y_border - by) * ky_inv; + } + else + { + stepY = -1; + float y_border = (cell.y-1) * voxel; + tMaxY = (y_border - by) * ky_inv; + } + + //int Cycles = std::max((int)ceilf(max_dist/tMaxX),(int)ceilf(max_dist/tMaxY)); + //int i = 0; + + float tDeltaX = voxel * std::fabs(kx_inv); + float tDeltaY = voxel * std::fabs(ky_inv); + do + { + if (Node* node = nodes[cell.x][cell.y]) + { + //float enterdist = max_dist; + node->intersectRay(ray, intersectCallback, max_dist); + } + if (cell == last_cell) + break; + if (tMaxX < tMaxY) + { + tMaxX += tDeltaX; + cell.x += stepX; + } + else + { + tMaxY += tDeltaY; + cell.y += stepY; + } + //++i; + } while (cell.isValid()); + } + + template<typename IsectCallback> + void intersectPoint(const G3D::Vector3& point, IsectCallback& intersectCallback) + { + Cell cell = Cell::ComputeCell(point.x, point.y); + if (!cell.isValid()) + return; + if (Node* node = nodes[cell.x][cell.y]) + node->intersectPoint(point, intersectCallback); + } + + // Optimized verson of intersectRay function for rays with vertical directions + template<typename RayCallback> + void intersectZAllignedRay(const G3D::Ray& ray, RayCallback& intersectCallback, float& max_dist) + { + Cell cell = Cell::ComputeCell(ray.origin().x, ray.origin().y); + if (!cell.isValid()) + return; + if (Node* node = nodes[cell.x][cell.y]) + node->intersectRay(ray, intersectCallback, max_dist); + } +}; + +#undef CELL_SIZE +#undef HGRID_MAP_SIZE + +#endif diff --git a/src/common/Collision/VMapDefinitions.h b/src/common/Collision/VMapDefinitions.h new file mode 100644 index 00000000000..1035d5307cc --- /dev/null +++ b/src/common/Collision/VMapDefinitions.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _VMAPDEFINITIONS_H +#define _VMAPDEFINITIONS_H +#include <cstring> +#include <cstdio> + +#define LIQUID_TILE_SIZE (533.333f / 128.f) + +namespace VMAP +{ + const char VMAP_MAGIC[] = "VMAP_4.1"; + const char RAW_VMAP_MAGIC[] = "VMAP041"; // used in extracted vmap files with raw data + const char GAMEOBJECT_MODELS[] = "GameObjectModels.dtree"; + + // defined in TileAssembler.cpp currently... + bool readChunk(FILE* rf, char *dest, const char *compare, uint32 len); +} + +// Set of helper macros for extractors (VMAP and MMAP) +#ifndef NO_CORE_FUNCS +#define VMAP_ERROR_LOG(FILTER, ...) TC_LOG_ERROR(FILTER, __VA_ARGS__) +#define VMAP_DEBUG_LOG(FILTER, ...) TC_LOG_DEBUG(FILTER, __VA_ARGS__) +#define VMAP_INFO_LOG(FILTER, ...) TC_LOG_INFO(FILTER, __VA_ARGS__) +#else +#define VMAP_ERROR_LOG(FILTER, ...) (void)sizeof(FILTER) +#define VMAP_DEBUG_LOG(FILTER, ...) (void)sizeof(FILTER) +#define VMAP_INFO_LOG(FILTER, ...) (void)sizeof(FILTER) +#endif + +#endif diff --git a/src/common/Collision/VMapTools.h b/src/common/Collision/VMapTools.h new file mode 100644 index 00000000000..662dc43e9e7 --- /dev/null +++ b/src/common/Collision/VMapTools.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _VMAPTOOLS_H +#define _VMAPTOOLS_H + +#include <G3D/CollisionDetection.h> +#include <G3D/AABox.h> + +/** +The Class is mainly taken from G3D/AABSPTree.h but modified to be able to use our internal data structure. +This is an iterator that helps us analysing the BSP-Trees. +The collision detection is modified to return true, if we are inside an object. +*/ + +namespace VMAP +{ + template<class TValue> + class IntersectionCallBack { + public: + TValue* closestEntity; + G3D::Vector3 hitLocation; + G3D::Vector3 hitNormal; + + void operator()(const G3D::Ray& ray, const TValue* entity, bool pStopAtFirstHit, float& distance) { + entity->intersect(ray, distance, pStopAtFirstHit, hitLocation, hitNormal); + } + }; + + //============================================================== + //============================================================== + //============================================================== + + class MyCollisionDetection + { + private: + public: + + static bool collisionLocationForMovingPointFixedAABox( + const G3D::Vector3& origin, + const G3D::Vector3& dir, + const G3D::AABox& box, + G3D::Vector3& location, + bool& Inside) + { + + // Integer representation of a floating-point value. +#define IR(x) (reinterpret_cast<G3D::uint32 const&>(x)) + + Inside = true; + const G3D::Vector3& MinB = box.low(); + const G3D::Vector3& MaxB = box.high(); + G3D::Vector3 MaxT(-1.0f, -1.0f, -1.0f); + + // Find candidate planes. + for (int i = 0; i < 3; ++i) + { + if (origin[i] < MinB[i]) + { + location[i] = MinB[i]; + Inside = false; + + // Calculate T distances to candidate planes + if (IR(dir[i])) + { + MaxT[i] = (MinB[i] - origin[i]) / dir[i]; + } + } + else if (origin[i] > MaxB[i]) + { + location[i] = MaxB[i]; + Inside = false; + + // Calculate T distances to candidate planes + if (IR(dir[i])) + { + MaxT[i] = (MaxB[i] - origin[i]) / dir[i]; + } + } + } + + if (Inside) + { + // definite hit + location = origin; + return true; + } + + // Get largest of the maxT's for final choice of intersection + int WhichPlane = 0; + if (MaxT[1] > MaxT[WhichPlane]) + { + WhichPlane = 1; + } + + if (MaxT[2] > MaxT[WhichPlane]) + { + WhichPlane = 2; + } + + // Check final candidate actually inside box + if (IR(MaxT[WhichPlane]) & 0x80000000) + { + // Miss the box + return false; + } + + for (int i = 0; i < 3; ++i) + { + if (i != WhichPlane) + { + location[i] = origin[i] + MaxT[WhichPlane] * dir[i]; + if ((location[i] < MinB[i]) || + (location[i] > MaxB[i])) + { + // On this plane we're outside the box extents, so + // we miss the box + return false; + } + } + } + /* + // Choose the normal to be the plane normal facing into the ray + normal = G3D::Vector3::zero(); + normal[WhichPlane] = (dir[WhichPlane] > 0) ? -1.0 : 1.0; + */ + return true; + +#undef IR + } + }; +} +#endif diff --git a/src/common/Common.cpp b/src/common/Common.cpp new file mode 100644 index 00000000000..56e3c4faaf5 --- /dev/null +++ b/src/common/Common.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Common.h" + +char const* localeNames[TOTAL_LOCALES] = { + "enUS", + "koKR", + "frFR", + "deDE", + "zhCN", + "zhTW", + "esES", + "esMX", + "ruRU" +}; + +LocaleConstant GetLocaleByName(const std::string& name) +{ + for (uint32 i = 0; i < TOTAL_LOCALES; ++i) + if (name==localeNames[i]) + return LocaleConstant(i); + + return LOCALE_enUS; // including enGB case +} + diff --git a/src/common/Common.h b/src/common/Common.h new file mode 100644 index 00000000000..7a1ea247a94 --- /dev/null +++ b/src/common/Common.h @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_COMMON_H +#define TRINITYCORE_COMMON_H + +#include "Define.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <cmath> +#include <errno.h> +#include <signal.h> +#include <assert.h> + +#include <set> +#include <unordered_set> +#include <list> +#include <string> +#include <map> +#include <unordered_map> +#include <queue> +#include <sstream> +#include <algorithm> +#include <memory> +#include <vector> +#include <array> + +#include <boost/functional/hash.hpp> + +#include "Debugging/Errors.h" + +#include "Threading/LockedQueue.h" + +#if PLATFORM == PLATFORM_WINDOWS +# include <ws2tcpip.h> + +# if defined(__INTEL_COMPILER) +# if !defined(BOOST_ASIO_HAS_MOVE) +# define BOOST_ASIO_HAS_MOVE +# endif // !defined(BOOST_ASIO_HAS_MOVE) +# endif // if defined(__INTEL_COMPILER) + +#else +# include <sys/types.h> +# include <sys/ioctl.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <unistd.h> +# include <netdb.h> +#endif + +#if COMPILER == COMPILER_MICROSOFT + +#include <float.h> + +#define snprintf _snprintf +#define atoll _atoi64 +#define vsnprintf _vsnprintf +#define llabs _abs64 + +#else + +#define stricmp strcasecmp +#define strnicmp strncasecmp + +#endif + +inline float finiteAlways(float f) { return std::isfinite(f) ? f : 0.0f; } + +inline unsigned long atoul(char const* str) { return strtoul(str, nullptr, 10); } +inline unsigned long long atoull(char const* str) { return strtoull(str, nullptr, 10); } + +#define STRINGIZE(a) #a + +enum TimeConstants +{ + MINUTE = 60, + HOUR = MINUTE*60, + DAY = HOUR*24, + WEEK = DAY*7, + MONTH = DAY*30, + YEAR = MONTH*12, + IN_MILLISECONDS = 1000 +}; + +enum AccountTypes +{ + SEC_PLAYER = 0, + SEC_MODERATOR = 1, + SEC_GAMEMASTER = 2, + SEC_ADMINISTRATOR = 3, + SEC_CONSOLE = 4 // must be always last in list, accounts must have less security level always also +}; + +enum LocaleConstant +{ + LOCALE_enUS = 0, + LOCALE_koKR = 1, + LOCALE_frFR = 2, + LOCALE_deDE = 3, + LOCALE_zhCN = 4, + LOCALE_zhTW = 5, + LOCALE_esES = 6, + LOCALE_esMX = 7, + LOCALE_ruRU = 8, + + TOTAL_LOCALES +}; + +#define DEFAULT_LOCALE LOCALE_enUS + +#define MAX_LOCALES 8 +#define MAX_ACCOUNT_TUTORIAL_VALUES 8 + +extern char const* localeNames[TOTAL_LOCALES]; + +LocaleConstant GetLocaleByName(const std::string& name); + +typedef std::vector<std::string> StringVector; + +// we always use stdlibc++ std::max/std::min, undefine some not C++ standard defines (Win API and some other platforms) +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define MAX_QUERY_LEN 32*1024 + +namespace Trinity +{ + //! std::make_unique implementation (TODO: remove this once C++14 is supported) + template<typename T, typename ...Args> + std::unique_ptr<T> make_unique(Args&& ...args) + { + return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); + } +} + +//! Hash implementation for std::pair to allow using pairs in unordered_set or as key for unordered_map +//! Individual types used in pair must be hashable by boost::hash +namespace std +{ + template<class K, class V> + struct hash<std::pair<K, V>> + { + public: + size_t operator()(std::pair<K, V> const& key) const + { + return boost::hash_value(key); + } + }; +} + +#endif diff --git a/src/common/CompilerDefs.h b/src/common/CompilerDefs.h new file mode 100644 index 00000000000..c7867be517b --- /dev/null +++ b/src/common/CompilerDefs.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_COMPILERDEFS_H +#define TRINITY_COMPILERDEFS_H + +#define PLATFORM_WINDOWS 0 +#define PLATFORM_UNIX 1 +#define PLATFORM_APPLE 2 +#define PLATFORM_INTEL 3 + +// must be first (win 64 also define _WIN32) +#if defined( _WIN64 ) +# define PLATFORM PLATFORM_WINDOWS +#elif defined( __WIN32__ ) || defined( WIN32 ) || defined( _WIN32 ) +# define PLATFORM PLATFORM_WINDOWS +#elif defined( __APPLE_CC__ ) +# define PLATFORM PLATFORM_APPLE +#elif defined( __INTEL_COMPILER ) +# define PLATFORM PLATFORM_INTEL +#else +# define PLATFORM PLATFORM_UNIX +#endif + +#define COMPILER_MICROSOFT 0 +#define COMPILER_GNU 1 +#define COMPILER_BORLAND 2 +#define COMPILER_INTEL 3 + +#ifdef _MSC_VER +# define COMPILER COMPILER_MICROSOFT +#elif defined( __BORLANDC__ ) +# define COMPILER COMPILER_BORLAND +#elif defined( __INTEL_COMPILER ) +# define COMPILER COMPILER_INTEL +#elif defined( __GNUC__ ) +# define COMPILER COMPILER_GNU +# define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#else +# error "FATAL ERROR: Unknown compiler." +#endif + +#endif diff --git a/src/common/Configuration/Config.cpp b/src/common/Configuration/Config.cpp new file mode 100644 index 00000000000..ea426a5d33e --- /dev/null +++ b/src/common/Configuration/Config.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <algorithm> +#include <mutex> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/ini_parser.hpp> +#include "Config.h" + +using namespace boost::property_tree; + +bool ConfigMgr::LoadInitial(std::string const& file, std::string& error) +{ + std::lock_guard<std::mutex> lock(_configLock); + + _filename = file; + + try + { + ptree fullTree; + ini_parser::read_ini(file, fullTree); + + if (fullTree.empty()) + { + error = "empty file (" + file + ")"; + return false; + } + + // Since we're using only one section per config file, we skip the section and have direct property access + _config = fullTree.begin()->second; + } + catch (ini_parser::ini_parser_error const& e) + { + if (e.line() == 0) + error = e.message() + " (" + e.filename() + ")"; + else + error = e.message() + " (" + e.filename() + ":" + std::to_string(e.line()) + ")"; + return false; + } + + return true; +} + +bool ConfigMgr::Reload(std::string& error) +{ + return LoadInitial(_filename, error); +} + +std::string ConfigMgr::GetStringDefault(std::string const& name, const std::string& def) +{ + std::string value = _config.get<std::string>(ptree::path_type(name, '/'), def); + + value.erase(std::remove(value.begin(), value.end(), '"'), value.end()); + + return value; +} + +bool ConfigMgr::GetBoolDefault(std::string const& name, bool def) +{ + try + { + std::string val = _config.get<std::string>(ptree::path_type(name, '/')); + val.erase(std::remove(val.begin(), val.end(), '"'), val.end()); + return (val == "true" || val == "TRUE" || val == "yes" || val == "YES" || val == "1"); + } + catch (std::exception const& /*ex*/) + { + return def; + } +} + +int ConfigMgr::GetIntDefault(std::string const& name, int def) +{ + return _config.get<int>(ptree::path_type(name, '/'), def); +} + +float ConfigMgr::GetFloatDefault(std::string const& name, float def) +{ + return _config.get<float>(ptree::path_type(name, '/'), def); +} + +std::string const& ConfigMgr::GetFilename() +{ + std::lock_guard<std::mutex> lock(_configLock); + return _filename; +} + +std::list<std::string> ConfigMgr::GetKeysByString(std::string const& name) +{ + std::lock_guard<std::mutex> lock(_configLock); + + std::list<std::string> keys; + + for (const ptree::value_type& child : _config) + if (child.first.compare(0, name.length(), name) == 0) + keys.push_back(child.first); + + return keys; +} diff --git a/src/common/Configuration/Config.h b/src/common/Configuration/Config.h new file mode 100644 index 00000000000..5fb7cef9241 --- /dev/null +++ b/src/common/Configuration/Config.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#include <string> +#include <list> +#include <mutex> +#include <boost/property_tree/ptree.hpp> + +class ConfigMgr +{ + ConfigMgr() { } + ~ConfigMgr() { } + +public: + /// Method used only for loading main configuration files (authserver.conf and worldserver.conf) + bool LoadInitial(std::string const& file, std::string& error); + + static ConfigMgr* instance() + { + static ConfigMgr instance; + return &instance; + } + + bool Reload(std::string& error); + + std::string GetStringDefault(std::string const& name, const std::string& def); + bool GetBoolDefault(std::string const& name, bool def); + int GetIntDefault(std::string const& name, int def); + float GetFloatDefault(std::string const& name, float def); + + std::string const& GetFilename(); + std::list<std::string> GetKeysByString(std::string const& name); + +private: + std::string _filename; + boost::property_tree::ptree _config; + std::mutex _configLock; + + ConfigMgr(ConfigMgr const&); + ConfigMgr& operator=(ConfigMgr const&); +}; + +#define sConfigMgr ConfigMgr::instance() + +#endif diff --git a/src/common/Cryptography/ARC4.cpp b/src/common/Cryptography/ARC4.cpp new file mode 100644 index 00000000000..d1082b39347 --- /dev/null +++ b/src/common/Cryptography/ARC4.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ARC4.h" + +ARC4::ARC4(uint8 len) : m_ctx() +{ + EVP_CIPHER_CTX_init(&m_ctx); + EVP_EncryptInit_ex(&m_ctx, EVP_rc4(), NULL, NULL, NULL); + EVP_CIPHER_CTX_set_key_length(&m_ctx, len); +} + +ARC4::ARC4(uint8 *seed, uint8 len) : m_ctx() +{ + EVP_CIPHER_CTX_init(&m_ctx); + EVP_EncryptInit_ex(&m_ctx, EVP_rc4(), NULL, NULL, NULL); + EVP_CIPHER_CTX_set_key_length(&m_ctx, len); + EVP_EncryptInit_ex(&m_ctx, NULL, NULL, seed, NULL); +} + +ARC4::~ARC4() +{ + EVP_CIPHER_CTX_cleanup(&m_ctx); +} + +void ARC4::Init(uint8 *seed) +{ + EVP_EncryptInit_ex(&m_ctx, NULL, NULL, seed, NULL); +} + +void ARC4::UpdateData(int len, uint8 *data) +{ + int outlen = 0; + EVP_EncryptUpdate(&m_ctx, data, &outlen, data, len); + EVP_EncryptFinal_ex(&m_ctx, data, &outlen); +} diff --git a/src/common/Cryptography/ARC4.h b/src/common/Cryptography/ARC4.h new file mode 100644 index 00000000000..16a0cb92eb9 --- /dev/null +++ b/src/common/Cryptography/ARC4.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _AUTH_SARC4_H +#define _AUTH_SARC4_H + +#include <openssl/evp.h> +#include "Define.h" + +class ARC4 +{ + public: + ARC4(uint8 len); + ARC4(uint8 *seed, uint8 len); + ~ARC4(); + void Init(uint8 *seed); + void UpdateData(int len, uint8 *data); + private: + EVP_CIPHER_CTX m_ctx; +}; + +#endif diff --git a/src/common/Cryptography/Authentication/AuthCrypt.cpp b/src/common/Cryptography/Authentication/AuthCrypt.cpp new file mode 100644 index 00000000000..153726e6950 --- /dev/null +++ b/src/common/Cryptography/Authentication/AuthCrypt.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "AuthCrypt.h" +#include "Cryptography/HMACSHA1.h" +#include "Cryptography/BigNumber.h" + +#include <cstring> + +AuthCrypt::AuthCrypt() : + _clientDecrypt(SHA_DIGEST_LENGTH), _serverEncrypt(SHA_DIGEST_LENGTH), + _initialized(false) +{ } + +void AuthCrypt::Init(BigNumber* K) +{ + uint8 ServerEncryptionKey[SEED_KEY_SIZE] = { 0xCC, 0x98, 0xAE, 0x04, 0xE8, 0x97, 0xEA, 0xCA, 0x12, 0xDD, 0xC0, 0x93, 0x42, 0x91, 0x53, 0x57 }; + HmacHash serverEncryptHmac(SEED_KEY_SIZE, (uint8*)ServerEncryptionKey); + uint8 *encryptHash = serverEncryptHmac.ComputeHash(K); + + uint8 ServerDecryptionKey[SEED_KEY_SIZE] = { 0xC2, 0xB3, 0x72, 0x3C, 0xC6, 0xAE, 0xD9, 0xB5, 0x34, 0x3C, 0x53, 0xEE, 0x2F, 0x43, 0x67, 0xCE }; + HmacHash clientDecryptHmac(SEED_KEY_SIZE, (uint8*)ServerDecryptionKey); + uint8 *decryptHash = clientDecryptHmac.ComputeHash(K); + + //ARC4 _serverDecrypt(encryptHash); + _clientDecrypt.Init(decryptHash); + _serverEncrypt.Init(encryptHash); + //ARC4 _clientEncrypt(decryptHash); + + // Drop first 1024 bytes, as WoW uses ARC4-drop1024. + uint8 syncBuf[1024]; + memset(syncBuf, 0, 1024); + + _serverEncrypt.UpdateData(1024, syncBuf); + //_clientEncrypt.UpdateData(1024, syncBuf); + + memset(syncBuf, 0, 1024); + + //_serverDecrypt.UpdateData(1024, syncBuf); + _clientDecrypt.UpdateData(1024, syncBuf); + + _initialized = true; +} + +void AuthCrypt::DecryptRecv(uint8 *data, size_t len) +{ + if (!_initialized) + return; + + _clientDecrypt.UpdateData(len, data); +} + +void AuthCrypt::EncryptSend(uint8 *data, size_t len) +{ + if (!_initialized) + return; + + _serverEncrypt.UpdateData(len, data); +} + diff --git a/src/common/Cryptography/Authentication/AuthCrypt.h b/src/common/Cryptography/Authentication/AuthCrypt.h new file mode 100644 index 00000000000..b8913c95a2c --- /dev/null +++ b/src/common/Cryptography/Authentication/AuthCrypt.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _AUTHCRYPT_H +#define _AUTHCRYPT_H + +#include "Cryptography/ARC4.h" + +class BigNumber; + +class AuthCrypt +{ + public: + AuthCrypt(); + + void Init(BigNumber* K); + void DecryptRecv(uint8 *, size_t); + void EncryptSend(uint8 *, size_t); + + bool IsInitialized() const { return _initialized; } + + private: + ARC4 _clientDecrypt; + ARC4 _serverEncrypt; + bool _initialized; +}; +#endif diff --git a/src/common/Cryptography/BigNumber.cpp b/src/common/Cryptography/BigNumber.cpp new file mode 100644 index 00000000000..720e8e30441 --- /dev/null +++ b/src/common/Cryptography/BigNumber.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Cryptography/BigNumber.h" +#include <openssl/bn.h> +#include <cstring> +#include <algorithm> +#include <memory> + +BigNumber::BigNumber() + : _bn(BN_new()) +{ } + +BigNumber::BigNumber(BigNumber const& bn) + : _bn(BN_dup(bn._bn)) +{ } + +BigNumber::BigNumber(uint32 val) + : _bn(BN_new()) +{ + BN_set_word(_bn, val); +} + +BigNumber::~BigNumber() +{ + BN_free(_bn); +} + +void BigNumber::SetDword(uint32 val) +{ + BN_set_word(_bn, val); +} + +void BigNumber::SetQword(uint64 val) +{ + BN_set_word(_bn, (uint32)(val >> 32)); + BN_lshift(_bn, _bn, 32); + BN_add_word(_bn, (uint32)(val & 0xFFFFFFFF)); +} + +void BigNumber::SetBinary(uint8 const* bytes, int32 len) +{ + uint8* array = new uint8[len]; + + for (int i = 0; i < len; i++) + array[i] = bytes[len - 1 - i]; + + BN_bin2bn(array, len, _bn); + + delete[] array; +} + +void BigNumber::SetHexStr(char const* str) +{ + BN_hex2bn(&_bn, str); +} + +void BigNumber::SetRand(int32 numbits) +{ + BN_rand(_bn, numbits, 0, 1); +} + +BigNumber& BigNumber::operator=(BigNumber const& bn) +{ + if (this == &bn) + return *this; + + BN_copy(_bn, bn._bn); + return *this; +} + +BigNumber BigNumber::operator+=(BigNumber const& bn) +{ + BN_add(_bn, _bn, bn._bn); + return *this; +} + +BigNumber BigNumber::operator-=(BigNumber const& bn) +{ + BN_sub(_bn, _bn, bn._bn); + return *this; +} + +BigNumber BigNumber::operator*=(BigNumber const& bn) +{ + BN_CTX *bnctx; + + bnctx = BN_CTX_new(); + BN_mul(_bn, _bn, bn._bn, bnctx); + BN_CTX_free(bnctx); + + return *this; +} + +BigNumber BigNumber::operator/=(BigNumber const& bn) +{ + BN_CTX *bnctx; + + bnctx = BN_CTX_new(); + BN_div(_bn, NULL, _bn, bn._bn, bnctx); + BN_CTX_free(bnctx); + + return *this; +} + +BigNumber BigNumber::operator%=(BigNumber const& bn) +{ + BN_CTX *bnctx; + + bnctx = BN_CTX_new(); + BN_mod(_bn, _bn, bn._bn, bnctx); + BN_CTX_free(bnctx); + + return *this; +} + +BigNumber BigNumber::Exp(BigNumber const& bn) +{ + BigNumber ret; + BN_CTX *bnctx; + + bnctx = BN_CTX_new(); + BN_exp(ret._bn, _bn, bn._bn, bnctx); + BN_CTX_free(bnctx); + + return ret; +} + +BigNumber BigNumber::ModExp(BigNumber const& bn1, BigNumber const& bn2) +{ + BigNumber ret; + BN_CTX *bnctx; + + bnctx = BN_CTX_new(); + BN_mod_exp(ret._bn, _bn, bn1._bn, bn2._bn, bnctx); + BN_CTX_free(bnctx); + + return ret; +} + +int32 BigNumber::GetNumBytes(void) +{ + return BN_num_bytes(_bn); +} + +uint32 BigNumber::AsDword() +{ + return (uint32)BN_get_word(_bn); +} + +bool BigNumber::isZero() const +{ + return BN_is_zero(_bn); +} + +std::unique_ptr<uint8[]> BigNumber::AsByteArray(int32 minSize, bool littleEndian) +{ + int numBytes = GetNumBytes(); + int length = (minSize >= numBytes) ? minSize : numBytes; + + uint8* array = new uint8[length]; + + // If we need more bytes than length of BigNumber set the rest to 0 + if (length > numBytes) + memset((void*)array, 0, length); + + BN_bn2bin(_bn, (unsigned char *)array); + + // openssl's BN stores data internally in big endian format, reverse if little endian desired + if (littleEndian) + std::reverse(array, array + numBytes); + + std::unique_ptr<uint8[]> ret(array); + return ret; +} + +char * BigNumber::AsHexStr() const +{ + return BN_bn2hex(_bn); +} + +char * BigNumber::AsDecStr() const +{ + return BN_bn2dec(_bn); +} + diff --git a/src/common/Cryptography/BigNumber.h b/src/common/Cryptography/BigNumber.h new file mode 100644 index 00000000000..e6a056b5baa --- /dev/null +++ b/src/common/Cryptography/BigNumber.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _AUTH_BIGNUMBER_H +#define _AUTH_BIGNUMBER_H + +#include <memory> +#include "Define.h" + +struct bignum_st; + +class BigNumber +{ + public: + BigNumber(); + BigNumber(BigNumber const& bn); + BigNumber(uint32); + ~BigNumber(); + + void SetDword(uint32); + void SetQword(uint64); + void SetBinary(uint8 const* bytes, int32 len); + void SetHexStr(char const* str); + + void SetRand(int32 numbits); + + BigNumber& operator=(BigNumber const& bn); + + BigNumber operator+=(BigNumber const& bn); + BigNumber operator+(BigNumber const& bn) + { + BigNumber t(*this); + return t += bn; + } + + BigNumber operator-=(BigNumber const& bn); + BigNumber operator-(BigNumber const& bn) + { + BigNumber t(*this); + return t -= bn; + } + + BigNumber operator*=(BigNumber const& bn); + BigNumber operator*(BigNumber const& bn) + { + BigNumber t(*this); + return t *= bn; + } + + BigNumber operator/=(BigNumber const& bn); + BigNumber operator/(BigNumber const& bn) + { + BigNumber t(*this); + return t /= bn; + } + + BigNumber operator%=(BigNumber const& bn); + BigNumber operator%(BigNumber const& bn) + { + BigNumber t(*this); + return t %= bn; + } + + bool isZero() const; + + BigNumber ModExp(BigNumber const& bn1, BigNumber const& bn2); + BigNumber Exp(BigNumber const&); + + int32 GetNumBytes(void); + + struct bignum_st *BN() { return _bn; } + + uint32 AsDword(); + + std::unique_ptr<uint8[]> AsByteArray(int32 minSize = 0, bool littleEndian = true); + + char * AsHexStr() const; + char * AsDecStr() const; + + private: + struct bignum_st *_bn; + +}; +#endif + diff --git a/src/common/Cryptography/HMACSHA1.cpp b/src/common/Cryptography/HMACSHA1.cpp new file mode 100644 index 00000000000..304633cbc03 --- /dev/null +++ b/src/common/Cryptography/HMACSHA1.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "HMACSHA1.h" +#include "BigNumber.h" +#include "Common.h" + +HmacHash::HmacHash(uint32 len, uint8 *seed) +{ + HMAC_CTX_init(&m_ctx); + HMAC_Init_ex(&m_ctx, seed, len, EVP_sha1(), NULL); + memset(m_digest, 0, sizeof(m_digest)); +} + +HmacHash::~HmacHash() +{ + HMAC_CTX_cleanup(&m_ctx); +} + +void HmacHash::UpdateData(const std::string &str) +{ + HMAC_Update(&m_ctx, (uint8 const*)str.c_str(), str.length()); +} + +void HmacHash::UpdateData(const uint8* data, size_t len) +{ + HMAC_Update(&m_ctx, data, len); +} + +void HmacHash::Finalize() +{ + uint32 length = 0; + HMAC_Final(&m_ctx, (uint8*)m_digest, &length); + ASSERT(length == SHA_DIGEST_LENGTH); +} + +uint8 *HmacHash::ComputeHash(BigNumber* bn) +{ + HMAC_Update(&m_ctx, bn->AsByteArray().get(), bn->GetNumBytes()); + Finalize(); + return (uint8*)m_digest; +} diff --git a/src/common/Cryptography/HMACSHA1.h b/src/common/Cryptography/HMACSHA1.h new file mode 100644 index 00000000000..66cf214d9b1 --- /dev/null +++ b/src/common/Cryptography/HMACSHA1.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _AUTH_HMAC_H +#define _AUTH_HMAC_H + +#include "Define.h" +#include <string> +#include <openssl/hmac.h> +#include <openssl/sha.h> + +class BigNumber; + +#define SEED_KEY_SIZE 16 + +class HmacHash +{ + public: + HmacHash(uint32 len, uint8 *seed); + ~HmacHash(); + void UpdateData(const std::string &str); + void UpdateData(const uint8* data, size_t len); + void Finalize(); + uint8 *ComputeHash(BigNumber* bn); + uint8 *GetDigest() { return (uint8*)m_digest; } + int GetLength() const { return SHA_DIGEST_LENGTH; } + private: + HMAC_CTX m_ctx; + uint8 m_digest[SHA_DIGEST_LENGTH]; +}; +#endif + diff --git a/src/common/Cryptography/OpenSSLCrypto.cpp b/src/common/Cryptography/OpenSSLCrypto.cpp new file mode 100644 index 00000000000..f122888292f --- /dev/null +++ b/src/common/Cryptography/OpenSSLCrypto.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <OpenSSLCrypto.h> +#include <openssl/crypto.h> +#include <vector> +#include <thread> +#include <mutex> + +std::vector<std::mutex*> cryptoLocks; + +static void lockingCallback(int mode, int type, const char* /*file*/, int /*line*/) +{ + if (mode & CRYPTO_LOCK) + cryptoLocks[type]->lock(); + else + cryptoLocks[type]->unlock(); +} + +static void threadIdCallback(CRYPTO_THREADID * id) +{ + CRYPTO_THREADID_set_numeric(id, std::hash<std::thread::id>()(std::this_thread::get_id())); +} + +void OpenSSLCrypto::threadsSetup() +{ + cryptoLocks.resize(CRYPTO_num_locks()); + for(int i = 0 ; i < CRYPTO_num_locks(); ++i) + { + cryptoLocks[i] = new std::mutex; + } + CRYPTO_THREADID_set_callback(threadIdCallback); + CRYPTO_set_locking_callback(lockingCallback); +} + +void OpenSSLCrypto::threadsCleanup() +{ + CRYPTO_set_locking_callback(NULL); + CRYPTO_THREADID_set_callback(NULL); + for(int i = 0 ; i < CRYPTO_num_locks(); ++i) + { + delete cryptoLocks[i]; + } + cryptoLocks.resize(0); +} diff --git a/src/common/Cryptography/OpenSSLCrypto.h b/src/common/Cryptography/OpenSSLCrypto.h new file mode 100644 index 00000000000..0daa20c4780 --- /dev/null +++ b/src/common/Cryptography/OpenSSLCrypto.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef OPENSSL_CRYPTO_H +#define OPENSSL_CRYPTO_H + +/** +* A group of functions which setup openssl crypto module to work properly in multithreaded enviroment +* If not setup properly - it will crash +*/ +namespace OpenSSLCrypto +{ + /// Needs to be called before threads using openssl are spawned + void threadsSetup(); + /// Needs to be called after threads using openssl are despawned + void threadsCleanup(); +} + +#endif
\ No newline at end of file diff --git a/src/common/Cryptography/SHA1.cpp b/src/common/Cryptography/SHA1.cpp new file mode 100644 index 00000000000..bd7101075de --- /dev/null +++ b/src/common/Cryptography/SHA1.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "SHA1.h" +#include "BigNumber.h" +#include <cstring> +#include <stdarg.h> + +SHA1Hash::SHA1Hash() +{ + SHA1_Init(&mC); + memset(mDigest, 0, SHA_DIGEST_LENGTH * sizeof(uint8)); +} + +SHA1Hash::~SHA1Hash() +{ + SHA1_Init(&mC); +} + +void SHA1Hash::UpdateData(const uint8 *dta, int len) +{ + SHA1_Update(&mC, dta, len); +} + +void SHA1Hash::UpdateData(const std::string &str) +{ + UpdateData((uint8 const*)str.c_str(), str.length()); +} + +void SHA1Hash::UpdateBigNumbers(BigNumber* bn0, ...) +{ + va_list v; + BigNumber* bn; + + va_start(v, bn0); + bn = bn0; + while (bn) + { + UpdateData(bn->AsByteArray().get(), bn->GetNumBytes()); + bn = va_arg(v, BigNumber*); + } + va_end(v); +} + +void SHA1Hash::Initialize() +{ + SHA1_Init(&mC); +} + +void SHA1Hash::Finalize(void) +{ + SHA1_Final(mDigest, &mC); +} + diff --git a/src/common/Cryptography/SHA1.h b/src/common/Cryptography/SHA1.h new file mode 100644 index 00000000000..f59bdc25556 --- /dev/null +++ b/src/common/Cryptography/SHA1.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _AUTH_SHA1_H +#define _AUTH_SHA1_H + +#include "Define.h" +#include <string> +#include <openssl/sha.h> + +class BigNumber; + +class SHA1Hash +{ + public: + SHA1Hash(); + ~SHA1Hash(); + + void UpdateBigNumbers(BigNumber* bn0, ...); + + void UpdateData(const uint8 *dta, int len); + void UpdateData(const std::string &str); + + void Initialize(); + void Finalize(); + + uint8 *GetDigest(void) { return mDigest; } + int GetLength(void) const { return SHA_DIGEST_LENGTH; } + + private: + SHA_CTX mC; + uint8 mDigest[SHA_DIGEST_LENGTH]; +}; +#endif + diff --git a/src/common/Cryptography/WardenKeyGeneration.h b/src/common/Cryptography/WardenKeyGeneration.h new file mode 100644 index 00000000000..bfa0337d347 --- /dev/null +++ b/src/common/Cryptography/WardenKeyGeneration.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2011 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "SHA1.h" + +#include <cstring> + +#ifndef _WARDEN_KEY_GENERATION_H +#define _WARDEN_KEY_GENERATION_H + +class SHA1Randx +{ +public: + SHA1Randx(uint8* buff, uint32 size) + { + uint32 halfSize = size / 2; + + sh.Initialize(); + sh.UpdateData(buff, halfSize); + sh.Finalize(); + + memcpy(o1, sh.GetDigest(), 20); + + sh.Initialize(); + sh.UpdateData(buff + halfSize, size - halfSize); + sh.Finalize(); + + memcpy(o2, sh.GetDigest(), 20); + + memset(o0, 0x00, 20); + + FillUp(); + } + + void Generate(uint8* buf, uint32 sz) + { + for (uint32 i = 0; i < sz; ++i) + { + if (taken == 20) + FillUp(); + + buf[i] = o0[taken]; + taken++; + } + } + +private: + void FillUp() + { + sh.Initialize(); + sh.UpdateData(o1, 20); + sh.UpdateData(o0, 20); + sh.UpdateData(o2, 20); + sh.Finalize(); + + memcpy(o0, sh.GetDigest(), 20); + + taken = 0; + } + + SHA1Hash sh; + uint32 taken; + uint8 o0[20], o1[20], o2[20]; +}; + +#endif diff --git a/src/common/Debugging/Errors.cpp b/src/common/Debugging/Errors.cpp new file mode 100644 index 00000000000..4c7e91a8219 --- /dev/null +++ b/src/common/Debugging/Errors.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Errors.h" + +#include <cstdio> +#include <cstdlib> +#include <thread> +#include <cstdarg> + +/** + @file Errors.cpp + + @brief This file contains definitions of functions used for reporting critical application errors + + It is very important that (std::)abort is NEVER called in place of *((volatile int*)NULL) = 0; + Calling abort() on Windows does not invoke unhandled exception filters - a mechanism used by WheatyExceptionReport + to log crashes. exit(1) calls here are for static analysis tools to indicate that calling functions defined in this file + terminates the application. + */ + +namespace Trinity { + +void Assert(char const* file, int line, char const* function, char const* message) +{ + fprintf(stderr, "\n%s:%i in %s ASSERTION FAILED:\n %s\n", + file, line, function, message); + *((volatile int*)NULL) = 0; + exit(1); +} + +void Assert(char const* file, int line, char const* function, char const* message, char const* format, ...) +{ + va_list args; + va_start(args, format); + + fprintf(stderr, "\n%s:%i in %s ASSERTION FAILED:\n %s ", file, line, function, message); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + fflush(stderr); + + va_end(args); + *((volatile int*)NULL) = 0; + exit(1); +} + +void Fatal(char const* file, int line, char const* function, char const* message, ...) +{ + va_list args; + va_start(args, message); + + fprintf(stderr, "\n%s:%i in %s FATAL ERROR:\n ", file, line, function); + vfprintf(stderr, message, args); + fprintf(stderr, "\n"); + fflush(stderr); + + std::this_thread::sleep_for(std::chrono::seconds(10)); + *((volatile int*)NULL) = 0; + exit(1); +} + +void Error(char const* file, int line, char const* function, char const* message) +{ + fprintf(stderr, "\n%s:%i in %s ERROR:\n %s\n", + file, line, function, message); + *((volatile int*)NULL) = 0; + exit(1); +} + +void Warning(char const* file, int line, char const* function, char const* message) +{ + fprintf(stderr, "\n%s:%i in %s WARNING:\n %s\n", + file, line, function, message); +} + +void Abort(char const* file, int line, char const* function) +{ + fprintf(stderr, "\n%s:%i in %s ABORTED\n", + file, line, function); + *((volatile int*)NULL) = 0; + exit(1); +} + +} // namespace Trinity diff --git a/src/common/Debugging/Errors.h b/src/common/Debugging/Errors.h new file mode 100644 index 00000000000..9e526933acc --- /dev/null +++ b/src/common/Debugging/Errors.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_ERRORS_H +#define TRINITYCORE_ERRORS_H + +#include "Define.h" + +namespace Trinity +{ + DECLSPEC_NORETURN void Assert(char const* file, int line, char const* function, char const* message) ATTR_NORETURN; + DECLSPEC_NORETURN void Assert(char const* file, int line, char const* function, char const* message, char const* format, ...) ATTR_NORETURN ATTR_PRINTF(5, 6); + + DECLSPEC_NORETURN void Fatal(char const* file, int line, char const* function, char const* message, ...) ATTR_NORETURN ATTR_PRINTF(4, 5); + + DECLSPEC_NORETURN void Error(char const* file, int line, char const* function, char const* message) ATTR_NORETURN; + + DECLSPEC_NORETURN void Abort(char const* file, int line, char const* function) ATTR_NORETURN; + + void Warning(char const* file, int line, char const* function, char const* message); + +} // namespace Trinity + +#if COMPILER == COMPILER_MICROSOFT +#define ASSERT_BEGIN __pragma(warning(push)) __pragma(warning(disable: 4127)) +#define ASSERT_END __pragma(warning(pop)) +#else +#define ASSERT_BEGIN +#define ASSERT_END +#endif + +#define WPAssert(cond, ...) ASSERT_BEGIN do { if (!(cond)) Trinity::Assert(__FILE__, __LINE__, __FUNCTION__, #cond, ##__VA_ARGS__); } while(0) ASSERT_END +#define WPFatal(cond, ...) ASSERT_BEGIN do { if (!(cond)) Trinity::Fatal(__FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); } while(0) ASSERT_END +#define WPError(cond, msg) ASSERT_BEGIN do { if (!(cond)) Trinity::Error(__FILE__, __LINE__, __FUNCTION__, (msg)); } while(0) ASSERT_END +#define WPWarning(cond, msg) ASSERT_BEGIN do { if (!(cond)) Trinity::Warning(__FILE__, __LINE__, __FUNCTION__, (msg)); } while(0) ASSERT_END +#define WPAbort() ASSERT_BEGIN do { Trinity::Abort(__FILE__, __LINE__, __FUNCTION__); } while(0) ASSERT_END + +#define ASSERT WPAssert +#define ABORT WPAbort + +template <typename T> inline T* ASSERT_NOTNULL(T* pointer) +{ + ASSERT(pointer); + return pointer; +} + +#endif diff --git a/src/common/Debugging/WheatyExceptionReport.cpp b/src/common/Debugging/WheatyExceptionReport.cpp new file mode 100644 index 00000000000..5b9a1b1bd6c --- /dev/null +++ b/src/common/Debugging/WheatyExceptionReport.cpp @@ -0,0 +1,1463 @@ +//========================================== +// Matt Pietrek +// MSDN Magazine, 2002 +// FILE: WheatyExceptionReport.CPP +//========================================== +#include "CompilerDefs.h" + +#if PLATFORM == PLATFORM_WINDOWS && !defined(__MINGW32__) +#define WIN32_LEAN_AND_MEAN +#pragma warning(disable:4996) +#pragma warning(disable:4312) +#pragma warning(disable:4311) +#include <windows.h> +#include <tlhelp32.h> +#include <stdio.h> +#include <tchar.h> +#define _NO_CVCONST_H +#include <dbghelp.h> + +#include "WheatyExceptionReport.h" + +#include "Common.h" +#include "GitRevision.h" + +#define CrashFolder _T("Crashes") +#pragma comment(linker, "/DEFAULTLIB:dbghelp.lib") + +inline LPTSTR ErrorMessage(DWORD dw) +{ + LPVOID lpMsgBuf; + DWORD formatResult = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL); + if (formatResult != 0) + return (LPTSTR)lpMsgBuf; + else + { + LPTSTR msgBuf = (LPTSTR)LocalAlloc(LPTR, 30); + sprintf(msgBuf, "Unknown error: %u", dw); + return msgBuf; + } + +} + +//============================== Global Variables ============================= + +// +// Declare the static variables of the WheatyExceptionReport class +// +TCHAR WheatyExceptionReport::m_szLogFileName[MAX_PATH]; +TCHAR WheatyExceptionReport::m_szDumpFileName[MAX_PATH]; +LPTOP_LEVEL_EXCEPTION_FILTER WheatyExceptionReport::m_previousFilter; +HANDLE WheatyExceptionReport::m_hReportFile; +HANDLE WheatyExceptionReport::m_hDumpFile; +HANDLE WheatyExceptionReport::m_hProcess; +SymbolPairs WheatyExceptionReport::symbols; +std::stack<SymbolDetail> WheatyExceptionReport::symbolDetails; +bool WheatyExceptionReport::stackOverflowException; +bool WheatyExceptionReport::alreadyCrashed; +std::mutex WheatyExceptionReport::alreadyCrashedLock; +WheatyExceptionReport::pRtlGetVersion WheatyExceptionReport::RtlGetVersion; + + +// Declare global instance of class +WheatyExceptionReport g_WheatyExceptionReport; + +//============================== Class Methods ============================= + +WheatyExceptionReport::WheatyExceptionReport() // Constructor +{ + // Install the unhandled exception filter function + m_previousFilter = SetUnhandledExceptionFilter(WheatyUnhandledExceptionFilter); + m_hProcess = GetCurrentProcess(); + stackOverflowException = false; + alreadyCrashed = false; + RtlGetVersion = (pRtlGetVersion)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "RtlGetVersion"); + if (!IsDebuggerPresent()) + { + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + } +} + +//============ +// Destructor +//============ +WheatyExceptionReport::~WheatyExceptionReport() +{ + if (m_previousFilter) + SetUnhandledExceptionFilter(m_previousFilter); + ClearSymbols(); +} + +//=========================================================== +// Entry point where control comes on an unhandled exception +//=========================================================== +LONG WINAPI WheatyExceptionReport::WheatyUnhandledExceptionFilter( +PEXCEPTION_POINTERS pExceptionInfo) +{ + std::unique_lock<std::mutex> guard(alreadyCrashedLock); + // Handle only 1 exception in the whole process lifetime + if (alreadyCrashed) + return EXCEPTION_EXECUTE_HANDLER; + + alreadyCrashed = true; + + if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) + stackOverflowException = true; + + TCHAR module_folder_name[MAX_PATH]; + GetModuleFileName(0, module_folder_name, MAX_PATH); + TCHAR* pos = _tcsrchr(module_folder_name, '\\'); + if (!pos) + return 0; + pos[0] = '\0'; + ++pos; + + TCHAR crash_folder_path[MAX_PATH]; + sprintf(crash_folder_path, "%s\\%s", module_folder_name, CrashFolder); + if (!CreateDirectory(crash_folder_path, NULL)) + { + if (GetLastError() != ERROR_ALREADY_EXISTS) + return 0; + } + + SYSTEMTIME systime; + GetLocalTime(&systime); + sprintf(m_szDumpFileName, "%s\\%s_%s_[%u-%u_%u-%u-%u].dmp", + crash_folder_path, GitRevision::GetHash(), pos, systime.wDay, systime.wMonth, systime.wHour, systime.wMinute, systime.wSecond); + + sprintf(m_szLogFileName, "%s\\%s_%s_[%u-%u_%u-%u-%u].txt", + crash_folder_path, GitRevision::GetHash(), pos, systime.wDay, systime.wMonth, systime.wHour, systime.wMinute, systime.wSecond); + + m_hDumpFile = CreateFile(m_szDumpFileName, + GENERIC_WRITE, + 0, + 0, + OPEN_ALWAYS, + FILE_FLAG_WRITE_THROUGH, + 0); + + m_hReportFile = CreateFile(m_szLogFileName, + GENERIC_WRITE, + 0, + 0, + OPEN_ALWAYS, + FILE_FLAG_WRITE_THROUGH, + 0); + + if (m_hDumpFile) + { + MINIDUMP_EXCEPTION_INFORMATION info; + info.ClientPointers = FALSE; + info.ExceptionPointers = pExceptionInfo; + info.ThreadId = GetCurrentThreadId(); + + MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), + m_hDumpFile, MiniDumpWithIndirectlyReferencedMemory, &info, 0, 0); + + CloseHandle(m_hDumpFile); + } + + if (m_hReportFile) + { + SetFilePointer(m_hReportFile, 0, 0, FILE_END); + + GenerateExceptionReport(pExceptionInfo); + + CloseHandle(m_hReportFile); + m_hReportFile = 0; + } + + if (m_previousFilter) + return m_previousFilter(pExceptionInfo); + else + return EXCEPTION_EXECUTE_HANDLER/*EXCEPTION_CONTINUE_SEARCH*/; +} + +BOOL WheatyExceptionReport::_GetProcessorName(TCHAR* sProcessorName, DWORD maxcount) +{ + if (!sProcessorName) + return FALSE; + + HKEY hKey; + LONG lRet; + lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), + 0, KEY_QUERY_VALUE, &hKey); + if (lRet != ERROR_SUCCESS) + return FALSE; + TCHAR szTmp[2048]; + DWORD cntBytes = sizeof(szTmp); + lRet = ::RegQueryValueEx(hKey, _T("ProcessorNameString"), NULL, NULL, + (LPBYTE)szTmp, &cntBytes); + if (lRet != ERROR_SUCCESS) + return FALSE; + ::RegCloseKey(hKey); + sProcessorName[0] = '\0'; + // Skip spaces + TCHAR* psz = szTmp; + while (iswspace(*psz)) + ++psz; + _tcsncpy(sProcessorName, psz, maxcount); + return TRUE; +} + +template<size_t size> +void ToTchar(wchar_t const* src, TCHAR (&dst)[size], std::true_type) +{ + wcstombs_s(nullptr, dst, src, size); +} + +template<size_t size> +void ToTchar(wchar_t const* src, TCHAR (&dst)[size], std::false_type) +{ + wcscpy_s(dst, src); +} + +BOOL WheatyExceptionReport::_GetWindowsVersion(TCHAR* szVersion, DWORD cntMax) +{ + // Try calling GetVersionEx using the OSVERSIONINFOEX structure. + // If that fails, try using the OSVERSIONINFO structure. + RTL_OSVERSIONINFOEXW osvi = { 0 }; + osvi.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); + NTSTATUS bVersionEx = RtlGetVersion((PRTL_OSVERSIONINFOW)&osvi); + if (bVersionEx < 0) + { + osvi.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOW); + if (!RtlGetVersion((PRTL_OSVERSIONINFOW)&osvi)) + return FALSE; + } + *szVersion = _T('\0'); + + TCHAR szCSDVersion[256]; + ToTchar(osvi.szCSDVersion, szCSDVersion, std::is_same<TCHAR, char>::type()); + + TCHAR wszTmp[128]; + switch (osvi.dwPlatformId) + { + // Windows NT product family. + case VER_PLATFORM_WIN32_NT: + { + #if WINVER < 0x0500 + BYTE suiteMask = osvi.wReserved[0]; + BYTE productType = osvi.wReserved[1]; + #else + WORD suiteMask = osvi.wSuiteMask; + BYTE productType = osvi.wProductType; + #endif // WINVER < 0x0500 + + // Test for the specific product family. + if (osvi.dwMajorVersion == 10) + { + if (productType == VER_NT_WORKSTATION) + _tcsncat(szVersion, _T("Windows 10 "), cntMax); + else + _tcsncat(szVersion, _T("Windows Server 2016 "), cntMax); + } + else if (osvi.dwMajorVersion == 6) + { + if (productType == VER_NT_WORKSTATION) + { + if (osvi.dwMinorVersion == 3) + _tcsncat(szVersion, _T("Windows 8.1 "), cntMax); + else if (osvi.dwMinorVersion == 2) + _tcsncat(szVersion, _T("Windows 8 "), cntMax); + else if (osvi.dwMinorVersion == 1) + _tcsncat(szVersion, _T("Windows 7 "), cntMax); + else + _tcsncat(szVersion, _T("Windows Vista "), cntMax); + } + else if (osvi.dwMinorVersion == 3) + _tcsncat(szVersion, _T("Windows Server 2012 R2 "), cntMax); + else if (osvi.dwMinorVersion == 2) + _tcsncat(szVersion, _T("Windows Server 2012 "), cntMax); + else if (osvi.dwMinorVersion == 1) + _tcsncat(szVersion, _T("Windows Server 2008 R2 "), cntMax); + else + _tcsncat(szVersion, _T("Windows Server 2008 "), cntMax); + } + else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) + _tcsncat(szVersion, _T("Microsoft Windows Server 2003 "), cntMax); + else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) + _tcsncat(szVersion, _T("Microsoft Windows XP "), cntMax); + else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) + _tcsncat(szVersion, _T("Microsoft Windows 2000 "), cntMax); + else if (osvi.dwMajorVersion <= 4) + _tcsncat(szVersion, _T("Microsoft Windows NT "), cntMax); + + // Test for specific product on Windows NT 4.0 SP6 and later. + if (bVersionEx >= 0) + { + // Test for the workstation type. + if (productType == VER_NT_WORKSTATION) + { + if (osvi.dwMajorVersion == 4) + _tcsncat(szVersion, _T("Workstation 4.0 "), cntMax); + else if (suiteMask & VER_SUITE_PERSONAL) + _tcsncat(szVersion, _T("Home Edition "), cntMax); + else if (suiteMask & VER_SUITE_EMBEDDEDNT) + _tcsncat(szVersion, _T("Embedded "), cntMax); + else + _tcsncat(szVersion, _T("Professional "), cntMax); + } + // Test for the server type. + else if (productType == VER_NT_SERVER) + { + if (osvi.dwMajorVersion == 6 || osvi.dwMajorVersion == 10) + { + if (suiteMask & VER_SUITE_SMALLBUSINESS_RESTRICTED) + _tcsncat(szVersion, _T("Essentials "), cntMax); + else if (suiteMask & VER_SUITE_DATACENTER) + _tcsncat(szVersion, _T("Datacenter "), cntMax); + else if (suiteMask & VER_SUITE_ENTERPRISE) + _tcsncat(szVersion, _T("Enterprise "), cntMax); + else + _tcsncat(szVersion, _T("Standard "), cntMax); + } + else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) + { + if (suiteMask & VER_SUITE_DATACENTER) + _tcsncat(szVersion, _T("Datacenter Edition "), cntMax); + else if (suiteMask & VER_SUITE_ENTERPRISE) + _tcsncat(szVersion, _T("Enterprise Edition "), cntMax); + else if (suiteMask == VER_SUITE_BLADE) + _tcsncat(szVersion, _T("Web Edition "), cntMax); + else + _tcsncat(szVersion, _T("Standard Edition "), cntMax); + } + else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) + { + if (suiteMask & VER_SUITE_DATACENTER) + _tcsncat(szVersion, _T("Datacenter Server "), cntMax); + else if (suiteMask & VER_SUITE_ENTERPRISE) + _tcsncat(szVersion, _T("Advanced Server "), cntMax); + else + _tcsncat(szVersion, _T("Server "), cntMax); + } + else // Windows NT 4.0 + { + if (suiteMask & VER_SUITE_ENTERPRISE) + _tcsncat(szVersion, _T("Server 4.0, Enterprise Edition "), cntMax); + else + _tcsncat(szVersion, _T("Server 4.0 "), cntMax); + } + } + } + + // Display service pack (if any) and build number. + if (osvi.dwMajorVersion == 4 && _tcsicmp(szCSDVersion, _T("Service Pack 6")) == 0) + { + HKEY hKey; + LONG lRet; + + // Test for SP6 versus SP6a. + lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Hotfix\\Q246009"), 0, KEY_QUERY_VALUE, &hKey); + if (lRet == ERROR_SUCCESS) + { + _stprintf(wszTmp, _T("Service Pack 6a (Version %d.%d, Build %d)"), + osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); + _tcsncat(szVersion, wszTmp, cntMax); + } + else // Windows NT 4.0 prior to SP6a + { + _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"), + szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); + _tcsncat(szVersion, wszTmp, cntMax); + } + ::RegCloseKey(hKey); + } + else // Windows NT 3.51 and earlier or Windows 2000 and later + { + if (!_tcslen(szCSDVersion)) + _stprintf(wszTmp, _T("(Version %d.%d, Build %d)"), + osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); + else + _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"), + szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); + _tcsncat(szVersion, wszTmp, cntMax); + } + break; + } + default: + _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"), + szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); + _tcsncat(szVersion, wszTmp, cntMax); + break; + } + + return TRUE; +} + +void WheatyExceptionReport::PrintSystemInfo() +{ + SYSTEM_INFO SystemInfo; + ::GetSystemInfo(&SystemInfo); + + MEMORYSTATUS MemoryStatus; + MemoryStatus.dwLength = sizeof (MEMORYSTATUS); + ::GlobalMemoryStatus(&MemoryStatus); + TCHAR sString[1024]; + _tprintf(_T("//=====================================================\r\n")); + if (_GetProcessorName(sString, countof(sString))) + _tprintf(_T("*** Hardware ***\r\nProcessor: %s\r\nNumber Of Processors: %d\r\nPhysical Memory: %d KB (Available: %d KB)\r\nCommit Charge Limit: %d KB\r\n"), + sString, SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400); + else + _tprintf(_T("*** Hardware ***\r\nProcessor: <unknown>\r\nNumber Of Processors: %d\r\nPhysical Memory: %d KB (Available: %d KB)\r\nCommit Charge Limit: %d KB\r\n"), + SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400); + + if (_GetWindowsVersion(sString, countof(sString))) + _tprintf(_T("\r\n*** Operation System ***\r\n%s\r\n"), sString); + else + _tprintf(_T("\r\n*** Operation System:\r\n<unknown>\r\n")); +} + +//=========================================================================== +void WheatyExceptionReport::printTracesForAllThreads(bool bWriteVariables) +{ + THREADENTRY32 te32; + + DWORD dwOwnerPID = GetCurrentProcessId(); + m_hProcess = GetCurrentProcess(); + // Take a snapshot of all running threads + HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (hThreadSnap == INVALID_HANDLE_VALUE) + return; + + // Fill in the size of the structure before using it. + te32.dwSize = sizeof(THREADENTRY32); + + // Retrieve information about the first thread, + // and exit if unsuccessful + if (!Thread32First(hThreadSnap, &te32)) + { + CloseHandle(hThreadSnap); // Must clean up the + // snapshot object! + return; + } + + // Now walk the thread list of the system, + // and display information about each thread + // associated with the specified process + do + { + if (te32.th32OwnerProcessID == dwOwnerPID) + { + CONTEXT context; + context.ContextFlags = 0xffffffff; + HANDLE threadHandle = OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, false, te32.th32ThreadID); + if (threadHandle) + { + if (GetThreadContext(threadHandle, &context)) + WriteStackDetails(&context, bWriteVariables, threadHandle); + CloseHandle(threadHandle); + } + } + } while (Thread32Next(hThreadSnap, &te32)); + +// Don't forget to clean up the snapshot object. + CloseHandle(hThreadSnap); +} + +//=========================================================================== +// Open the report file, and write the desired information to it. Called by +// WheatyUnhandledExceptionFilter +//=========================================================================== +void WheatyExceptionReport::GenerateExceptionReport( +PEXCEPTION_POINTERS pExceptionInfo) +{ + __try + { + SYSTEMTIME systime; + GetLocalTime(&systime); + + // Start out with a banner + _tprintf(_T("Revision: %s\r\n"), GitRevision::GetFullVersion()); + _tprintf(_T("Date %u:%u:%u. Time %u:%u \r\n"), systime.wDay, systime.wMonth, systime.wYear, systime.wHour, systime.wMinute); + PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; + + PrintSystemInfo(); + // First print information about the type of fault + _tprintf(_T("\r\n//=====================================================\r\n")); + _tprintf(_T("Exception code: %08X %s\r\n"), + pExceptionRecord->ExceptionCode, + GetExceptionString(pExceptionRecord->ExceptionCode)); + + // Now print information about where the fault occured + TCHAR szFaultingModule[MAX_PATH]; + DWORD section; + DWORD_PTR offset; + GetLogicalAddress(pExceptionRecord->ExceptionAddress, + szFaultingModule, + sizeof(szFaultingModule), + section, offset); + +#ifdef _M_IX86 + _tprintf(_T("Fault address: %08X %02X:%08X %s\r\n"), + pExceptionRecord->ExceptionAddress, + section, offset, szFaultingModule); +#endif +#ifdef _M_X64 + _tprintf(_T("Fault address: %016I64X %02X:%016I64X %s\r\n"), + pExceptionRecord->ExceptionAddress, + section, offset, szFaultingModule); +#endif + + PCONTEXT pCtx = pExceptionInfo->ContextRecord; + + // Show the registers +#ifdef _M_IX86 // X86 Only! + _tprintf(_T("\r\nRegisters:\r\n")); + + _tprintf(_T("EAX:%08X\r\nEBX:%08X\r\nECX:%08X\r\nEDX:%08X\r\nESI:%08X\r\nEDI:%08X\r\n") + , pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, + pCtx->Esi, pCtx->Edi); + + _tprintf(_T("CS:EIP:%04X:%08X\r\n"), pCtx->SegCs, pCtx->Eip); + _tprintf(_T("SS:ESP:%04X:%08X EBP:%08X\r\n"), + pCtx->SegSs, pCtx->Esp, pCtx->Ebp); + _tprintf(_T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"), + pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs); + _tprintf(_T("Flags:%08X\r\n"), pCtx->EFlags); +#endif + +#ifdef _M_X64 + _tprintf(_T("\r\nRegisters:\r\n")); + _tprintf(_T("RAX:%016I64X\r\nRBX:%016I64X\r\nRCX:%016I64X\r\nRDX:%016I64X\r\nRSI:%016I64X\r\nRDI:%016I64X\r\n") + _T("R8: %016I64X\r\nR9: %016I64X\r\nR10:%016I64X\r\nR11:%016I64X\r\nR12:%016I64X\r\nR13:%016I64X\r\nR14:%016I64X\r\nR15:%016I64X\r\n") + , pCtx->Rax, pCtx->Rbx, pCtx->Rcx, pCtx->Rdx, + pCtx->Rsi, pCtx->Rdi, pCtx->R9, pCtx->R10, pCtx->R11, pCtx->R12, pCtx->R13, pCtx->R14, pCtx->R15); + _tprintf(_T("CS:RIP:%04X:%016I64X\r\n"), pCtx->SegCs, pCtx->Rip); + _tprintf(_T("SS:RSP:%04X:%016X RBP:%08X\r\n"), + pCtx->SegSs, pCtx->Rsp, pCtx->Rbp); + _tprintf(_T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"), + pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs); + _tprintf(_T("Flags:%08X\r\n"), pCtx->EFlags); +#endif + + SymSetOptions(SYMOPT_DEFERRED_LOADS); + + // Initialize DbgHelp + if (!SymInitialize(GetCurrentProcess(), 0, TRUE)) + { + _tprintf(_T("\n\rCRITICAL ERROR.\n\r Couldn't initialize the symbol handler for process.\n\rError [%s].\n\r\n\r"), + ErrorMessage(GetLastError())); + } + + CONTEXT trashableContext = *pCtx; + + WriteStackDetails(&trashableContext, false, NULL); + printTracesForAllThreads(false); + + // #ifdef _M_IX86 // X86 Only! + + _tprintf(_T("========================\r\n")); + _tprintf(_T("Local Variables And Parameters\r\n")); + + trashableContext = *pCtx; + WriteStackDetails(&trashableContext, true, NULL); + printTracesForAllThreads(true); + + /*_tprintf(_T("========================\r\n")); + _tprintf(_T("Global Variables\r\n")); + + SymEnumSymbols(GetCurrentProcess(), + (UINT_PTR)GetModuleHandle(szFaultingModule), + 0, EnumerateSymbolsCallback, 0);*/ + // #endif // X86 Only! + + SymCleanup(GetCurrentProcess()); + + _tprintf(_T("\r\n")); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + _tprintf(_T("Error writing the crash log\r\n")); + } +} + +//====================================================================== +// Given an exception code, returns a pointer to a static string with a +// description of the exception +//====================================================================== +LPTSTR WheatyExceptionReport::GetExceptionString(DWORD dwCode) +{ + #define EXCEPTION(x) case EXCEPTION_##x: return _T(#x); + + switch (dwCode) + { + EXCEPTION(ACCESS_VIOLATION) + EXCEPTION(DATATYPE_MISALIGNMENT) + EXCEPTION(BREAKPOINT) + EXCEPTION(SINGLE_STEP) + EXCEPTION(ARRAY_BOUNDS_EXCEEDED) + EXCEPTION(FLT_DENORMAL_OPERAND) + EXCEPTION(FLT_DIVIDE_BY_ZERO) + EXCEPTION(FLT_INEXACT_RESULT) + EXCEPTION(FLT_INVALID_OPERATION) + EXCEPTION(FLT_OVERFLOW) + EXCEPTION(FLT_STACK_CHECK) + EXCEPTION(FLT_UNDERFLOW) + EXCEPTION(INT_DIVIDE_BY_ZERO) + EXCEPTION(INT_OVERFLOW) + EXCEPTION(PRIV_INSTRUCTION) + EXCEPTION(IN_PAGE_ERROR) + EXCEPTION(ILLEGAL_INSTRUCTION) + EXCEPTION(NONCONTINUABLE_EXCEPTION) + EXCEPTION(STACK_OVERFLOW) + EXCEPTION(INVALID_DISPOSITION) + EXCEPTION(GUARD_PAGE) + EXCEPTION(INVALID_HANDLE) + } + + // If not one of the "known" exceptions, try to get the string + // from NTDLL.DLL's message table. + + static TCHAR szBuffer[512] = { 0 }; + + FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, + GetModuleHandle(_T("NTDLL.DLL")), + dwCode, 0, szBuffer, sizeof(szBuffer), 0); + + return szBuffer; +} + +//============================================================================= +// Given a linear address, locates the module, section, and offset containing +// that address. +// +// Note: the szModule paramater buffer is an output buffer of length specified +// by the len parameter (in characters!) +//============================================================================= +BOOL WheatyExceptionReport::GetLogicalAddress( +PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD_PTR& offset) +{ + MEMORY_BASIC_INFORMATION mbi; + + if (!VirtualQuery(addr, &mbi, sizeof(mbi))) + return FALSE; + + DWORD_PTR hMod = (DWORD_PTR)mbi.AllocationBase; + + if (!hMod) + return FALSE; + + if (!GetModuleFileName((HMODULE)hMod, szModule, len)) + return FALSE; + + // Point to the DOS header in memory + PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod; + + // From the DOS header, find the NT (PE) header + PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + DWORD_PTR(pDosHdr->e_lfanew)); + + PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHdr); + + DWORD_PTR rva = (DWORD_PTR)addr - hMod; // RVA is offset from module load address + + // Iterate through the section table, looking for the one that encompasses + // the linear address. + for (unsigned i = 0; + i < pNtHdr->FileHeader.NumberOfSections; + i++, pSection++) + { + DWORD_PTR sectionStart = pSection->VirtualAddress; + DWORD_PTR sectionEnd = sectionStart + + DWORD_PTR(std::max(pSection->SizeOfRawData, pSection->Misc.VirtualSize)); + + // Is the address in this section??? + if ((rva >= sectionStart) && (rva <= sectionEnd)) + { + // Yes, address is in the section. Calculate section and offset, + // and store in the "section" & "offset" params, which were + // passed by reference. + section = i+1; + offset = rva - sectionStart; + return TRUE; + } + } + + return FALSE; // Should never get here! +} + +// It contains SYMBOL_INFO structure plus additional +// space for the name of the symbol +struct CSymbolInfoPackage : public SYMBOL_INFO_PACKAGE +{ + CSymbolInfoPackage() + { + si.SizeOfStruct = sizeof(SYMBOL_INFO); + si.MaxNameLen = sizeof(name); + } +}; + +//============================================================ +// Walks the stack, and writes the results to the report file +//============================================================ +void WheatyExceptionReport::WriteStackDetails( +PCONTEXT pContext, +bool bWriteVariables, HANDLE pThreadHandle) // true if local/params should be output +{ + _tprintf(_T("\r\nCall stack:\r\n")); + + _tprintf(_T("Address Frame Function SourceFile\r\n")); + + DWORD dwMachineType = 0; + // Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag + + STACKFRAME64 sf; + memset(&sf, 0, sizeof(sf)); + + #ifdef _M_IX86 + // Initialize the STACKFRAME structure for the first call. This is only + // necessary for Intel CPUs, and isn't mentioned in the documentation. + sf.AddrPC.Offset = pContext->Eip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrStack.Offset = pContext->Esp; + sf.AddrStack.Mode = AddrModeFlat; + sf.AddrFrame.Offset = pContext->Ebp; + sf.AddrFrame.Mode = AddrModeFlat; + + dwMachineType = IMAGE_FILE_MACHINE_I386; + #endif + +#ifdef _M_X64 + sf.AddrPC.Offset = pContext->Rip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrStack.Offset = pContext->Rsp; + sf.AddrStack.Mode = AddrModeFlat; + sf.AddrFrame.Offset = pContext->Rbp; + sf.AddrFrame.Mode = AddrModeFlat; + dwMachineType = IMAGE_FILE_MACHINE_AMD64; +#endif + + for (;;) + { + // Get the next stack frame + if (! StackWalk64(dwMachineType, + m_hProcess, + pThreadHandle != NULL ? pThreadHandle : GetCurrentThread(), + &sf, + pContext, + 0, + SymFunctionTableAccess64, + SymGetModuleBase64, + 0)) + break; + if (0 == sf.AddrFrame.Offset) // Basic sanity check to make sure + break; // the frame is OK. Bail if not. +#ifdef _M_IX86 + _tprintf(_T("%08X %08X "), sf.AddrPC.Offset, sf.AddrFrame.Offset); +#endif +#ifdef _M_X64 + _tprintf(_T("%016I64X %016I64X "), sf.AddrPC.Offset, sf.AddrFrame.Offset); +#endif + + DWORD64 symDisplacement = 0; // Displacement of the input address, + // relative to the start of the symbol + + // Get the name of the function for this stack frame entry + CSymbolInfoPackage sip; + if (SymFromAddr( + m_hProcess, // Process handle of the current process + sf.AddrPC.Offset, // Symbol address + &symDisplacement, // Address of the variable that will receive the displacement + &sip.si)) // Address of the SYMBOL_INFO structure (inside "sip" object) + { + _tprintf(_T("%hs+%I64X"), sip.si.Name, symDisplacement); + + } + else // No symbol found. Print out the logical address instead. + { + TCHAR szModule[MAX_PATH] = _T(""); + DWORD section = 0; + DWORD_PTR offset = 0; + + GetLogicalAddress((PVOID)sf.AddrPC.Offset, + szModule, sizeof(szModule), section, offset); +#ifdef _M_IX86 + _tprintf(_T("%04X:%08X %s"), section, offset, szModule); +#endif +#ifdef _M_X64 + _tprintf(_T("%04X:%016I64X %s"), section, offset, szModule); +#endif + } + + // Get the source line for this stack frame entry + IMAGEHLP_LINE64 lineInfo = { sizeof(IMAGEHLP_LINE64) }; + DWORD dwLineDisplacement; + if (SymGetLineFromAddr64(m_hProcess, sf.AddrPC.Offset, + &dwLineDisplacement, &lineInfo)) + { + _tprintf(_T(" %s line %u"), lineInfo.FileName, lineInfo.LineNumber); + } + + _tprintf(_T("\r\n")); + + // Write out the variables, if desired + if (bWriteVariables) + { + // Use SymSetContext to get just the locals/params for this frame + IMAGEHLP_STACK_FRAME imagehlpStackFrame; + imagehlpStackFrame.InstructionOffset = sf.AddrPC.Offset; + SymSetContext(m_hProcess, &imagehlpStackFrame, 0); + + // Enumerate the locals/parameters + SymEnumSymbols(m_hProcess, 0, 0, EnumerateSymbolsCallback, &sf); + + _tprintf(_T("\r\n")); + } + } + +} + +////////////////////////////////////////////////////////////////////////////// +// The function invoked by SymEnumSymbols +////////////////////////////////////////////////////////////////////////////// + +BOOL CALLBACK +WheatyExceptionReport::EnumerateSymbolsCallback( +PSYMBOL_INFO pSymInfo, +ULONG /*SymbolSize*/, +PVOID UserContext) +{ + + char szBuffer[WER_LARGE_BUFFER_SIZE]; + memset(szBuffer, 0, sizeof(szBuffer)); + + __try + { + ClearSymbols(); + if (FormatSymbolValue(pSymInfo, (STACKFRAME64*)UserContext, + szBuffer, sizeof(szBuffer))) + _tprintf(_T("%s"), szBuffer); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + _tprintf(_T("punting on symbol %s, partial output:\r\n"), pSymInfo->Name); + if (szBuffer[0] != '\0') + _tprintf(_T("%s"), szBuffer); + } + + return TRUE; +} + +////////////////////////////////////////////////////////////////////////////// +// Given a SYMBOL_INFO representing a particular variable, displays its +// contents. If it's a user defined type, display the members and their +// values. +////////////////////////////////////////////////////////////////////////////// +bool WheatyExceptionReport::FormatSymbolValue( +PSYMBOL_INFO pSym, +STACKFRAME64 * sf, +char * pszBuffer, +unsigned /*cbBuffer*/) +{ + char * pszCurrBuffer = pszBuffer; + + // If it's a function, don't do anything. + if (pSym->Tag == SymTagFunction) // SymTagFunction from CVCONST.H from the DIA SDK + return false; + + DWORD_PTR pVariable = 0; // Will point to the variable's data in memory + + if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGRELATIVE) + { + // if (pSym->Register == 8) // EBP is the value 8 (in DBGHELP 5.1) + { // This may change!!! +#ifdef _M_IX86 + pVariable = sf->AddrFrame.Offset; +#elif _M_X64 + pVariable = sf->AddrStack.Offset; +#endif + pVariable += (DWORD_PTR)pSym->Address; + } + // else + // return false; + } + else if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGISTER) + return false; // Don't try to report register variable + else + { + pVariable = (DWORD_PTR)pSym->Address; // It must be a global variable + } + + pszCurrBuffer = PushSymbolDetail(pszCurrBuffer); + + // Indicate if the variable is a local or parameter + if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_PARAMETER) + symbolDetails.top().Prefix = "Parameter "; + else if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_LOCAL) + symbolDetails.top().Prefix = "Local "; + + // Determine if the variable is a user defined type (UDT). IF so, bHandled + // will return true. + bool bHandled; + pszCurrBuffer = DumpTypeIndex(pszCurrBuffer, pSym->ModBase, pSym->TypeIndex, + 0, pVariable, bHandled, pSym->Name, "", false, true); + + if (!bHandled) + { + // The symbol wasn't a UDT, so do basic, stupid formatting of the + // variable. Based on the size, we're assuming it's a char, WORD, or + // DWORD. + BasicType basicType = GetBasicType(pSym->TypeIndex, pSym->ModBase); + if (symbolDetails.top().Type.empty()) + symbolDetails.top().Type = rgBaseType[basicType]; + + // Emit the variable name + if (pSym->Name[0] != '\0') + symbolDetails.top().Name = pSym->Name; + + char buffer[50]; + FormatOutputValue(buffer, basicType, pSym->Size, (PVOID)pVariable, sizeof(buffer)); + symbolDetails.top().Value = buffer; + } + + pszCurrBuffer = PopSymbolDetail(pszCurrBuffer); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// If it's a user defined type (UDT), recurse through its members until we're +// at fundamental types. When he hit fundamental types, return +// bHandled = false, so that FormatSymbolValue() will format them. +////////////////////////////////////////////////////////////////////////////// +char * WheatyExceptionReport::DumpTypeIndex( +char * pszCurrBuffer, +DWORD64 modBase, +DWORD dwTypeIndex, +unsigned nestingLevel, +DWORD_PTR offset, +bool & bHandled, +const char* Name, +char* /*suffix*/, +bool newSymbol, +bool logChildren) +{ + bHandled = false; + + if (newSymbol) + pszCurrBuffer = PushSymbolDetail(pszCurrBuffer); + + DWORD typeTag; + if (!SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_SYMTAG, &typeTag)) + return pszCurrBuffer; + + // Get the name of the symbol. This will either be a Type name (if a UDT), + // or the structure member name. + WCHAR * pwszTypeName; + if (SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_SYMNAME, + &pwszTypeName)) + { + // handle special cases + if (wcscmp(pwszTypeName, L"std::basic_string<char,std::char_traits<char>,std::allocator<char> >") == 0) + { + LocalFree(pwszTypeName); + symbolDetails.top().Type = "std::string"; + char buffer[50]; + FormatOutputValue(buffer, btStdString, 0, (PVOID)offset, sizeof(buffer)); + symbolDetails.top().Value = buffer; + if (Name != NULL && Name[0] != '\0') + symbolDetails.top().Name = Name; + bHandled = true; + return pszCurrBuffer; + } + + char buffer[200]; + wcstombs(buffer, pwszTypeName, sizeof(buffer)); + buffer[199] = '\0'; + if (Name != NULL && Name[0] != '\0') + { + symbolDetails.top().Type = buffer; + symbolDetails.top().Name = Name; + } + else if (buffer[0] != '\0') + symbolDetails.top().Name = buffer; + + LocalFree(pwszTypeName); + } + else if (Name != NULL && Name[0] != '\0') + symbolDetails.top().Name = Name; + + if (!StoreSymbol(dwTypeIndex, offset)) + { + // Skip printing address and base class if it has been printed already + if (typeTag == SymTagBaseClass) + bHandled = true; + return pszCurrBuffer; + } + + DWORD innerTypeID; + switch (typeTag) + { + case SymTagPointerType: + if (SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_TYPEID, &innerTypeID)) + { + if (Name != NULL && Name[0] != '\0') + symbolDetails.top().Name = Name; + + BOOL isReference; + SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_IS_REFERENCE, &isReference); + + char addressStr[40]; + memset(addressStr, 0, sizeof(addressStr)); + + if (isReference) + symbolDetails.top().Suffix += "&"; + else + symbolDetails.top().Suffix += "*"; + + // Try to dereference the pointer in a try/except block since it might be invalid + DWORD_PTR address = DereferenceUnsafePointer(offset); + + char buffer[50]; + FormatOutputValue(buffer, btVoid, sizeof(PVOID), (PVOID)offset, sizeof(buffer)); + symbolDetails.top().Value = buffer; + + if (nestingLevel >= WER_MAX_NESTING_LEVEL) + logChildren = false; + + // no need to log any children since the address is invalid anyway + if (address == NULL || address == DWORD_PTR(-1)) + logChildren = false; + + pszCurrBuffer = DumpTypeIndex(pszCurrBuffer, modBase, innerTypeID, nestingLevel + 1, + address, bHandled, Name, addressStr, false, logChildren); + + if (!bHandled) + { + BasicType basicType = GetBasicType(dwTypeIndex, modBase); + if (symbolDetails.top().Type.empty()) + symbolDetails.top().Type = rgBaseType[basicType]; + + if (address == NULL) + symbolDetails.top().Value = "NULL"; + else if (address == DWORD_PTR(-1)) + symbolDetails.top().Value = "<Unable to read memory>"; + else + { + // Get the size of the child member + ULONG64 length; + SymGetTypeInfo(m_hProcess, modBase, innerTypeID, TI_GET_LENGTH, &length); + char buffer2[50]; + FormatOutputValue(buffer2, basicType, length, (PVOID)address, sizeof(buffer)); + symbolDetails.top().Value = buffer2; + } + bHandled = true; + return pszCurrBuffer; + } + else if (address == NULL) + symbolDetails.top().Value = "NULL"; + else if (address == DWORD_PTR(-1)) + { + symbolDetails.top().Value = "<Unable to read memory>"; + bHandled = true; + return pszCurrBuffer; + } + } + break; + case SymTagData: + if (SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_TYPEID, &innerTypeID)) + { + DWORD innerTypeTag; + if (!SymGetTypeInfo(m_hProcess, modBase, innerTypeID, TI_GET_SYMTAG, &innerTypeTag)) + break; + + switch (innerTypeTag) + { + case SymTagUDT: + if (nestingLevel >= WER_MAX_NESTING_LEVEL) + logChildren = false; + pszCurrBuffer = DumpTypeIndex(pszCurrBuffer, modBase, innerTypeID, nestingLevel + 1, + offset, bHandled, symbolDetails.top().Name.c_str(), "", false, logChildren); + break; + case SymTagPointerType: + if (Name != NULL && Name[0] != '\0') + symbolDetails.top().Name = Name; + pszCurrBuffer = DumpTypeIndex(pszCurrBuffer, modBase, innerTypeID, nestingLevel + 1, + offset, bHandled, symbolDetails.top().Name.c_str(), "", false, logChildren); + break; + case SymTagArrayType: + pszCurrBuffer = DumpTypeIndex(pszCurrBuffer, modBase, innerTypeID, nestingLevel + 1, + offset, bHandled, symbolDetails.top().Name.c_str(), "", false, logChildren); + break; + default: + break; + } + } + break; + case SymTagArrayType: + if (SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_TYPEID, &innerTypeID)) + { + symbolDetails.top().HasChildren = true; + + BasicType basicType = btNoType; + pszCurrBuffer = DumpTypeIndex(pszCurrBuffer, modBase, innerTypeID, nestingLevel + 1, + offset, bHandled, Name, "", false, false); + + // Set Value back to an empty string since the Array object itself has no value, only its elements have + symbolDetails.top().Value.clear(); + + DWORD elementsCount; + if (SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_COUNT, &elementsCount)) + symbolDetails.top().Suffix += "[" + std::to_string(elementsCount) + "]"; + else + symbolDetails.top().Suffix += "[<unknown count>]"; + + if (!bHandled) + { + basicType = GetBasicType(dwTypeIndex, modBase); + if (symbolDetails.top().Type.empty()) + symbolDetails.top().Type = rgBaseType[basicType]; + bHandled = true; + } + + // Get the size of the child member + ULONG64 length; + SymGetTypeInfo(m_hProcess, modBase, innerTypeID, TI_GET_LENGTH, &length); + + char buffer[50]; + switch (basicType) + { + case btChar: + case btStdString: + FormatOutputValue(buffer, basicType, length, (PVOID)offset, sizeof(buffer), elementsCount); + symbolDetails.top().Value = buffer; + break; + default: + for (DWORD index = 0; index < elementsCount && index < WER_MAX_ARRAY_ELEMENTS_COUNT; index++) + { + pszCurrBuffer = PushSymbolDetail(pszCurrBuffer); + symbolDetails.top().Suffix += "[" + std::to_string(index) + "]"; + FormatOutputValue(buffer, basicType, length, (PVOID)(offset + length * index), sizeof(buffer)); + symbolDetails.top().Value = buffer; + pszCurrBuffer = PopSymbolDetail(pszCurrBuffer); + } + break; + } + + return pszCurrBuffer; + } + break; + case SymTagBaseType: + break; + case SymTagEnum: + return pszCurrBuffer; + default: + break; + } + + // Determine how many children this type has. + DWORD dwChildrenCount = 0; + SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_CHILDRENCOUNT, &dwChildrenCount); + + if (!dwChildrenCount) // If no children, we're done + return pszCurrBuffer; + + // Prepare to get an array of "TypeIds", representing each of the children. + // SymGetTypeInfo(TI_FINDCHILDREN) expects more memory than just a + // TI_FINDCHILDREN_PARAMS struct has. Use derivation to accomplish this. + struct FINDCHILDREN : TI_FINDCHILDREN_PARAMS + { + ULONG MoreChildIds[1024*2]; + FINDCHILDREN(){Count = sizeof(MoreChildIds) / sizeof(MoreChildIds[0]);} + } children; + + children.Count = dwChildrenCount; + children.Start= 0; + + // Get the array of TypeIds, one for each child type + if (!SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_FINDCHILDREN, + &children)) + { + return pszCurrBuffer; + } + + // Iterate through each of the children + for (unsigned i = 0; i < dwChildrenCount; i++) + { + DWORD symTag; + SymGetTypeInfo(m_hProcess, modBase, children.ChildId[i], TI_GET_SYMTAG, &symTag); + + if (symTag == SymTagFunction || + symTag == SymTagEnum || + symTag == SymTagTypedef || + symTag == SymTagVTable) + continue; + + // Ignore static fields + DWORD dataKind; + SymGetTypeInfo(m_hProcess, modBase, children.ChildId[i], TI_GET_DATAKIND, &dataKind); + if (dataKind == DataIsStaticLocal || + dataKind == DataIsGlobal || + dataKind == DataIsStaticMember) + continue; + + + symbolDetails.top().HasChildren = true; + if (!logChildren) + { + bHandled = false; + return pszCurrBuffer; + } + + // Recurse for each of the child types + bool bHandled2; + BasicType basicType = GetBasicType(children.ChildId[i], modBase); + + // Get the offset of the child member, relative to its parent + DWORD dwMemberOffset; + SymGetTypeInfo(m_hProcess, modBase, children.ChildId[i], + TI_GET_OFFSET, &dwMemberOffset); + + // Calculate the address of the member + DWORD_PTR dwFinalOffset = offset + dwMemberOffset; + + pszCurrBuffer = DumpTypeIndex(pszCurrBuffer, modBase, + children.ChildId[i], nestingLevel+1, + dwFinalOffset, bHandled2, ""/*Name */, "", true, true); + + // If the child wasn't a UDT, format it appropriately + if (!bHandled2) + { + if (symbolDetails.top().Type.empty()) + symbolDetails.top().Type = rgBaseType[basicType]; + + // Get the real "TypeId" of the child. We need this for the + // SymGetTypeInfo(TI_GET_TYPEID) call below. + DWORD typeId; + SymGetTypeInfo(m_hProcess, modBase, children.ChildId[i], + TI_GET_TYPEID, &typeId); + + // Get the size of the child member + ULONG64 length; + SymGetTypeInfo(m_hProcess, modBase, typeId, TI_GET_LENGTH, &length); + + char buffer[50]; + FormatOutputValue(buffer, basicType, length, (PVOID)dwFinalOffset, sizeof(buffer)); + symbolDetails.top().Value = buffer; + } + + pszCurrBuffer = PopSymbolDetail(pszCurrBuffer); + } + + bHandled = true; + return pszCurrBuffer; +} + +void WheatyExceptionReport::FormatOutputValue(char * pszCurrBuffer, +BasicType basicType, +DWORD64 length, +PVOID pAddress, +size_t bufferSize, +size_t countOverride) +{ + __try + { + switch (basicType) + { + case btChar: + { + // Special case handling for char[] type + if (countOverride != 0) + length = countOverride; + else + length = strlen((char*)pAddress); + if (length > bufferSize - 6) + pszCurrBuffer += sprintf(pszCurrBuffer, "\"%.*s...\"", (DWORD)(bufferSize - 6), (char*)pAddress); + else + pszCurrBuffer += sprintf(pszCurrBuffer, "\"%.*s\"", (DWORD)length, (char*)pAddress); + break; + } + case btStdString: + { + std::string* value = static_cast<std::string*>(pAddress); + if (value->length() > bufferSize - 6) + pszCurrBuffer += sprintf(pszCurrBuffer, "\"%.*s...\"", (DWORD)(bufferSize - 6), value->c_str()); + else + pszCurrBuffer += sprintf(pszCurrBuffer, "\"%s\"", value->c_str()); + break; + } + default: + // Format appropriately (assuming it's a 1, 2, or 4 bytes (!!!) + if (length == 1) + pszCurrBuffer += sprintf(pszCurrBuffer, "0x%X", *(PBYTE)pAddress); + else if (length == 2) + pszCurrBuffer += sprintf(pszCurrBuffer, "0x%X", *(PWORD)pAddress); + else if (length == 4) + { + if (basicType == btFloat) + pszCurrBuffer += sprintf(pszCurrBuffer, "%f", *(PFLOAT)pAddress); + else + pszCurrBuffer += sprintf(pszCurrBuffer, "0x%X", *(PDWORD)pAddress); + } + else if (length == 8) + { + if (basicType == btFloat) + { + pszCurrBuffer += sprintf(pszCurrBuffer, "%f", + *(double *)pAddress); + } + else + pszCurrBuffer += sprintf(pszCurrBuffer, "0x%I64X", + *(DWORD64*)pAddress); + } + else + { + #if _WIN64 + pszCurrBuffer += sprintf(pszCurrBuffer, "0x%I64X", (DWORD64)pAddress); + #else + pszCurrBuffer += sprintf(pszCurrBuffer, "0x%X", (DWORD)pAddress); + #endif + } + break; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { +#if _WIN64 + pszCurrBuffer += sprintf(pszCurrBuffer, "0x%I64X <Unable to read memory>", (DWORD64)pAddress); +#else + pszCurrBuffer += sprintf(pszCurrBuffer, "0x%X <Unable to read memory>", (DWORD)pAddress); +#endif + } +} + +BasicType +WheatyExceptionReport::GetBasicType(DWORD typeIndex, DWORD64 modBase) +{ + BasicType basicType; + if (SymGetTypeInfo(m_hProcess, modBase, typeIndex, + TI_GET_BASETYPE, &basicType)) + { + return basicType; + } + + // Get the real "TypeId" of the child. We need this for the + // SymGetTypeInfo(TI_GET_TYPEID) call below. + DWORD typeId; + if (SymGetTypeInfo(m_hProcess, modBase, typeIndex, TI_GET_TYPEID, &typeId)) + { + if (SymGetTypeInfo(m_hProcess, modBase, typeId, TI_GET_BASETYPE, + &basicType)) + { + return basicType; + } + } + + return btNoType; +} + +DWORD_PTR WheatyExceptionReport::DereferenceUnsafePointer(DWORD_PTR address) +{ + __try + { + return *(PDWORD_PTR)address; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return DWORD_PTR(-1); + } +} + +//============================================================================ +// Helper function that writes to the report file, and allows the user to use +// printf style formating +//============================================================================ +int __cdecl WheatyExceptionReport::_tprintf(const TCHAR * format, ...) +{ + int retValue; + va_list argptr; + va_start(argptr, format); + if (stackOverflowException) + { + retValue = heapprintf(format, argptr); + va_end(argptr); + } + else + { + retValue = stackprintf(format, argptr); + va_end(argptr); + } + + return retValue; +} + +int __cdecl WheatyExceptionReport::stackprintf(const TCHAR * format, va_list argptr) +{ + int retValue; + DWORD cbWritten; + + TCHAR szBuff[WER_LARGE_BUFFER_SIZE]; + retValue = vsprintf(szBuff, format, argptr); + WriteFile(m_hReportFile, szBuff, retValue * sizeof(TCHAR), &cbWritten, 0); + + return retValue; +} + +int __cdecl WheatyExceptionReport::heapprintf(const TCHAR * format, va_list argptr) +{ + int retValue; + DWORD cbWritten; + TCHAR* szBuff = (TCHAR*)malloc(sizeof(TCHAR) * WER_LARGE_BUFFER_SIZE); + retValue = vsprintf(szBuff, format, argptr); + WriteFile(m_hReportFile, szBuff, retValue * sizeof(TCHAR), &cbWritten, 0); + free(szBuff); + + return retValue; +} + +bool WheatyExceptionReport::StoreSymbol(DWORD type, DWORD_PTR offset) +{ + return symbols.insert(SymbolPair(type, offset)).second; +} + +void WheatyExceptionReport::ClearSymbols() +{ + symbols.clear(); + while (!symbolDetails.empty()) + symbolDetails.pop(); +} + +char* WheatyExceptionReport::PushSymbolDetail(char* pszCurrBuffer) +{ + // Log current symbol and then add another to the stack to keep the hierarchy format + pszCurrBuffer = PrintSymbolDetail(pszCurrBuffer); + symbolDetails.emplace(); + return pszCurrBuffer; +} + +char* WheatyExceptionReport::PopSymbolDetail(char* pszCurrBuffer) +{ + pszCurrBuffer = PrintSymbolDetail(pszCurrBuffer); + symbolDetails.pop(); + return pszCurrBuffer; +} + +char* WheatyExceptionReport::PrintSymbolDetail(char* pszCurrBuffer) +{ + if (symbolDetails.empty()) + return pszCurrBuffer; + + // Don't log anything if has been logged already or if it's empty + if (symbolDetails.top().Logged || symbolDetails.top().empty()) + return pszCurrBuffer; + + // Add appropriate indentation level (since this routine is recursive) + for (size_t i = 0; i < symbolDetails.size(); i++) + pszCurrBuffer += sprintf(pszCurrBuffer, "\t"); + + pszCurrBuffer += sprintf(pszCurrBuffer, "%s\r\n", symbolDetails.top().ToString().c_str()); + + return pszCurrBuffer; +} + +#endif // _WIN32 diff --git a/src/common/Debugging/WheatyExceptionReport.h b/src/common/Debugging/WheatyExceptionReport.h new file mode 100644 index 00000000000..eb62d8bceef --- /dev/null +++ b/src/common/Debugging/WheatyExceptionReport.h @@ -0,0 +1,214 @@ +#ifndef _WHEATYEXCEPTIONREPORT_ +#define _WHEATYEXCEPTIONREPORT_ + +#if PLATFORM == PLATFORM_WINDOWS && !defined(__MINGW32__) + +#include <winnt.h> +#include <winternl.h> +#include <dbghelp.h> +#include <set> +#include <stdlib.h> +#include <stack> +#include <mutex> +#define countof _countof + +#define WER_MAX_ARRAY_ELEMENTS_COUNT 10 +#define WER_MAX_NESTING_LEVEL 5 +#define WER_LARGE_BUFFER_SIZE 1024 * 128 + +enum BasicType // Stolen from CVCONST.H in the DIA 2.0 SDK +{ + btNoType = 0, + btVoid = 1, + btChar = 2, + btWChar = 3, + btInt = 6, + btUInt = 7, + btFloat = 8, + btBCD = 9, + btBool = 10, + btLong = 13, + btULong = 14, + btCurrency = 25, + btDate = 26, + btVariant = 27, + btComplex = 28, + btBit = 29, + btBSTR = 30, + btHresult = 31, + + // Custom types + btStdString = 101 +}; + +enum DataKind // Stolen from CVCONST.H in the DIA 2.0 SDK +{ + DataIsUnknown, + DataIsLocal, + DataIsStaticLocal, + DataIsParam, + DataIsObjectPtr, + DataIsFileStatic, + DataIsGlobal, + DataIsMember, + DataIsStaticMember, + DataIsConstant +}; + +const char* const rgBaseType[] = +{ + "<user defined>", // btNoType = 0, + "void", // btVoid = 1, + "char",//char* // btChar = 2, + "wchar_t*", // btWChar = 3, + "signed char", + "unsigned char", + "int", // btInt = 6, + "unsigned int", // btUInt = 7, + "float", // btFloat = 8, + "<BCD>", // btBCD = 9, + "bool", // btBool = 10, + "short", + "unsigned short", + "long", // btLong = 13, + "unsigned long", // btULong = 14, + "int8", + "int16", + "int32", + "int64", + "int128", + "uint8", + "uint16", + "uint32", + "uint64", + "uint128", + "<currency>", // btCurrency = 25, + "<date>", // btDate = 26, + "VARIANT", // btVariant = 27, + "<complex>", // btComplex = 28, + "<bit>", // btBit = 29, + "BSTR", // btBSTR = 30, + "HRESULT" // btHresult = 31 +}; + +struct SymbolPair +{ + SymbolPair(DWORD type, DWORD_PTR offset) + { + _type = type; + _offset = offset; + } + + bool operator<(const SymbolPair& other) const + { + return _offset < other._offset || + (_offset == other._offset && _type < other._type); + } + + DWORD _type; + DWORD_PTR _offset; +}; +typedef std::set<SymbolPair> SymbolPairs; + +struct SymbolDetail +{ + SymbolDetail() : Prefix(), Type(), Suffix(), Name(), Value(), Logged(false), HasChildren(false) {} + + std::string ToString() + { + Logged = true; + std::string formatted = Prefix + Type + Suffix; + if (!Name.empty()) + { + if (!formatted.empty()) + formatted += " "; + formatted += Name; + } + if (!Value.empty()) + formatted += " = " + Value; + return formatted; + } + + bool empty() const + { + return Value.empty() && !HasChildren; + } + + std::string Prefix; + std::string Type; + std::string Suffix; + std::string Name; + std::string Value; + bool Logged; + bool HasChildren; +}; + +class WheatyExceptionReport +{ + public: + + WheatyExceptionReport(); + ~WheatyExceptionReport(); + + // entry point where control comes on an unhandled exception + static LONG WINAPI WheatyUnhandledExceptionFilter( + PEXCEPTION_POINTERS pExceptionInfo); + + static void printTracesForAllThreads(bool); + private: + // where report info is extracted and generated + static void GenerateExceptionReport(PEXCEPTION_POINTERS pExceptionInfo); + static void PrintSystemInfo(); + static BOOL _GetWindowsVersion(TCHAR* szVersion, DWORD cntMax); + static BOOL _GetProcessorName(TCHAR* sProcessorName, DWORD maxcount); + + // Helper functions + static LPTSTR GetExceptionString(DWORD dwCode); + static BOOL GetLogicalAddress(PVOID addr, PTSTR szModule, DWORD len, + DWORD& section, DWORD_PTR& offset); + + static void WriteStackDetails(PCONTEXT pContext, bool bWriteVariables, HANDLE pThreadHandle); + + static BOOL CALLBACK EnumerateSymbolsCallback(PSYMBOL_INFO, ULONG, PVOID); + + static bool FormatSymbolValue(PSYMBOL_INFO, STACKFRAME64 *, char * pszBuffer, unsigned cbBuffer); + + static char * DumpTypeIndex(char *, DWORD64, DWORD, unsigned, DWORD_PTR, bool &, const char*, char*, bool, bool); + + static void FormatOutputValue(char * pszCurrBuffer, BasicType basicType, DWORD64 length, PVOID pAddress, size_t bufferSize, size_t countOverride = 0); + + static BasicType GetBasicType(DWORD typeIndex, DWORD64 modBase); + static DWORD_PTR DereferenceUnsafePointer(DWORD_PTR address); + + static int __cdecl _tprintf(const TCHAR * format, ...); + static int __cdecl stackprintf(const TCHAR * format, va_list argptr); + static int __cdecl heapprintf(const TCHAR * format, va_list argptr); + + static bool StoreSymbol(DWORD type , DWORD_PTR offset); + static void ClearSymbols(); + + // Variables used by the class + static TCHAR m_szLogFileName[MAX_PATH]; + static TCHAR m_szDumpFileName[MAX_PATH]; + static LPTOP_LEVEL_EXCEPTION_FILTER m_previousFilter; + static HANDLE m_hReportFile; + static HANDLE m_hDumpFile; + static HANDLE m_hProcess; + static SymbolPairs symbols; + static std::stack<SymbolDetail> symbolDetails; + static bool stackOverflowException; + static bool alreadyCrashed; + static std::mutex alreadyCrashedLock; + typedef NTSTATUS(NTAPI* pRtlGetVersion)(PRTL_OSVERSIONINFOW lpVersionInformation); + static pRtlGetVersion RtlGetVersion; + + static char* PushSymbolDetail(char* pszCurrBuffer); + static char* PopSymbolDetail(char* pszCurrBuffer); + static char* PrintSymbolDetail(char* pszCurrBuffer); + +}; + +extern WheatyExceptionReport g_WheatyExceptionReport; // global instance of class +#endif // _WIN32 +#endif // _WHEATYEXCEPTIONREPORT_ + diff --git a/src/common/Define.h b/src/common/Define.h new file mode 100644 index 00000000000..7332bdc4cff --- /dev/null +++ b/src/common/Define.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_DEFINE_H +#define TRINITY_DEFINE_H + +#include "CompilerDefs.h" + +#if COMPILER == COMPILER_GNU +# if !defined(__STDC_FORMAT_MACROS) +# define __STDC_FORMAT_MACROS +# endif +# if !defined(__STDC_CONSTANT_MACROS) +# define __STDC_CONSTANT_MACROS +# endif +# if !defined(_GLIBCXX_USE_NANOSLEEP) +# define _GLIBCXX_USE_NANOSLEEP +# endif +# if defined(HELGRIND) +# include <valgrind/helgrind.h> +# undef _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE +# undef _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER +# define _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(A) ANNOTATE_HAPPENS_BEFORE(A) +# define _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(A) ANNOTATE_HAPPENS_AFTER(A) +# endif +#endif + +#include <cstddef> +#include <cinttypes> +#include <climits> + +#define TRINITY_LITTLEENDIAN 0 +#define TRINITY_BIGENDIAN 1 + +#if !defined(TRINITY_ENDIAN) +# if defined (BOOST_BIG_ENDIAN) +# define TRINITY_ENDIAN TRINITY_BIGENDIAN +# else +# define TRINITY_ENDIAN TRINITY_LITTLEENDIAN +# endif +#endif + +#if PLATFORM == PLATFORM_WINDOWS +# define TRINITY_PATH_MAX MAX_PATH +# define _USE_MATH_DEFINES +# ifndef DECLSPEC_NORETURN +# define DECLSPEC_NORETURN __declspec(noreturn) +# endif //DECLSPEC_NORETURN +# ifndef DECLSPEC_DEPRECATED +# define DECLSPEC_DEPRECATED __declspec(deprecated) +# endif //DECLSPEC_DEPRECATED +#else //PLATFORM != PLATFORM_WINDOWS +# define TRINITY_PATH_MAX PATH_MAX +# define DECLSPEC_NORETURN +# define DECLSPEC_DEPRECATED +#endif //PLATFORM + +#if !defined(COREDEBUG) +# define TRINITY_INLINE inline +#else //COREDEBUG +# if !defined(TRINITY_DEBUG) +# define TRINITY_DEBUG +# endif //TRINITY_DEBUG +# define TRINITY_INLINE +#endif //!COREDEBUG + +#if COMPILER == COMPILER_GNU +# define ATTR_NORETURN __attribute__((__noreturn__)) +# define ATTR_PRINTF(F, V) __attribute__ ((__format__ (__printf__, F, V))) +# define ATTR_DEPRECATED __attribute__((__deprecated__)) +#else //COMPILER != COMPILER_GNU +# define ATTR_NORETURN +# define ATTR_PRINTF(F, V) +# define ATTR_DEPRECATED +#endif //COMPILER == COMPILER_GNU + +#define UI64FMTD "%" PRIu64 +#define UI64LIT(N) UINT64_C(N) + +#define SI64FMTD "%" PRId64 +#define SI64LIT(N) INT64_C(N) + +#define SZFMTD "%" PRIuPTR + +typedef int64_t int64; +typedef int32_t int32; +typedef int16_t int16; +typedef int8_t int8; +typedef uint64_t uint64; +typedef uint32_t uint32; +typedef uint16_t uint16; +typedef uint8_t uint8; + +#endif //TRINITY_DEFINE_H diff --git a/src/common/GitRevision.cpp b/src/common/GitRevision.cpp new file mode 100644 index 00000000000..d0719c09959 --- /dev/null +++ b/src/common/GitRevision.cpp @@ -0,0 +1,78 @@ +#include "GitRevision.h" +#include "CompilerDefs.h" +#include "revision_data.h" + +char const* GitRevision::GetHash() +{ + return _HASH; +} + +char const* GitRevision::GetDate() +{ + return _DATE; +} + +char const* GitRevision::GetBranch() +{ + return _BRANCH; +} + +char const* GitRevision::GetSourceDirectory() +{ + return _SOURCE_DIRECTORY; +} + +char const* GitRevision::GetMySQLExecutable() +{ + return _MYSQL_EXECUTABLE; +} + +char const* GitRevision::GetFullDatabase() +{ + return _FULL_DATABASE; +} + +#define _PACKAGENAME "TrinityCore" + +char const* GitRevision::GetFullVersion() +{ +#if PLATFORM == PLATFORM_WINDOWS +# ifdef _WIN64 + return _PACKAGENAME " rev. " VER_PRODUCTVERSION_STR " (Win64, " _BUILD_DIRECTIVE ")"; +# else + return _PACKAGENAME " rev. " VER_PRODUCTVERSION_STR " (Win32, " _BUILD_DIRECTIVE ")"; +# endif +#else + return _PACKAGENAME " rev. " VER_PRODUCTVERSION_STR " (Unix, " _BUILD_DIRECTIVE ")"; +#endif +} + +char const* GitRevision::GetCompanyNameStr() +{ + return VER_COMPANYNAME_STR; +} + +char const* GitRevision::GetLegalCopyrightStr() +{ + return VER_LEGALCOPYRIGHT_STR; +} + +char const* GitRevision::GetFileVersionStr() +{ + return VER_FILEVERSION_STR; +} + +char const* GitRevision::GetProductVersionStr() +{ + return VER_PRODUCTVERSION_STR; +} + +char const* GitRevision::GetCompilerCFlags() +{ + return COMPILER_C_FLAGS; +} + +char const* GitRevision::GetCompilerCXXFlags() +{ + return COMPILER_CXX_FLAGS; +} diff --git a/src/common/GitRevision.h b/src/common/GitRevision.h new file mode 100644 index 00000000000..d9b292031f6 --- /dev/null +++ b/src/common/GitRevision.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __GITREVISION_H__ +#define __GITREVISION_H__ + +#include <string> + +namespace GitRevision +{ + char const* GetHash(); + char const* GetDate(); + char const* GetBranch(); + char const* GetSourceDirectory(); + char const* GetMySQLExecutable(); + char const* GetFullDatabase(); + char const* GetFullVersion(); + char const* GetCompanyNameStr(); + char const* GetLegalCopyrightStr(); + char const* GetFileVersionStr(); + char const* GetProductVersionStr(); + char const* GetCompilerCFlags(); + char const* GetCompilerCXXFlags(); +} + +#endif diff --git a/src/common/Logging/Appender.cpp b/src/common/Logging/Appender.cpp new file mode 100644 index 00000000000..d19ef8cf96f --- /dev/null +++ b/src/common/Logging/Appender.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Appender.h" +#include "Common.h" +#include "Util.h" +#include "StringFormat.h" + +#include <utility> +#include <sstream> + +std::string LogMessage::getTimeStr(time_t time) +{ + tm aTm; + localtime_r(&time, &aTm); + char buf[20]; + snprintf(buf, 20, "%04d-%02d-%02d_%02d:%02d:%02d", aTm.tm_year+1900, aTm.tm_mon+1, aTm.tm_mday, aTm.tm_hour, aTm.tm_min, aTm.tm_sec); + return std::string(buf); +} + +std::string LogMessage::getTimeStr() +{ + return getTimeStr(mtime); +} + +Appender::Appender(uint8 _id, std::string const& _name, LogLevel _level /* = LOG_LEVEL_DISABLED */, AppenderFlags _flags /* = APPENDER_FLAGS_NONE */): +id(_id), name(_name), level(_level), flags(_flags) { } + +Appender::~Appender() { } + +uint8 Appender::getId() const +{ + return id; +} + +std::string const& Appender::getName() const +{ + return name; +} + +LogLevel Appender::getLogLevel() const +{ + return level; +} + +AppenderFlags Appender::getFlags() const +{ + return flags; +} + +void Appender::setLogLevel(LogLevel _level) +{ + level = _level; +} + +void Appender::write(LogMessage* message) +{ + if (!level || level > message->level) + return; + + std::ostringstream ss; + + if (flags & APPENDER_FLAGS_PREFIX_TIMESTAMP) + ss << message->getTimeStr() << ' '; + + if (flags & APPENDER_FLAGS_PREFIX_LOGLEVEL) + ss << Trinity::StringFormat("%-5s ", Appender::getLogLevelString(message->level)); + + if (flags & APPENDER_FLAGS_PREFIX_LOGFILTERTYPE) + ss << '[' << message->type << "] "; + + message->prefix = ss.str(); + _write(message); +} + +const char* Appender::getLogLevelString(LogLevel level) +{ + switch (level) + { + case LOG_LEVEL_FATAL: + return "FATAL"; + case LOG_LEVEL_ERROR: + return "ERROR"; + case LOG_LEVEL_WARN: + return "WARN"; + case LOG_LEVEL_INFO: + return "INFO"; + case LOG_LEVEL_DEBUG: + return "DEBUG"; + case LOG_LEVEL_TRACE: + return "TRACE"; + default: + return "DISABLED"; + } +} diff --git a/src/common/Logging/Appender.h b/src/common/Logging/Appender.h new file mode 100644 index 00000000000..6382399a0b4 --- /dev/null +++ b/src/common/Logging/Appender.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef APPENDER_H +#define APPENDER_H + +#include <unordered_map> +#include <stdexcept> +#include <string> +#include <time.h> +#include <type_traits> +#include <vector> +#include <utility> +#include "Define.h" + +// Values assigned have their equivalent in enum ACE_Log_Priority +enum LogLevel +{ + LOG_LEVEL_DISABLED = 0, + LOG_LEVEL_TRACE = 1, + LOG_LEVEL_DEBUG = 2, + LOG_LEVEL_INFO = 3, + LOG_LEVEL_WARN = 4, + LOG_LEVEL_ERROR = 5, + LOG_LEVEL_FATAL = 6 +}; + +const uint8 MaxLogLevels = 6; + +enum AppenderType +{ + APPENDER_NONE, + APPENDER_CONSOLE, + APPENDER_FILE, + APPENDER_DB +}; + +enum AppenderFlags +{ + APPENDER_FLAGS_NONE = 0x00, + APPENDER_FLAGS_PREFIX_TIMESTAMP = 0x01, + APPENDER_FLAGS_PREFIX_LOGLEVEL = 0x02, + APPENDER_FLAGS_PREFIX_LOGFILTERTYPE = 0x04, + APPENDER_FLAGS_USE_TIMESTAMP = 0x08, // only used by FileAppender + APPENDER_FLAGS_MAKE_FILE_BACKUP = 0x10 // only used by FileAppender +}; + +struct LogMessage +{ + LogMessage(LogLevel _level, std::string const& _type, std::string&& _text) + : level(_level), type(_type), text(std::forward<std::string>(_text)), mtime(time(NULL)) + { } + + LogMessage(LogMessage const& /*other*/) = delete; + LogMessage& operator=(LogMessage const& /*other*/) = delete; + + static std::string getTimeStr(time_t time); + std::string getTimeStr(); + + LogLevel const level; + std::string const type; + std::string const text; + std::string prefix; + std::string param1; + time_t mtime; + + ///@ Returns size of the log message content in bytes + uint32 Size() const + { + return static_cast<uint32>(prefix.size() + text.size()); + } +}; + +class Appender +{ + public: + Appender(uint8 _id, std::string const& name, LogLevel level = LOG_LEVEL_DISABLED, AppenderFlags flags = APPENDER_FLAGS_NONE); + virtual ~Appender(); + + uint8 getId() const; + std::string const& getName() const; + virtual AppenderType getType() const = 0; + LogLevel getLogLevel() const; + AppenderFlags getFlags() const; + + void setLogLevel(LogLevel); + void write(LogMessage* message); + static const char* getLogLevelString(LogLevel level); + virtual void setRealmId(uint32 /*realmId*/) { } + + private: + virtual void _write(LogMessage const* /*message*/) = 0; + + uint8 id; + std::string name; + LogLevel level; + AppenderFlags flags; +}; + +typedef std::unordered_map<uint8, Appender*> AppenderMap; + +typedef std::vector<char const*> ExtraAppenderArgs; +typedef Appender*(*AppenderCreatorFn)(uint8 id, std::string const& name, LogLevel level, AppenderFlags flags, ExtraAppenderArgs extraArgs); +typedef std::unordered_map<uint8, AppenderCreatorFn> AppenderCreatorMap; + +template<class AppenderImpl> +Appender* CreateAppender(uint8 id, std::string const& name, LogLevel level, AppenderFlags flags, ExtraAppenderArgs extraArgs) +{ + return new AppenderImpl(id, name, level, flags, std::forward<ExtraAppenderArgs>(extraArgs)); +} + +class InvalidAppenderArgsException : public std::length_error +{ +public: + explicit InvalidAppenderArgsException(std::string const& message) : std::length_error(message) { } +}; + +#endif diff --git a/src/common/Logging/AppenderConsole.cpp b/src/common/Logging/AppenderConsole.cpp new file mode 100644 index 00000000000..531df266aa1 --- /dev/null +++ b/src/common/Logging/AppenderConsole.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sstream> + +#include "AppenderConsole.h" +#include "Config.h" +#include "Util.h" + +#if PLATFORM == PLATFORM_WINDOWS + #include <Windows.h> +#endif + +AppenderConsole::AppenderConsole(uint8 id, std::string const& name, LogLevel level, AppenderFlags flags, ExtraAppenderArgs extraArgs) + : Appender(id, name, level, flags), _colored(false) +{ + for (uint8 i = 0; i < MaxLogLevels; ++i) + _colors[i] = ColorTypes(MaxColors); + + if (!extraArgs.empty()) + InitColors(extraArgs[0]); +} + +void AppenderConsole::InitColors(std::string const& str) +{ + if (str.empty()) + { + _colored = false; + return; + } + + int color[MaxLogLevels]; + + std::istringstream ss(str); + + for (uint8 i = 0; i < MaxLogLevels; ++i) + { + ss >> color[i]; + + if (!ss) + return; + + if (color[i] < 0 || color[i] >= MaxColors) + return; + } + + for (uint8 i = 0; i < MaxLogLevels; ++i) + _colors[i] = ColorTypes(color[i]); + + _colored = true; +} + +void AppenderConsole::SetColor(bool stdout_stream, ColorTypes color) +{ +#if PLATFORM == PLATFORM_WINDOWS + static WORD WinColorFG[MaxColors] = + { + 0, // BLACK + FOREGROUND_RED, // RED + FOREGROUND_GREEN, // GREEN + FOREGROUND_RED | FOREGROUND_GREEN, // BROWN + FOREGROUND_BLUE, // BLUE + FOREGROUND_RED | FOREGROUND_BLUE, // MAGENTA + FOREGROUND_GREEN | FOREGROUND_BLUE, // CYAN + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // WHITE + // YELLOW + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, + // RED_BOLD + FOREGROUND_RED | FOREGROUND_INTENSITY, + // GREEN_BOLD + FOREGROUND_GREEN | FOREGROUND_INTENSITY, + FOREGROUND_BLUE | FOREGROUND_INTENSITY, // BLUE_BOLD + // MAGENTA_BOLD + FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY, + // CYAN_BOLD + FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, + // WHITE_BOLD + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY + }; + + HANDLE hConsole = GetStdHandle(stdout_stream ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); + SetConsoleTextAttribute(hConsole, WinColorFG[color]); +#else + enum ANSITextAttr + { + TA_NORMAL = 0, + TA_BOLD = 1, + TA_BLINK = 5, + TA_REVERSE = 7 + }; + + enum ANSIFgTextAttr + { + FG_BLACK = 30, + FG_RED, + FG_GREEN, + FG_BROWN, + FG_BLUE, + FG_MAGENTA, + FG_CYAN, + FG_WHITE, + FG_YELLOW + }; + + enum ANSIBgTextAttr + { + BG_BLACK = 40, + BG_RED, + BG_GREEN, + BG_BROWN, + BG_BLUE, + BG_MAGENTA, + BG_CYAN, + BG_WHITE + }; + + static uint8 UnixColorFG[MaxColors] = + { + FG_BLACK, // BLACK + FG_RED, // RED + FG_GREEN, // GREEN + FG_BROWN, // BROWN + FG_BLUE, // BLUE + FG_MAGENTA, // MAGENTA + FG_CYAN, // CYAN + FG_WHITE, // WHITE + FG_YELLOW, // YELLOW + FG_RED, // LRED + FG_GREEN, // LGREEN + FG_BLUE, // LBLUE + FG_MAGENTA, // LMAGENTA + FG_CYAN, // LCYAN + FG_WHITE // LWHITE + }; + + fprintf((stdout_stream? stdout : stderr), "\x1b[%d%sm", UnixColorFG[color], (color >= YELLOW && color < MaxColors ? ";1" : "")); + #endif +} + +void AppenderConsole::ResetColor(bool stdout_stream) +{ + #if PLATFORM == PLATFORM_WINDOWS + HANDLE hConsole = GetStdHandle(stdout_stream ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); + SetConsoleTextAttribute(hConsole, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED); + #else + fprintf((stdout_stream ? stdout : stderr), "\x1b[0m"); + #endif +} + +void AppenderConsole::_write(LogMessage const* message) +{ + bool stdout_stream = !(message->level == LOG_LEVEL_ERROR || message->level == LOG_LEVEL_FATAL); + + if (_colored) + { + uint8 index; + switch (message->level) + { + case LOG_LEVEL_TRACE: + index = 5; + break; + case LOG_LEVEL_DEBUG: + index = 4; + break; + case LOG_LEVEL_INFO: + index = 3; + break; + case LOG_LEVEL_WARN: + index = 2; + break; + case LOG_LEVEL_FATAL: + index = 0; + break; + case LOG_LEVEL_ERROR: // No break on purpose + default: + index = 1; + break; + } + + SetColor(stdout_stream, _colors[index]); + utf8printf(stdout_stream ? stdout : stderr, "%s%s\n", message->prefix.c_str(), message->text.c_str()); + ResetColor(stdout_stream); + } + else + utf8printf(stdout_stream ? stdout : stderr, "%s%s\n", message->prefix.c_str(), message->text.c_str()); +} diff --git a/src/common/Logging/AppenderConsole.h b/src/common/Logging/AppenderConsole.h new file mode 100644 index 00000000000..6b30505c6bd --- /dev/null +++ b/src/common/Logging/AppenderConsole.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef APPENDERCONSOLE_H +#define APPENDERCONSOLE_H + +#include <string> +#include "Appender.h" + +enum ColorTypes +{ + BLACK, + RED, + GREEN, + BROWN, + BLUE, + MAGENTA, + CYAN, + GREY, + YELLOW, + LRED, + LGREEN, + LBLUE, + LMAGENTA, + LCYAN, + WHITE +}; + +const uint8 MaxColors = uint8(WHITE) + 1; + +class AppenderConsole : public Appender +{ + public: + typedef std::integral_constant<AppenderType, APPENDER_CONSOLE>::type TypeIndex; + + AppenderConsole(uint8 _id, std::string const& name, LogLevel level, AppenderFlags flags, ExtraAppenderArgs extraArgs); + void InitColors(const std::string& init_str); + AppenderType getType() const override { return TypeIndex::value; } + + private: + void SetColor(bool stdout_stream, ColorTypes color); + void ResetColor(bool stdout_stream); + void _write(LogMessage const* message) override; + bool _colored; + ColorTypes _colors[MaxLogLevels]; +}; + +#endif diff --git a/src/common/Logging/AppenderFile.cpp b/src/common/Logging/AppenderFile.cpp new file mode 100644 index 00000000000..c90c8f6ccea --- /dev/null +++ b/src/common/Logging/AppenderFile.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "AppenderFile.h" +#include "Common.h" +#include "StringFormat.h" +#include "Log.h" + +#if PLATFORM == PLATFORM_WINDOWS +# include <Windows.h> +#endif + +AppenderFile::AppenderFile(uint8 id, std::string const& name, LogLevel level, AppenderFlags flags, ExtraAppenderArgs extraArgs) : + Appender(id, name, level, flags), + logfile(NULL), + _logDir(sLog->GetLogsDir()), + _fileSize(0) +{ + if (extraArgs.empty()) + throw InvalidAppenderArgsException(Trinity::StringFormat("Log::CreateAppenderFromConfig: Missing file name for appender %s\n", name.c_str())); + + _fileName = extraArgs[0]; + + char const* mode = "a"; + if (extraArgs.size() > 1) + mode = extraArgs[1]; + + if (flags & APPENDER_FLAGS_USE_TIMESTAMP) + { + size_t dot_pos = _fileName.find_last_of("."); + if (dot_pos != std::string::npos) + _fileName.insert(dot_pos, sLog->GetLogsTimestamp()); + else + _fileName += sLog->GetLogsTimestamp(); + } + + if (extraArgs.size() > 2) + _maxFileSize = atoi(extraArgs[2]); + else + _maxFileSize = 0; + + _dynamicName = std::string::npos != _fileName.find("%s"); + _backup = (flags & APPENDER_FLAGS_MAKE_FILE_BACKUP) != 0; + + if (!_dynamicName) + logfile = OpenFile(_fileName, mode, !strcmp(mode, "w") && _backup); +} + +AppenderFile::~AppenderFile() +{ + CloseFile(); +} + +void AppenderFile::_write(LogMessage const* message) +{ + bool exceedMaxSize = _maxFileSize > 0 && (_fileSize.load() + message->Size()) > _maxFileSize; + + if (_dynamicName) + { + char namebuf[TRINITY_PATH_MAX]; + snprintf(namebuf, TRINITY_PATH_MAX, _fileName.c_str(), message->param1.c_str()); + // always use "a" with dynamic name otherwise it could delete the log we wrote in last _write() call + FILE* file = OpenFile(namebuf, "a", _backup || exceedMaxSize); + if (!file) + return; + fprintf(file, "%s%s\n", message->prefix.c_str(), message->text.c_str()); + fflush(file); + _fileSize += uint64(message->Size()); + fclose(file); + return; + } + else if (exceedMaxSize) + logfile = OpenFile(_fileName, "w", true); + + if (!logfile) + return; + + fprintf(logfile, "%s%s\n", message->prefix.c_str(), message->text.c_str()); + fflush(logfile); + _fileSize += uint64(message->Size()); +} + +FILE* AppenderFile::OpenFile(std::string const& filename, std::string const& mode, bool backup) +{ + std::string fullName(_logDir + filename); + if (backup) + { + CloseFile(); + std::string newName(fullName); + newName.push_back('.'); + newName.append(LogMessage::getTimeStr(time(NULL))); + std::replace(newName.begin(), newName.end(), ':', '-'); + rename(fullName.c_str(), newName.c_str()); // no error handling... if we couldn't make a backup, just ignore + } + + if (FILE* ret = fopen(fullName.c_str(), mode.c_str())) + { + _fileSize = ftell(ret); + return ret; + } + + return NULL; +} + +void AppenderFile::CloseFile() +{ + if (logfile) + { + fclose(logfile); + logfile = NULL; + } +} diff --git a/src/common/Logging/AppenderFile.h b/src/common/Logging/AppenderFile.h new file mode 100644 index 00000000000..c2781eb1ee9 --- /dev/null +++ b/src/common/Logging/AppenderFile.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef APPENDERFILE_H +#define APPENDERFILE_H + +#include <atomic> +#include "Appender.h" + +class AppenderFile : public Appender +{ + public: + typedef std::integral_constant<AppenderType, APPENDER_FILE>::type TypeIndex; + + AppenderFile(uint8 id, std::string const& name, LogLevel level, AppenderFlags flags, ExtraAppenderArgs extraArgs); + ~AppenderFile(); + FILE* OpenFile(std::string const& name, std::string const& mode, bool backup); + AppenderType getType() const override { return TypeIndex::value; } + + private: + void CloseFile(); + void _write(LogMessage const* message) override; + FILE* logfile; + std::string _fileName; + std::string _logDir; + bool _dynamicName; + bool _backup; + uint64 _maxFileSize; + std::atomic<uint64> _fileSize; +}; + +#endif diff --git a/src/common/Logging/Log.cpp b/src/common/Logging/Log.cpp new file mode 100644 index 00000000000..57d399a9d96 --- /dev/null +++ b/src/common/Logging/Log.cpp @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2008 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Log.h" +#include "Common.h" +#include "Config.h" +#include "Util.h" +#include "AppenderConsole.h" +#include "AppenderFile.h" +#include "LogOperation.h" + +#include <cstdio> +#include <sstream> + +Log::Log() : AppenderId(0), lowestLogLevel(LOG_LEVEL_FATAL), _ioService(nullptr), _strand(nullptr) +{ + m_logsTimestamp = "_" + GetTimestampStr(); + RegisterAppender<AppenderConsole>(); + RegisterAppender<AppenderFile>(); +} + +Log::~Log() +{ + delete _strand; + Close(); +} + +uint8 Log::NextAppenderId() +{ + return AppenderId++; +} + +int32 GetConfigIntDefault(std::string base, const char* name, int32 value) +{ + base.append(name); + return sConfigMgr->GetIntDefault(base.c_str(), value); +} + +std::string GetConfigStringDefault(std::string base, const char* name, const char* value) +{ + base.append(name); + return sConfigMgr->GetStringDefault(base.c_str(), value); +} + +Appender* Log::GetAppenderByName(std::string const& name) +{ + AppenderMap::iterator it = appenders.begin(); + while (it != appenders.end() && it->second && it->second->getName() != name) + ++it; + + return it == appenders.end() ? NULL : it->second; +} + +void Log::CreateAppenderFromConfig(std::string const& appenderName) +{ + if (appenderName.empty()) + return; + + // Format=type, level, flags, optional1, optional2 + // if type = File. optional1 = file and option2 = mode + // if type = Console. optional1 = Color + std::string options = sConfigMgr->GetStringDefault(appenderName.c_str(), ""); + + Tokenizer tokens(options, ','); + Tokenizer::const_iterator iter = tokens.begin(); + + size_t size = tokens.size(); + std::string name = appenderName.substr(9); + + if (size < 2) + { + fprintf(stderr, "Log::CreateAppenderFromConfig: Wrong configuration for appender %s. Config line: %s\n", name.c_str(), options.c_str()); + return; + } + + AppenderFlags flags = APPENDER_FLAGS_NONE; + AppenderType type = AppenderType(atoi(*iter++)); + LogLevel level = LogLevel(atoi(*iter++)); + + if (level > LOG_LEVEL_FATAL) + { + fprintf(stderr, "Log::CreateAppenderFromConfig: Wrong Log Level %d for appender %s\n", level, name.c_str()); + return; + } + + if (size > 2) + flags = AppenderFlags(atoi(*iter++)); + + auto factoryFunction = appenderFactory.find(type); + if (factoryFunction == appenderFactory.end()) + { + fprintf(stderr, "Log::CreateAppenderFromConfig: Unknown type %d for appender %s\n", type, name.c_str()); + return; + } + + try + { + Appender* appender = factoryFunction->second(NextAppenderId(), name, level, flags, ExtraAppenderArgs(iter, tokens.end())); + appenders[appender->getId()] = appender; + } + catch (InvalidAppenderArgsException const& iaae) + { + fprintf(stderr, "%s", iaae.what()); + } +} + +void Log::CreateLoggerFromConfig(std::string const& appenderName) +{ + if (appenderName.empty()) + return; + + LogLevel level = LOG_LEVEL_DISABLED; + uint8 type = uint8(-1); + + std::string options = sConfigMgr->GetStringDefault(appenderName.c_str(), ""); + std::string name = appenderName.substr(7); + + if (options.empty()) + { + fprintf(stderr, "Log::CreateLoggerFromConfig: Missing config option Logger.%s\n", name.c_str()); + return; + } + + Tokenizer tokens(options, ','); + Tokenizer::const_iterator iter = tokens.begin(); + + if (tokens.size() != 2) + { + fprintf(stderr, "Log::CreateLoggerFromConfig: Wrong config option Logger.%s=%s\n", name.c_str(), options.c_str()); + return; + } + + Logger& logger = loggers[name]; + if (!logger.getName().empty()) + { + fprintf(stderr, "Error while configuring Logger %s. Already defined\n", name.c_str()); + return; + } + + level = LogLevel(atoi(*iter++)); + if (level > LOG_LEVEL_FATAL) + { + fprintf(stderr, "Log::CreateLoggerFromConfig: Wrong Log Level %u for logger %s\n", type, name.c_str()); + return; + } + + if (level < lowestLogLevel) + lowestLogLevel = level; + + logger.Create(name, level); + //fprintf(stdout, "Log::CreateLoggerFromConfig: Created Logger %s, Level %u\n", name.c_str(), level); + + std::istringstream ss(*iter); + std::string str; + + ss >> str; + while (ss) + { + if (Appender* appender = GetAppenderByName(str)) + { + logger.addAppender(appender->getId(), appender); + //fprintf(stdout, "Log::CreateLoggerFromConfig: Added Appender %s to Logger %s\n", appender->getName().c_str(), name.c_str()); + } + else + fprintf(stderr, "Error while configuring Appender %s in Logger %s. Appender does not exist", str.c_str(), name.c_str()); + ss >> str; + } +} + +void Log::ReadAppendersFromConfig() +{ + std::list<std::string> keys = sConfigMgr->GetKeysByString("Appender."); + + while (!keys.empty()) + { + CreateAppenderFromConfig(keys.front()); + keys.pop_front(); + } +} + +void Log::ReadLoggersFromConfig() +{ + std::list<std::string> keys = sConfigMgr->GetKeysByString("Logger."); + + while (!keys.empty()) + { + CreateLoggerFromConfig(keys.front()); + keys.pop_front(); + } + + // Bad config configuration, creating default config + if (loggers.find(LOGGER_ROOT) == loggers.end()) + { + fprintf(stderr, "Wrong Loggers configuration. Review your Logger config section.\n" + "Creating default loggers [root (Error), server (Info)] to console\n"); + + Close(); // Clean any Logger or Appender created + + AppenderConsole* appender = new AppenderConsole(NextAppenderId(), "Console", LOG_LEVEL_DEBUG, APPENDER_FLAGS_NONE, ExtraAppenderArgs()); + appenders[appender->getId()] = appender; + + Logger& logger = loggers[LOGGER_ROOT]; + logger.Create(LOGGER_ROOT, LOG_LEVEL_ERROR); + logger.addAppender(appender->getId(), appender); + + logger = loggers["server"]; + logger.Create("server", LOG_LEVEL_ERROR); + logger.addAppender(appender->getId(), appender); + } +} + +void Log::write(std::unique_ptr<LogMessage>&& msg) const +{ + Logger const* logger = GetLoggerByType(msg->type); + + if (_ioService) + { + auto logOperation = std::shared_ptr<LogOperation>(new LogOperation(logger, std::move(msg))); + + _ioService->post(_strand->wrap([logOperation](){ logOperation->call(); })); + } + else + logger->write(msg.get()); +} + +std::string Log::GetTimestampStr() +{ + time_t tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + + std::tm aTm; + localtime_r(&tt, &aTm); + + // YYYY year + // MM month (2 digits 01-12) + // DD day (2 digits 01-31) + // HH hour (2 digits 00-23) + // MM minutes (2 digits 00-59) + // SS seconds (2 digits 00-59) + return Trinity::StringFormat("%04d-%02d-%02d_%02d-%02d-%02d", + aTm.tm_year + 1900, aTm.tm_mon + 1, aTm.tm_mday, aTm.tm_hour, aTm.tm_min, aTm.tm_sec); +} + +bool Log::SetLogLevel(std::string const& name, const char* newLevelc, bool isLogger /* = true */) +{ + LogLevel newLevel = LogLevel(atoi(newLevelc)); + if (newLevel < 0) + return false; + + if (isLogger) + { + LoggerMap::iterator it = loggers.begin(); + while (it != loggers.end() && it->second.getName() != name) + ++it; + + if (it == loggers.end()) + return false; + + it->second.setLogLevel(newLevel); + + if (newLevel != LOG_LEVEL_DISABLED && newLevel < lowestLogLevel) + lowestLogLevel = newLevel; + } + else + { + Appender* appender = GetAppenderByName(name); + if (!appender) + return false; + + appender->setLogLevel(newLevel); + } + + return true; +} + +void Log::outCharDump(char const* str, uint32 accountId, uint64 guid, char const* name) +{ + if (!str || !ShouldLog("entities.player.dump", LOG_LEVEL_INFO)) + return; + + std::ostringstream ss; + ss << "== START DUMP == (account: " << accountId << " guid: " << guid << " name: " << name + << ")\n" << str << "\n== END DUMP ==\n"; + + std::unique_ptr<LogMessage> msg(new LogMessage(LOG_LEVEL_INFO, "entities.player.dump", ss.str())); + std::ostringstream param; + param << guid << '_' << name; + + msg->param1 = param.str(); + + write(std::move(msg)); +} + +void Log::SetRealmId(uint32 id) +{ + for (AppenderMap::iterator it = appenders.begin(); it != appenders.end(); ++it) + it->second->setRealmId(id); +} + +void Log::Close() +{ + loggers.clear(); + for (AppenderMap::iterator it = appenders.begin(); it != appenders.end(); ++it) + delete it->second; + + appenders.clear(); +} + +void Log::Initialize(boost::asio::io_service* ioService) +{ + if (ioService) + { + _ioService = ioService; + _strand = new boost::asio::strand(*ioService); + } + + LoadFromConfig(); +} + +void Log::LoadFromConfig() +{ + Close(); + + lowestLogLevel = LOG_LEVEL_FATAL; + AppenderId = 0; + m_logsDir = sConfigMgr->GetStringDefault("LogsDir", ""); + if (!m_logsDir.empty()) + if ((m_logsDir.at(m_logsDir.length() - 1) != '/') && (m_logsDir.at(m_logsDir.length() - 1) != '\\')) + m_logsDir.push_back('/'); + + ReadAppendersFromConfig(); + ReadLoggersFromConfig(); +} diff --git a/src/common/Logging/Log.h b/src/common/Logging/Log.h new file mode 100644 index 00000000000..a15bb4ad485 --- /dev/null +++ b/src/common/Logging/Log.h @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_LOG_H +#define TRINITYCORE_LOG_H + +#include "Define.h" +#include "Appender.h" +#include "Logger.h" +#include "StringFormat.h" +#include "Common.h" +#include <boost/asio/io_service.hpp> +#include <boost/asio/strand.hpp> + +#include <stdarg.h> +#include <unordered_map> +#include <string> +#include <memory> + +#define LOGGER_ROOT "root" + +class Log +{ + typedef std::unordered_map<std::string, Logger> LoggerMap; + + private: + Log(); + ~Log(); + + public: + + static Log* instance() + { + static Log instance; + return &instance; + } + + void Initialize(boost::asio::io_service* ioService); + void LoadFromConfig(); + void Close(); + bool ShouldLog(std::string const& type, LogLevel level) const; + bool SetLogLevel(std::string const& name, char const* level, bool isLogger = true); + + template<typename Format, typename... Args> + inline void outMessage(std::string const& filter, LogLevel const level, Format&& fmt, Args&&... args) + { + write(Trinity::make_unique<LogMessage>(level, filter, + Trinity::StringFormat(std::forward<Format>(fmt), std::forward<Args>(args)...))); + } + + template<typename Format, typename... Args> + void outCommand(uint32 account, Format&& fmt, Args&&... args) + { + if (!ShouldLog("commands.gm", LOG_LEVEL_INFO)) + return; + + std::unique_ptr<LogMessage> msg = + Trinity::make_unique<LogMessage>(LOG_LEVEL_INFO, "commands.gm", + Trinity::StringFormat(std::forward<Format>(fmt), std::forward<Args>(args)...)); + + msg->param1 = std::to_string(account); + + write(std::move(msg)); + } + + void outCharDump(char const* str, uint32 account_id, uint64 guid, char const* name); + + void SetRealmId(uint32 id); + + template<class AppenderImpl> + void RegisterAppender() + { + using Index = typename AppenderImpl::TypeIndex; + auto itr = appenderFactory.find(Index::value); + ASSERT(itr == appenderFactory.end()); + appenderFactory[Index::value] = &CreateAppender<AppenderImpl>; + } + + std::string const& GetLogsDir() const { return m_logsDir; } + std::string const& GetLogsTimestamp() const { return m_logsTimestamp; } + + private: + static std::string GetTimestampStr(); + void write(std::unique_ptr<LogMessage>&& msg) const; + + Logger const* GetLoggerByType(std::string const& type) const; + Appender* GetAppenderByName(std::string const& name); + uint8 NextAppenderId(); + void CreateAppenderFromConfig(std::string const& name); + void CreateLoggerFromConfig(std::string const& name); + void ReadAppendersFromConfig(); + void ReadLoggersFromConfig(); + + AppenderCreatorMap appenderFactory; + AppenderMap appenders; + LoggerMap loggers; + uint8 AppenderId; + LogLevel lowestLogLevel; + + std::string m_logsDir; + std::string m_logsTimestamp; + + boost::asio::io_service* _ioService; + boost::asio::strand* _strand; +}; + +inline Logger const* Log::GetLoggerByType(std::string const& type) const +{ + LoggerMap::const_iterator it = loggers.find(type); + if (it != loggers.end()) + return &(it->second); + + if (type == LOGGER_ROOT) + return NULL; + + std::string parentLogger = LOGGER_ROOT; + size_t found = type.find_last_of("."); + if (found != std::string::npos) + parentLogger = type.substr(0,found); + + return GetLoggerByType(parentLogger); +} + +inline bool Log::ShouldLog(std::string const& type, LogLevel level) const +{ + // TODO: Use cache to store "Type.sub1.sub2": "Type" equivalence, should + // Speed up in cases where requesting "Type.sub1.sub2" but only configured + // Logger "Type" + + // Don't even look for a logger if the LogLevel is lower than lowest log levels across all loggers + if (level < lowestLogLevel) + return false; + + Logger const* logger = GetLoggerByType(type); + if (!logger) + return false; + + LogLevel logLevel = logger->getLogLevel(); + return logLevel != LOG_LEVEL_DISABLED && logLevel <= level; +} + +#define sLog Log::instance() + +#define LOG_EXCEPTION_FREE(filterType__, level__, ...) \ + { \ + try \ + { \ + sLog->outMessage(filterType__, level__, __VA_ARGS__); \ + } \ + catch (std::exception& e) \ + { \ + sLog->outMessage("server", LOG_LEVEL_ERROR, "Wrong format occurred (%s) at %s:%u.", \ + e.what(), __FILE__, __LINE__); \ + } \ + } + +#if PLATFORM != PLATFORM_WINDOWS +void check_args(const char*, ...) ATTR_PRINTF(1, 2); +void check_args(std::string const&, ...); + +// This will catch format errors on build time +#define TC_LOG_MESSAGE_BODY(filterType__, level__, ...) \ + do { \ + if (sLog->ShouldLog(filterType__, level__)) \ + { \ + if (false) \ + check_args(__VA_ARGS__); \ + \ + LOG_EXCEPTION_FREE(filterType__, level__, __VA_ARGS__); \ + } \ + } while (0) +#else +#define TC_LOG_MESSAGE_BODY(filterType__, level__, ...) \ + __pragma(warning(push)) \ + __pragma(warning(disable:4127)) \ + do { \ + if (sLog->ShouldLog(filterType__, level__)) \ + LOG_EXCEPTION_FREE(filterType__, level__, __VA_ARGS__); \ + } while (0) \ + __pragma(warning(pop)) +#endif + +#define TC_LOG_TRACE(filterType__, ...) \ + TC_LOG_MESSAGE_BODY(filterType__, LOG_LEVEL_TRACE, __VA_ARGS__) + +#define TC_LOG_DEBUG(filterType__, ...) \ + TC_LOG_MESSAGE_BODY(filterType__, LOG_LEVEL_DEBUG, __VA_ARGS__) + +#define TC_LOG_INFO(filterType__, ...) \ + TC_LOG_MESSAGE_BODY(filterType__, LOG_LEVEL_INFO, __VA_ARGS__) + +#define TC_LOG_WARN(filterType__, ...) \ + TC_LOG_MESSAGE_BODY(filterType__, LOG_LEVEL_WARN, __VA_ARGS__) + +#define TC_LOG_ERROR(filterType__, ...) \ + TC_LOG_MESSAGE_BODY(filterType__, LOG_LEVEL_ERROR, __VA_ARGS__) + +#define TC_LOG_FATAL(filterType__, ...) \ + TC_LOG_MESSAGE_BODY(filterType__, LOG_LEVEL_FATAL, __VA_ARGS__) + +#endif diff --git a/src/common/Logging/LogOperation.cpp b/src/common/Logging/LogOperation.cpp new file mode 100644 index 00000000000..bcd923c705e --- /dev/null +++ b/src/common/Logging/LogOperation.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "LogOperation.h" +#include "Logger.h" + +int LogOperation::call() +{ + logger->write(msg.get()); + return 0; +} diff --git a/src/common/Logging/LogOperation.h b/src/common/Logging/LogOperation.h new file mode 100644 index 00000000000..ffdd35c3c09 --- /dev/null +++ b/src/common/Logging/LogOperation.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LOGOPERATION_H +#define LOGOPERATION_H + +#include <memory> + +class Logger; +struct LogMessage; + +class LogOperation +{ + public: + LogOperation(Logger const* _logger, std::unique_ptr<LogMessage>&& _msg) + : logger(_logger), msg(std::forward<std::unique_ptr<LogMessage>>(_msg)) + { } + + ~LogOperation() { } + + int call(); + + protected: + Logger const* logger; + std::unique_ptr<LogMessage> msg; +}; + +#endif diff --git a/src/common/Logging/Logger.cpp b/src/common/Logging/Logger.cpp new file mode 100644 index 00000000000..3b02eb47575 --- /dev/null +++ b/src/common/Logging/Logger.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Logger.h" + +Logger::Logger(): name(""), level(LOG_LEVEL_DISABLED) { } + +void Logger::Create(std::string const& _name, LogLevel _level) +{ + name = _name; + level = _level; +} + +std::string const& Logger::getName() const +{ + return name; +} + +LogLevel Logger::getLogLevel() const +{ + return level; +} + +void Logger::addAppender(uint8 id, Appender* appender) +{ + appenders[id] = appender; +} + +void Logger::delAppender(uint8 id) +{ + appenders.erase(id); +} + +void Logger::setLogLevel(LogLevel _level) +{ + level = _level; +} + +void Logger::write(LogMessage* message) const +{ + if (!level || level > message->level || message->text.empty()) + { + //fprintf(stderr, "Logger::write: Logger %s, Level %u. Msg %s Level %u WRONG LEVEL MASK OR EMPTY MSG\n", getName().c_str(), getLogLevel(), message.text.c_str(), message.level); + return; + } + + for (AppenderMap::const_iterator it = appenders.begin(); it != appenders.end(); ++it) + if (it->second) + it->second->write(message); +} diff --git a/src/common/Logging/Logger.h b/src/common/Logging/Logger.h new file mode 100644 index 00000000000..1aee75c5d72 --- /dev/null +++ b/src/common/Logging/Logger.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LOGGER_H +#define LOGGER_H + +#include "Appender.h" + +class Logger +{ + public: + Logger(); + + void Create(std::string const& name, LogLevel level); + void addAppender(uint8 type, Appender *); + void delAppender(uint8 type); + + std::string const& getName() const; + LogLevel getLogLevel() const; + void setLogLevel(LogLevel level); + void write(LogMessage* message) const; + + private: + std::string name; + LogLevel level; + AppenderMap appenders; +}; + +#endif diff --git a/src/common/PrecompiledHeaders/commonPCH.cpp b/src/common/PrecompiledHeaders/commonPCH.cpp new file mode 100644 index 00000000000..52c623ca64a --- /dev/null +++ b/src/common/PrecompiledHeaders/commonPCH.cpp @@ -0,0 +1 @@ +#include "PrecompiledHeaders/commonPCH.h" diff --git a/src/common/PrecompiledHeaders/commonPCH.h b/src/common/PrecompiledHeaders/commonPCH.h new file mode 100644 index 00000000000..2d4f1ff4694 --- /dev/null +++ b/src/common/PrecompiledHeaders/commonPCH.h @@ -0,0 +1,13 @@ +#include "Define.h" +#include "Common.h" +#include "BoundingIntervalHierarchy.h" +#include "BoundingIntervalHierarchyWrapper.h" +#include "RegularGrid.h" +#include "Collision/VMapDefinitions.h" +#include "Collision/Maps/MapTree.h" +#include "Collision/Models/WorldModel.h" +#include "Collision/Models/ModelInstance.h" +#include "Collision/Models/GameObjectModel.h" +#include "Threading/ProducerConsumerQueue.h" +#include "Utilities/TaskScheduler.h" +#include "Utilities/EventMap.h" diff --git a/src/common/Threading/Callback.h b/src/common/Threading/Callback.h new file mode 100644 index 00000000000..1f4ffc97cfc --- /dev/null +++ b/src/common/Threading/Callback.h @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _CALLBACK_H +#define _CALLBACK_H + +#include <future> +#include "QueryResult.h" + +typedef std::future<QueryResult> QueryResultFuture; +typedef std::promise<QueryResult> QueryResultPromise; +typedef std::future<PreparedQueryResult> PreparedQueryResultFuture; +typedef std::promise<PreparedQueryResult> PreparedQueryResultPromise; + +#define CALLBACK_STAGE_INVALID uint8(-1) + +template <typename Result, typename ParamType, bool chain = false> +class QueryCallback +{ + public: + QueryCallback() : _param(), _stage(chain ? 0 : CALLBACK_STAGE_INVALID) { } + + //! The parameter of this function should be a resultset returned from either .AsyncQuery or .AsyncPQuery + void SetFutureResult(std::future<Result> value) + { + _result = std::move(value); + } + + std::future<Result>& GetFutureResult() + { + return _result; + } + + int IsReady() + { + return _result.valid() && _result.wait_for(std::chrono::seconds(0)) == std::future_status::ready; + } + + void GetResult(Result& res) + { + res = _result.get(); + } + + void FreeResult() + { + // Nothing to do here, the constructor of std::future will take care of the cleanup + } + + void SetParam(ParamType value) + { + _param = value; + } + + ParamType GetParam() + { + return _param; + } + + //! Resets the stage of the callback chain + void ResetStage() + { + if (!chain) + return; + + _stage = 0; + } + + //! Advances the callback chain to the next stage, so upper level code can act on its results accordingly + void NextStage() + { + if (!chain) + return; + + ++_stage; + } + + //! Returns the callback stage (or CALLBACK_STAGE_INVALID if invalid) + uint8 GetStage() + { + return _stage; + } + + //! Resets all underlying variables (param, result and stage) + void Reset() + { + SetParam(ParamType()); + FreeResult(); + ResetStage(); + } + + private: + std::future<Result> _result; + ParamType _param; + uint8 _stage; + + QueryCallback(QueryCallback const& right) = delete; + QueryCallback& operator=(QueryCallback const& right) = delete; +}; + +template <typename Result, typename ParamType1, typename ParamType2, bool chain = false> +class QueryCallback_2 +{ + public: + QueryCallback_2() : _stage(chain ? 0 : CALLBACK_STAGE_INVALID) { } + + //! The parameter of this function should be a resultset returned from either .AsyncQuery or .AsyncPQuery + void SetFutureResult(std::future<Result> value) + { + _result = std::move(value); + } + + std::future<Result>& GetFutureResult() + { + return _result; + } + + int IsReady() + { + return _result.valid() && _result.wait_for(std::chrono::seconds(0)) == std::future_status::ready; + } + + void GetResult(Result& res) + { + res = _result.get(); + } + + void FreeResult() + { + // Nothing to do here, the constructor of std::future will take care of the cleanup + } + + void SetFirstParam(ParamType1 value) + { + _param_1 = value; + } + + void SetSecondParam(ParamType2 value) + { + _param_2 = value; + } + + ParamType1 GetFirstParam() + { + return _param_1; + } + + ParamType2 GetSecondParam() + { + return _param_2; + } + + //! Resets the stage of the callback chain + void ResetStage() + { + if (!chain) + return; + + _stage = 0; + } + + //! Advances the callback chain to the next stage, so upper level code can act on its results accordingly + void NextStage() + { + if (!chain) + return; + + ++_stage; + } + + //! Returns the callback stage (or CALLBACK_STAGE_INVALID if invalid) + uint8 GetStage() + { + return _stage; + } + + //! Resets all underlying variables (param, result and stage) + void Reset() + { + SetFirstParam(NULL); + SetSecondParam(NULL); + FreeResult(); + ResetStage(); + } + + private: + std::future<Result> _result; + ParamType1 _param_1; + ParamType2 _param_2; + uint8 _stage; + + QueryCallback_2(QueryCallback_2 const& right) = delete; + QueryCallback_2& operator=(QueryCallback_2 const& right) = delete; +}; + +#endif diff --git a/src/common/Threading/LockedQueue.h b/src/common/Threading/LockedQueue.h new file mode 100644 index 00000000000..3abb0f4b8bc --- /dev/null +++ b/src/common/Threading/LockedQueue.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2008 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LOCKEDQUEUE_H +#define LOCKEDQUEUE_H + +#include <deque> +#include <mutex> + +template <class T, typename StorageType = std::deque<T> > +class LockedQueue +{ + //! Lock access to the queue. + std::mutex _lock; + + //! Storage backing the queue. + StorageType _queue; + + //! Cancellation flag. + volatile bool _canceled; + +public: + + //! Create a LockedQueue. + LockedQueue() + : _canceled(false) + { + } + + //! Destroy a LockedQueue. + virtual ~LockedQueue() + { + } + + //! Adds an item to the queue. + void add(const T& item) + { + lock(); + + _queue.push_back(item); + + unlock(); + } + + //! Gets the next result in the queue, if any. + bool next(T& result) + { + std::lock_guard<std::mutex> lock(_lock); + + if (_queue.empty()) + return false; + + result = _queue.front(); + _queue.pop_front(); + + return true; + } + + template<class Checker> + bool next(T& result, Checker& check) + { + std::lock_guard<std::mutex> lock(_lock); + + if (_queue.empty()) + return false; + + result = _queue.front(); + if (!check.Process(result)) + return false; + + _queue.pop_front(); + return true; + } + + //! Peeks at the top of the queue. Check if the queue is empty before calling! Remember to unlock after use if autoUnlock == false. + T& peek(bool autoUnlock = false) + { + lock(); + + T& result = _queue.front(); + + if (autoUnlock) + unlock(); + + return result; + } + + //! Cancels the queue. + void cancel() + { + std::lock_guard<std::mutex> lock(_lock); + + _canceled = true; + } + + //! Checks if the queue is cancelled. + bool cancelled() + { + std::lock_guard<std::mutex> lock(_lock); + return _canceled; + } + + //! Locks the queue for access. + void lock() + { + this->_lock.lock(); + } + + //! Unlocks the queue. + void unlock() + { + this->_lock.unlock(); + } + + ///! Calls pop_front of the queue + void pop_front() + { + std::lock_guard<std::mutex> lock(_lock); + _queue.pop_front(); + } + + ///! Checks if we're empty or not with locks held + bool empty() + { + std::lock_guard<std::mutex> lock(_lock); + return _queue.empty(); + } +}; +#endif diff --git a/src/common/Threading/ProcessPriority.h b/src/common/Threading/ProcessPriority.h new file mode 100644 index 00000000000..2a8501a0249 --- /dev/null +++ b/src/common/Threading/ProcessPriority.h @@ -0,0 +1,105 @@ +/* +* Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +* +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* +* You should have received a copy of the GNU General Public License along +* with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _PROCESSPRIO_H +#define _PROCESSPRIO_H + +#include "Configuration/Config.h" + +#ifdef __linux__ +#include <sched.h> +#include <sys/resource.h> +#define PROCESS_HIGH_PRIORITY -15 // [-20, 19], default is 0 +#endif + +void SetProcessPriority(const std::string& logChannel) +{ +// Suppresses Mac OS X Warning since logChannel isn't used. +#if PLATFORM_APPLE + (void)logChannel; +#endif + +#if defined(_WIN32) || defined(__linux__) + + ///- Handle affinity for multiple processors and process priority + uint32 affinity = sConfigMgr->GetIntDefault("UseProcessors", 0); + bool highPriority = sConfigMgr->GetBoolDefault("ProcessPriority", false); + +#ifdef _WIN32 // Windows + + HANDLE hProcess = GetCurrentProcess(); + if (affinity > 0) + { + ULONG_PTR appAff; + ULONG_PTR sysAff; + + if (GetProcessAffinityMask(hProcess, &appAff, &sysAff)) + { + // remove non accessible processors + ULONG_PTR currentAffinity = affinity & appAff; + + if (!currentAffinity) + TC_LOG_ERROR(logChannel, "Processors marked in UseProcessors bitmask (hex) %x are not accessible. Accessible processors bitmask (hex): %x", affinity, appAff); + else if (SetProcessAffinityMask(hProcess, currentAffinity)) + TC_LOG_INFO(logChannel, "Using processors (bitmask, hex): %x", currentAffinity); + else + TC_LOG_ERROR(logChannel, "Can't set used processors (hex): %x", currentAffinity); + } + } + + if (highPriority) + { + if (SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS)) + TC_LOG_INFO(logChannel, "Process priority class set to HIGH"); + else + TC_LOG_ERROR(logChannel, "Can't set process priority class."); + } + +#else // Linux + + if (affinity > 0) + { + cpu_set_t mask; + CPU_ZERO(&mask); + + for (unsigned int i = 0; i < sizeof(affinity) * 8; ++i) + if (affinity & (1 << i)) + CPU_SET(i, &mask); + + if (sched_setaffinity(0, sizeof(mask), &mask)) + TC_LOG_ERROR(logChannel, "Can't set used processors (hex): %x, error: %s", affinity, strerror(errno)); + else + { + CPU_ZERO(&mask); + sched_getaffinity(0, sizeof(mask), &mask); + TC_LOG_INFO(logChannel, "Using processors (bitmask, hex): %lx", *(__cpu_mask*)(&mask)); + } + } + + if (highPriority) + { + if (setpriority(PRIO_PROCESS, 0, PROCESS_HIGH_PRIORITY)) + TC_LOG_ERROR(logChannel, "Can't set process priority class, error: %s", strerror(errno)); + else + TC_LOG_INFO(logChannel, "Process priority class set to %i", getpriority(PRIO_PROCESS, 0)); + } + +#endif +#endif +} + +#endif diff --git a/src/common/Threading/ProducerConsumerQueue.h b/src/common/Threading/ProducerConsumerQueue.h new file mode 100644 index 00000000000..96546960393 --- /dev/null +++ b/src/common/Threading/ProducerConsumerQueue.h @@ -0,0 +1,112 @@ +/* +* Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +* +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* +* You should have received a copy of the GNU General Public License along +* with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _PCQ_H +#define _PCQ_H + +#include <condition_variable> +#include <mutex> +#include <queue> +#include <atomic> +#include <type_traits> + +template <typename T> +class ProducerConsumerQueue +{ +private: + std::mutex _queueLock; + std::queue<T> _queue; + std::condition_variable _condition; + std::atomic<bool> _shutdown; + +public: + + ProducerConsumerQueue<T>() : _shutdown(false) { } + + void Push(const T& value) + { + std::lock_guard<std::mutex> lock(_queueLock); + _queue.push(std::move(value)); + + _condition.notify_one(); + } + + bool Empty() + { + std::lock_guard<std::mutex> lock(_queueLock); + + return _queue.empty(); + } + + bool Pop(T& value) + { + std::lock_guard<std::mutex> lock(_queueLock); + + if (_queue.empty() || _shutdown) + return false; + + value = _queue.front(); + + _queue.pop(); + + return true; + } + + void WaitAndPop(T& value) + { + std::unique_lock<std::mutex> lock(_queueLock); + + // we could be using .wait(lock, predicate) overload here but it is broken + // https://connect.microsoft.com/VisualStudio/feedback/details/1098841 + while (_queue.empty() && !_shutdown) + _condition.wait(lock); + + if (_queue.empty() || _shutdown) + return; + + value = _queue.front(); + + _queue.pop(); + } + + void Cancel() + { + std::unique_lock<std::mutex> lock(_queueLock); + + while (!_queue.empty()) + { + T& value = _queue.front(); + + DeleteQueuedObject(value); + + _queue.pop(); + } + + _shutdown = true; + + _condition.notify_all(); + } + +private: + template<typename E = T> + typename std::enable_if<std::is_pointer<E>::value>::type DeleteQueuedObject(E& obj) { delete obj; } + + template<typename E = T> + typename std::enable_if<!std::is_pointer<E>::value>::type DeleteQueuedObject(E const& /*packet*/) { } +}; + +#endif diff --git a/src/common/Utilities/ByteConverter.h b/src/common/Utilities/ByteConverter.h new file mode 100644 index 00000000000..a077de3eb0b --- /dev/null +++ b/src/common/Utilities/ByteConverter.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_BYTECONVERTER_H +#define TRINITY_BYTECONVERTER_H + +/** ByteConverter reverse your byte order. This is use + for cross platform where they have different endians. + */ + +#include "Define.h" +#include <algorithm> + +namespace ByteConverter +{ + template<size_t T> + inline void convert(char *val) + { + std::swap(*val, *(val + T - 1)); + convert<T - 2>(val + 1); + } + + template<> inline void convert<0>(char *) { } + template<> inline void convert<1>(char *) { } // ignore central byte + + template<typename T> inline void apply(T *val) + { + convert<sizeof(T)>((char *)(val)); + } +} + +#if TRINITY_ENDIAN == TRINITY_BIGENDIAN +template<typename T> inline void EndianConvert(T& val) { ByteConverter::apply<T>(&val); } +template<typename T> inline void EndianConvertReverse(T&) { } +template<typename T> inline void EndianConvertPtr(void* val) { ByteConverter::apply<T>(val); } +template<typename T> inline void EndianConvertPtrReverse(void*) { } +#else +template<typename T> inline void EndianConvert(T&) { } +template<typename T> inline void EndianConvertReverse(T& val) { ByteConverter::apply<T>(&val); } +template<typename T> inline void EndianConvertPtr(void*) { } +template<typename T> inline void EndianConvertPtrReverse(void* val) { ByteConverter::apply<T>(val); } +#endif + +template<typename T> void EndianConvert(T*); // will generate link error +template<typename T> void EndianConvertReverse(T*); // will generate link error + +inline void EndianConvert(uint8&) { } +inline void EndianConvert( int8&) { } +inline void EndianConvertReverse(uint8&) { } +inline void EndianConvertReverse( int8&) { } + +#endif + diff --git a/src/common/Utilities/Duration.h b/src/common/Utilities/Duration.h new file mode 100644 index 00000000000..58a08e5842f --- /dev/null +++ b/src/common/Utilities/Duration.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _DURATION_H_ +#define _DURATION_H_ + +#include <chrono> + +/// Milliseconds shorthand typedef. +typedef std::chrono::milliseconds Milliseconds; + +/// Seconds shorthand typedef. +typedef std::chrono::seconds Seconds; + +/// Minutes shorthand typedef. +typedef std::chrono::minutes Minutes; + +/// Hours shorthand typedef. +typedef std::chrono::hours Hours; + +/// Makes std::chrono_literals globally available. +// ToDo: Enable this when TC supports C++14. +// using namespace std::chrono_literals; + +#endif // _DURATION_H_ diff --git a/src/common/Utilities/EventMap.cpp b/src/common/Utilities/EventMap.cpp new file mode 100644 index 00000000000..8c3f60afe82 --- /dev/null +++ b/src/common/Utilities/EventMap.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "EventMap.h" + +void EventMap::Reset() +{ + _eventMap.clear(); + _time = 0; + _phase = 0; +} + +void EventMap::SetPhase(uint8 phase) +{ + if (!phase) + _phase = 0; + else if (phase <= 8) + _phase = uint8(1 << (phase - 1)); +} + +void EventMap::ScheduleEvent(uint32 eventId, uint32 time, uint32 group /*= 0*/, uint8 phase /*= 0*/) +{ + if (group && group <= 8) + eventId |= (1 << (group + 15)); + + if (phase && phase <= 8) + eventId |= (1 << (phase + 23)); + + _eventMap.insert(EventStore::value_type(_time + time, eventId)); +} + +uint32 EventMap::ExecuteEvent() +{ + while (!Empty()) + { + EventStore::iterator itr = _eventMap.begin(); + + if (itr->first > _time) + return 0; + else if (_phase && (itr->second & 0xFF000000) && !((itr->second >> 24) & _phase)) + _eventMap.erase(itr); + else + { + uint32 eventId = (itr->second & 0x0000FFFF); + _lastEvent = itr->second; // include phase/group + _eventMap.erase(itr); + return eventId; + } + } + + return 0; +} + +void EventMap::DelayEvents(uint32 delay, uint32 group) +{ + if (!group || group > 8 || Empty()) + return; + + EventStore delayed; + + for (EventStore::iterator itr = _eventMap.begin(); itr != _eventMap.end();) + { + if (itr->second & (1 << (group + 15))) + { + delayed.insert(EventStore::value_type(itr->first + delay, itr->second)); + _eventMap.erase(itr++); + } + else + ++itr; + } + + _eventMap.insert(delayed.begin(), delayed.end()); +} + +void EventMap::CancelEvent(uint32 eventId) +{ + if (Empty()) + return; + + for (EventStore::iterator itr = _eventMap.begin(); itr != _eventMap.end();) + { + if (eventId == (itr->second & 0x0000FFFF)) + _eventMap.erase(itr++); + else + ++itr; + } +} + +void EventMap::CancelEventGroup(uint32 group) +{ + if (!group || group > 8 || Empty()) + return; + + for (EventStore::iterator itr = _eventMap.begin(); itr != _eventMap.end();) + { + if (itr->second & (1 << (group + 15))) + _eventMap.erase(itr++); + else + ++itr; + } +} + +uint32 EventMap::GetNextEventTime(uint32 eventId) const +{ + if (Empty()) + return 0; + + for (EventStore::const_iterator itr = _eventMap.begin(); itr != _eventMap.end(); ++itr) + if (eventId == (itr->second & 0x0000FFFF)) + return itr->first; + + return 0; +} + +uint32 EventMap::GetTimeUntilEvent(uint32 eventId) const +{ + for (EventStore::const_iterator itr = _eventMap.begin(); itr != _eventMap.end(); ++itr) + if (eventId == (itr->second & 0x0000FFFF)) + return itr->first - _time; + + return std::numeric_limits<uint32>::max(); +} diff --git a/src/common/Utilities/EventMap.h b/src/common/Utilities/EventMap.h new file mode 100644 index 00000000000..021dffc4940 --- /dev/null +++ b/src/common/Utilities/EventMap.h @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _EVENT_MAP_H_ +#define _EVENT_MAP_H_ + +#include "Common.h" +#include "Duration.h" +#include "Util.h" + +class EventMap +{ + /** + * Internal storage type. + * Key: Time as uint32 when the event should occur. + * Value: The event data as uint32. + * + * Structure of event data: + * - Bit 0 - 15: Event Id. + * - Bit 16 - 23: Group + * - Bit 24 - 31: Phase + * - Pattern: 0xPPGGEEEE + */ + typedef std::multimap<uint32, uint32> EventStore; + +public: + EventMap() : _time(0), _phase(0), _lastEvent(0) { } + + /** + * @name Reset + * @brief Removes all scheduled events and resets time and phase. + */ + void Reset(); + + /** + * @name Update + * @brief Updates the timer of the event map. + * @param time Value in ms to be added to time. + */ + void Update(uint32 time) + { + _time += time; + } + + /** + * @name GetTimer + * @return Current timer in ms value. + */ + uint32 GetTimer() const + { + return _time; + } + + /** + * @name GetPhaseMask + * @return Active phases as mask. + */ + uint8 GetPhaseMask() const + { + return _phase; + } + + /** + * @name Empty + * @return True, if there are no events scheduled. + */ + bool Empty() const + { + return _eventMap.empty(); + } + + /** + * @name SetPhase + * @brief Sets the phase of the map (absolute). + * @param phase Phase which should be set. Values: 1 - 8. 0 resets phase. + */ + void SetPhase(uint8 phase); + + /** + * @name AddPhase + * @brief Activates the given phase (bitwise). + * @param phase Phase which should be activated. Values: 1 - 8 + */ + void AddPhase(uint8 phase) + { + if (phase && phase <= 8) + _phase |= uint8(1 << (phase - 1)); + } + + /** + * @name RemovePhase + * @brief Deactivates the given phase (bitwise). + * @param phase Phase which should be deactivated. Values: 1 - 8. + */ + void RemovePhase(uint8 phase) + { + if (phase && phase <= 8) + _phase &= uint8(~(1 << (phase - 1))); + } + + /** + * @name ScheduleEvent + * @brief Creates new event entry in map. + * @param eventId The id of the new event. + * @param time The time in milliseconds as std::chrono::duration until the event occurs. + * @param group The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group. + * @param phase The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases. + */ + void ScheduleEvent(uint32 eventId, Milliseconds const& time, uint32 group = 0, uint8 phase = 0) + { + ScheduleEvent(eventId, time.count(), group, phase); + } + + /** + * @name ScheduleEvent + * @brief Creates new event entry in map. + * @param eventId The id of the new event. + * @param time The time in milliseconds until the event occurs. + * @param group The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group. + * @param phase The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases. + */ + void ScheduleEvent(uint32 eventId, uint32 time, uint32 group = 0, uint8 phase = 0); + + /** + * @name RescheduleEvent + * @brief Cancels the given event and reschedules it. + * @param eventId The id of the event. + * @param time The time in milliseconds as std::chrono::duration until the event occurs. + * @param group The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group. + * @param phase The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases. + */ + void RescheduleEvent(uint32 eventId, Milliseconds const& time, uint32 group = 0, uint8 phase = 0) + { + RescheduleEvent(eventId, time.count(), group, phase); + } + + /** + * @name RescheduleEvent + * @brief Cancels the given event and reschedules it. + * @param eventId The id of the event. + * @param time The time in milliseconds until the event occurs. + * @param group The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group. + * @param phase The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases. + */ + void RescheduleEvent(uint32 eventId, uint32 time, uint32 group = 0, uint8 phase = 0) + { + CancelEvent(eventId); + ScheduleEvent(eventId, time, group, phase); + } + + /** + * @name RepeatEvent + * @brief Repeats the mostly recently executed event. + * @param time Time until in milliseconds as std::chrono::duration the event occurs. + */ + void Repeat(Milliseconds const& time) + { + Repeat(time.count()); + } + + /** + * @name RepeatEvent + * @brief Repeats the mostly recently executed event. + * @param time Time until the event occurs. + */ + void Repeat(uint32 time) + { + _eventMap.insert(EventStore::value_type(_time + time, _lastEvent)); + } + + /** + * @name RepeatEvent + * @brief Repeats the mostly recently executed event. + * @param minTime Minimum time as std::chrono::duration until the event occurs. + * @param maxTime Maximum time as std::chrono::duration until the event occurs. + */ + void Repeat(Milliseconds const& minTime, Milliseconds const& maxTime) + { + Repeat(minTime.count(), maxTime.count()); + } + + /** + * @name RepeatEvent + * @brief Repeats the mostly recently executed event, Equivalent to Repeat(urand(minTime, maxTime). + * @param minTime Minimum time until the event occurs. + * @param maxTime Maximum time until the event occurs. + */ + void Repeat(uint32 minTime, uint32 maxTime) + { + Repeat(urand(minTime, maxTime)); + } + + /** + * @name ExecuteEvent + * @brief Returns the next event to execute and removes it from map. + * @return Id of the event to execute. + */ + uint32 ExecuteEvent(); + + /** + * @name DelayEvents + * @brief Delays all events in the map. If delay is greater than or equal internal timer, delay will be 0. + * @param delay Amount of delay in ms as std::chrono::duration. + */ + void DelayEvents(Milliseconds const& delay) + { + DelayEvents(delay.count()); + } + + /** + * @name DelayEvents + * @brief Delays all events in the map. If delay is greater than or equal internal timer, delay will be 0. + * @param delay Amount of delay. + */ + void DelayEvents(uint32 delay) + { + _time = delay < _time ? _time - delay : 0; + } + + /** + * @name DelayEvents + * @brief Delay all events of the same group. + * @param delay Amount of delay in ms as std::chrono::duration. + * @param group Group of the events. + */ + void DelayEvents(Milliseconds const& delay, uint32 group) + { + DelayEvents(delay.count(), group); + } + + /** + * @name DelayEvents + * @brief Delay all events of the same group. + * @param delay Amount of delay. + * @param group Group of the events. + */ + void DelayEvents(uint32 delay, uint32 group); + + /** + * @name CancelEvent + * @brief Cancels all events of the specified id. + * @param eventId Event id to cancel. + */ + void CancelEvent(uint32 eventId); + + /** + * @name CancelEventGroup + * @brief Cancel events belonging to specified group. + * @param group Group to cancel. + */ + void CancelEventGroup(uint32 group); + + /** + * @name GetNextEventTime + * @brief Returns closest occurence of specified event. + * @param eventId Wanted event id. + * @return Time of found event. + */ + uint32 GetNextEventTime(uint32 eventId) const; + + /** + * @name GetNextEventTime + * @return Time of next event. + */ + uint32 GetNextEventTime() const + { + return Empty() ? 0 : _eventMap.begin()->first; + } + + /** + * @name IsInPhase + * @brief Returns whether event map is in specified phase or not. + * @param phase Wanted phase. + * @return True, if phase of event map contains specified phase. + */ + bool IsInPhase(uint8 phase) const + { + return phase <= 8 && (!phase || _phase & (1 << (phase - 1))); + } + + /** + * @name GetTimeUntilEvent + * @brief Returns time in milliseconds until next event. + * @param eventId of the event. + * @return Time of next event. + */ + uint32 GetTimeUntilEvent(uint32 eventId) const; + +private: + /** + * @name _time + * @brief Internal timer. + * + * This does not represent the real date/time value. + * It's more like a stopwatch: It can run, it can be stopped, + * it can be resetted and so on. Events occur when this timer + * has reached their time value. Its value is changed in the + * Update method. + */ + uint32 _time; + + /** + * @name _phase + * @brief Phase mask of the event map. + * + * Contains the phases the event map is in. Multiple + * phases from 1 to 8 can be set with SetPhase or + * AddPhase. RemovePhase deactives a phase. + */ + uint8 _phase; + + /** + * @name _eventMap + * @brief Internal event storage map. Contains the scheduled events. + * + * See typedef at the beginning of the class for more + * details. + */ + EventStore _eventMap; + + /** + * @name _lastEvent + * @brief Stores information on the most recently executed event + */ + uint32 _lastEvent; +}; + +#endif // _EVENT_MAP_H_ diff --git a/src/common/Utilities/EventProcessor.cpp b/src/common/Utilities/EventProcessor.cpp new file mode 100644 index 00000000000..34695665443 --- /dev/null +++ b/src/common/Utilities/EventProcessor.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "EventProcessor.h" + +EventProcessor::EventProcessor() +{ + m_time = 0; + m_aborting = false; +} + +EventProcessor::~EventProcessor() +{ + KillAllEvents(true); +} + +void EventProcessor::Update(uint32 p_time) +{ + // update time + m_time += p_time; + + // main event loop + EventList::iterator i; + while (((i = m_events.begin()) != m_events.end()) && i->first <= m_time) + { + // get and remove event from queue + BasicEvent* Event = i->second; + m_events.erase(i); + + if (!Event->to_Abort) + { + if (Event->Execute(m_time, p_time)) + { + // completely destroy event if it is not re-added + delete Event; + } + } + else + { + Event->Abort(m_time); + delete Event; + } + } +} + +void EventProcessor::KillAllEvents(bool force) +{ + // prevent event insertions + m_aborting = true; + + // first, abort all existing events + for (EventList::iterator i = m_events.begin(); i != m_events.end();) + { + EventList::iterator i_old = i; + ++i; + + i_old->second->to_Abort = true; + i_old->second->Abort(m_time); + if (force || i_old->second->IsDeletable()) + { + delete i_old->second; + + if (!force) // need per-element cleanup + m_events.erase (i_old); + } + } + + // fast clear event list (in force case) + if (force) + m_events.clear(); +} + +void EventProcessor::AddEvent(BasicEvent* Event, uint64 e_time, bool set_addtime) +{ + if (set_addtime) Event->m_addTime = m_time; + Event->m_execTime = e_time; + m_events.insert(std::pair<uint64, BasicEvent*>(e_time, Event)); +} + +uint64 EventProcessor::CalculateTime(uint64 t_offset) const +{ + return(m_time + t_offset); +} + diff --git a/src/common/Utilities/EventProcessor.h b/src/common/Utilities/EventProcessor.h new file mode 100644 index 00000000000..3d54bd6f9f2 --- /dev/null +++ b/src/common/Utilities/EventProcessor.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __EVENTPROCESSOR_H +#define __EVENTPROCESSOR_H + +#include "Define.h" + +#include <map> + +// Note. All times are in milliseconds here. + +class BasicEvent +{ + public: + BasicEvent() + { + to_Abort = false; + m_addTime = 0; + m_execTime = 0; + } + virtual ~BasicEvent() { } // override destructor to perform some actions on event removal + + // this method executes when the event is triggered + // return false if event does not want to be deleted + // e_time is execution time, p_time is update interval + virtual bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) { return true; } + + virtual bool IsDeletable() const { return true; } // this event can be safely deleted + + virtual void Abort(uint64 /*e_time*/) { } // this method executes when the event is aborted + + bool to_Abort; // set by externals when the event is aborted, aborted events don't execute + // and get Abort call when deleted + + // these can be used for time offset control + uint64 m_addTime; // time when the event was added to queue, filled by event handler + uint64 m_execTime; // planned time of next execution, filled by event handler +}; + +typedef std::multimap<uint64, BasicEvent*> EventList; + +class EventProcessor +{ + public: + EventProcessor(); + ~EventProcessor(); + + void Update(uint32 p_time); + void KillAllEvents(bool force); + void AddEvent(BasicEvent* Event, uint64 e_time, bool set_addtime = true); + uint64 CalculateTime(uint64 t_offset) const; + protected: + uint64 m_time; + EventList m_events; + bool m_aborting; +}; +#endif diff --git a/src/common/Utilities/StringFormat.h b/src/common/Utilities/StringFormat.h new file mode 100644 index 00000000000..67e0100e7c8 --- /dev/null +++ b/src/common/Utilities/StringFormat.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_STRING_FORMAT_H +#define TRINITYCORE_STRING_FORMAT_H + +#include "format.h" + +namespace Trinity +{ + /// Default TC string format function. + template<typename Format, typename... Args> + inline std::string StringFormat(Format&& fmt, Args&&... args) + { + return fmt::sprintf(std::forward<Format>(fmt), std::forward<Args>(args)...); + } + + /// Returns true if the given char pointer is null. + inline bool IsFormatEmptyOrNull(const char* fmt) + { + return fmt == nullptr; + } + + /// Returns true if the given std::string is empty. + inline bool IsFormatEmptyOrNull(std::string const& fmt) + { + return fmt.empty(); + } +} + +#endif diff --git a/src/common/Utilities/TaskScheduler.cpp b/src/common/Utilities/TaskScheduler.cpp new file mode 100644 index 00000000000..801cc96cf77 --- /dev/null +++ b/src/common/Utilities/TaskScheduler.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "TaskScheduler.h" + +TaskScheduler& TaskScheduler::ClearValidator() +{ + _predicate = EmptyValidator; + return *this; +} + +TaskScheduler& TaskScheduler::Update(success_t const& callback) +{ + _now = clock_t::now(); + Dispatch(callback); + return *this; +} + +TaskScheduler& TaskScheduler::Update(size_t const milliseconds, success_t const& callback) +{ + return Update(std::chrono::milliseconds(milliseconds), callback); +} + +TaskScheduler& TaskScheduler::Async(std::function<void()> const& callable) +{ + _asyncHolder.push(callable); + return *this; +} + +TaskScheduler& TaskScheduler::CancelAll() +{ + /// Clear the task holder + _task_holder.Clear(); + _asyncHolder = AsyncHolder(); + return *this; +} + +TaskScheduler& TaskScheduler::CancelGroup(group_t const group) +{ + _task_holder.RemoveIf([group](TaskContainer const& task) -> bool + { + return task->IsInGroup(group); + }); + return *this; +} + +TaskScheduler& TaskScheduler::CancelGroupsOf(std::vector<group_t> const& groups) +{ + std::for_each(groups.begin(), groups.end(), + std::bind(&TaskScheduler::CancelGroup, this, std::placeholders::_1)); + + return *this; +} + +TaskScheduler& TaskScheduler::InsertTask(TaskContainer task) +{ + _task_holder.Push(std::move(task)); + return *this; +} + +void TaskScheduler::Dispatch(success_t const& callback) +{ + // If the validation failed abort the dispatching here. + if (!_predicate()) + return; + + // Process all asyncs + while (!_asyncHolder.empty()) + { + _asyncHolder.front()(); + _asyncHolder.pop(); + + // If the validation failed abort the dispatching here. + if (!_predicate()) + return; + } + + while (!_task_holder.IsEmpty()) + { + if (_task_holder.First()->_end > _now) + break; + + // Perfect forward the context to the handler + // Use weak references to catch destruction before callbacks. + TaskContext context(_task_holder.Pop(), std::weak_ptr<TaskScheduler>(self_reference)); + + // Invoke the context + context.Invoke(); + + // If the validation failed abort the dispatching here. + if (!_predicate()) + return; + } + + // On finish call the final callback + callback(); +} + +void TaskScheduler::TaskQueue::Push(TaskContainer&& task) +{ + container.insert(task); +} + +auto TaskScheduler::TaskQueue::Pop() -> TaskContainer +{ + TaskContainer result = *container.begin(); + container.erase(container.begin()); + return result; +} + +auto TaskScheduler::TaskQueue::First() const -> TaskContainer const& +{ + return *container.begin(); +} + +void TaskScheduler::TaskQueue::Clear() +{ + container.clear(); +} + +void TaskScheduler::TaskQueue::RemoveIf(std::function<bool(TaskContainer const&)> const& filter) +{ + for (auto itr = container.begin(); itr != container.end();) + if (filter(*itr)) + itr = container.erase(itr); + else + ++itr; +} + +void TaskScheduler::TaskQueue::ModifyIf(std::function<bool(TaskContainer const&)> const& filter) +{ + std::vector<TaskContainer> cache; + for (auto itr = container.begin(); itr != container.end();) + if (filter(*itr)) + { + cache.push_back(*itr); + itr = container.erase(itr); + } + else + ++itr; + + container.insert(cache.begin(), cache.end()); +} + +bool TaskScheduler::TaskQueue::IsEmpty() const +{ + return container.empty(); +} + +TaskContext& TaskContext::Dispatch(std::function<TaskScheduler&(TaskScheduler&)> const& apply) +{ + if (auto const owner = _owner.lock()) + apply(*owner); + + return *this; +} + +bool TaskContext::IsExpired() const +{ + return _owner.expired(); +} + +bool TaskContext::IsInGroup(TaskScheduler::group_t const group) const +{ + return _task->IsInGroup(group); +} + +TaskContext& TaskContext::SetGroup(TaskScheduler::group_t const group) +{ + _task->_group = group; + return *this; +} + +TaskContext& TaskContext::ClearGroup() +{ + _task->_group = boost::none; + return *this; +} + +TaskScheduler::repeated_t TaskContext::GetRepeatCounter() const +{ + return _task->_repeated; +} + +TaskContext& TaskContext::Async(std::function<void()> const& callable) +{ + return Dispatch(std::bind(&TaskScheduler::Async, std::placeholders::_1, callable)); +} + +TaskContext& TaskContext::CancelAll() +{ + return Dispatch(std::mem_fn(&TaskScheduler::CancelAll)); +} + +TaskContext& TaskContext::CancelGroup(TaskScheduler::group_t const group) +{ + return Dispatch(std::bind(&TaskScheduler::CancelGroup, std::placeholders::_1, group)); +} + +TaskContext& TaskContext::CancelGroupsOf(std::vector<TaskScheduler::group_t> const& groups) +{ + return Dispatch(std::bind(&TaskScheduler::CancelGroupsOf, std::placeholders::_1, std::cref(groups))); +} + +void TaskContext::AssertOnConsumed() const +{ + // This was adapted to TC to prevent static analysis tools from complaining. + // If you encounter this assertion check if you repeat a TaskContext more then 1 time! + ASSERT(!(*_consumed) && "Bad task logic, task context was consumed already!"); +} + +void TaskContext::Invoke() +{ + _task->_task(*this); +} diff --git a/src/common/Utilities/TaskScheduler.h b/src/common/Utilities/TaskScheduler.h new file mode 100644 index 00000000000..f1fe7ea0a21 --- /dev/null +++ b/src/common/Utilities/TaskScheduler.h @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _TASK_SCHEDULER_H_ +#define _TASK_SCHEDULER_H_ + +#include <algorithm> +#include <chrono> +#include <vector> +#include <queue> +#include <memory> +#include <utility> +#include <set> + +#include <boost/optional.hpp> + +#include "Util.h" +#include "Duration.h" + +class TaskContext; + +/// The TaskScheduler class provides the ability to schedule std::function's in the near future. +/// Use TaskScheduler::Update to update the scheduler. +/// Popular methods are: +/// * Schedule (Schedules a std::function which will be executed in the near future). +/// * Schedules an asynchronous function which will be executed at the next update tick. +/// * Cancel, Delay & Reschedule (Methods to manipulate already scheduled tasks). +/// Tasks are organized in groups (uint), multiple tasks can have the same group id, +/// you can provide a group or not, but keep in mind that you can only manipulate specific tasks through its group id! +/// Tasks callbacks use the function signature void(TaskContext) where TaskContext provides +/// access to the function schedule plan which makes it possible to repeat the task +/// with the same duration or a new one. +/// It also provides access to the repeat counter which is useful for task that repeat itself often +/// but behave different every time (spoken event dialogs for example). +class TaskScheduler +{ + friend class TaskContext; + + // Time definitions (use steady clock) + typedef std::chrono::steady_clock clock_t; + typedef clock_t::time_point timepoint_t; + typedef clock_t::duration duration_t; + + // Task group type + typedef uint32 group_t; + // Task repeated type + typedef uint32 repeated_t; + // Task handle type + typedef std::function<void(TaskContext)> task_handler_t; + // Predicate type + typedef std::function<bool()> predicate_t; + // Success handle type + typedef std::function<void()> success_t; + + class Task + { + friend class TaskContext; + friend class TaskScheduler; + + timepoint_t _end; + duration_t _duration; + boost::optional<group_t> _group; + repeated_t _repeated; + task_handler_t _task; + + public: + // All Argument construct + Task(timepoint_t const& end, duration_t const& duration, boost::optional<group_t> const& group, + repeated_t const repeated, task_handler_t const& task) + : _end(end), _duration(duration), _group(group), _repeated(repeated), _task(task) { } + + // Minimal Argument construct + Task(timepoint_t const& end, duration_t const& duration, task_handler_t const& task) + : _end(end), _duration(duration), _group(boost::none), _repeated(0), _task(task) { } + + // Copy construct + Task(Task const&) = delete; + // Move construct + Task(Task&&) = delete; + // Copy Assign + Task& operator= (Task const&) = default; + // Move Assign + Task& operator= (Task&& right) = delete; + + // Order tasks by its end + inline bool operator< (Task const& other) const + { + return _end < other._end; + } + + inline bool operator> (Task const& other) const + { + return _end > other._end; + } + + // Compare tasks with its end + inline bool operator== (Task const& other) + { + return _end == other._end; + } + + // Returns true if the task is in the given group + inline bool IsInGroup(group_t const group) const + { + return _group == group; + } + }; + + typedef std::shared_ptr<Task> TaskContainer; + + /// Container which provides Task order, insert and reschedule operations. + struct Compare + { + bool operator() (TaskContainer const& left, TaskContainer const& right) + { + return (*left.get()) < (*right.get()); + }; + }; + + class TaskQueue + { + std::multiset<TaskContainer, Compare> container; + + public: + // Pushes the task in the container + void Push(TaskContainer&& task); + + /// Pops the task out of the container + TaskContainer Pop(); + + TaskContainer const& First() const; + + void Clear(); + + void RemoveIf(std::function<bool(TaskContainer const&)> const& filter); + + void ModifyIf(std::function<bool(TaskContainer const&)> const& filter); + + bool IsEmpty() const; + }; + + /// Contains a self reference to track if this object was deleted or not. + std::shared_ptr<TaskScheduler> self_reference; + + /// The current time point (now) + timepoint_t _now; + + /// The Task Queue which contains all task objects. + TaskQueue _task_holder; + + typedef std::queue<std::function<void()>> AsyncHolder; + + /// Contains all asynchronous tasks which will be invoked at + /// the next update tick. + AsyncHolder _asyncHolder; + + predicate_t _predicate; + + static bool EmptyValidator() + { + return true; + } + + static void EmptyCallback() + { + } + +public: + TaskScheduler() + : self_reference(this, [](TaskScheduler const*) { }), _now(clock_t::now()), _predicate(EmptyValidator) { } + + template<typename P> + TaskScheduler(P&& predicate) + : self_reference(this, [](TaskScheduler const*) { }), _now(clock_t::now()), _predicate(std::forward<P>(predicate)) { } + + TaskScheduler(TaskScheduler const&) = delete; + TaskScheduler(TaskScheduler&&) = delete; + TaskScheduler& operator= (TaskScheduler const&) = delete; + TaskScheduler& operator= (TaskScheduler&&) = delete; + + /// Sets a validator which is asked if tasks are allowed to be executed. + template<typename P> + TaskScheduler& SetValidator(P&& predicate) + { + _predicate = std::forward<P>(predicate); + return *this; + } + + /// Clears the validator which is asked if tasks are allowed to be executed. + TaskScheduler& ClearValidator(); + + /// Update the scheduler to the current time. + /// Calls the optional callback on successfully finish. + TaskScheduler& Update(success_t const& callback = EmptyCallback); + + /// Update the scheduler with a difftime in ms. + /// Calls the optional callback on successfully finish. + TaskScheduler& Update(size_t const milliseconds, success_t const& callback = EmptyCallback); + + /// Update the scheduler with a difftime. + /// Calls the optional callback on successfully finish. + template<class _Rep, class _Period> + TaskScheduler& Update(std::chrono::duration<_Rep, _Period> const& difftime, + success_t const& callback = EmptyCallback) + { + _now += difftime; + Dispatch(callback); + return *this; + } + + /// Schedule an callable function that is executed at the next update tick. + /// Its safe to modify the TaskScheduler from within the callable. + TaskScheduler& Async(std::function<void()> const& callable); + + /// Schedule an event with a fixed rate. + /// Never call this from within a task context! Use TaskContext::Schedule instead! + template<class _Rep, class _Period> + TaskScheduler& Schedule(std::chrono::duration<_Rep, _Period> const& time, + task_handler_t const& task) + { + return ScheduleAt(_now, time, task); + } + + /// Schedule an event with a fixed rate. + /// Never call this from within a task context! Use TaskContext::Schedule instead! + template<class _Rep, class _Period> + TaskScheduler& Schedule(std::chrono::duration<_Rep, _Period> const& time, + group_t const group, task_handler_t const& task) + { + return ScheduleAt(_now, time, group, task); + } + + /// Schedule an event with a randomized rate between min and max rate. + /// Never call this from within a task context! Use TaskContext::Schedule instead! + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskScheduler& Schedule(std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max, task_handler_t const& task) + { + return Schedule(RandomDurationBetween(min, max), task); + } + + /// Schedule an event with a fixed rate. + /// Never call this from within a task context! Use TaskContext::Schedule instead! + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskScheduler& Schedule(std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max, group_t const group, + task_handler_t const& task) + { + return Schedule(RandomDurationBetween(min, max), group, task); + } + + /// Cancels all tasks. + /// Never call this from within a task context! Use TaskContext::CancelAll instead! + TaskScheduler& CancelAll(); + + /// Cancel all tasks of a single group. + /// Never call this from within a task context! Use TaskContext::CancelGroup instead! + TaskScheduler& CancelGroup(group_t const group); + + /// Cancels all groups in the given std::vector. + /// Hint: Use std::initializer_list for this: "{1, 2, 3, 4}" + TaskScheduler& CancelGroupsOf(std::vector<group_t> const& groups); + + /// Delays all tasks with the given duration. + template<class _Rep, class _Period> + TaskScheduler& DelayAll(std::chrono::duration<_Rep, _Period> const& duration) + { + _task_holder.ModifyIf([&duration](TaskContainer const& task) -> bool + { + task->_end += duration; + return true; + }); + return *this; + } + + /// Delays all tasks with a random duration between min and max. + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskScheduler& DelayAll(std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max) + { + return DelayAll(RandomDurationBetween(min, max)); + } + + /// Delays all tasks of a group with the given duration. + template<class _Rep, class _Period> + TaskScheduler& DelayGroup(group_t const group, std::chrono::duration<_Rep, _Period> const& duration) + { + _task_holder.ModifyIf([&duration, group](TaskContainer const& task) -> bool + { + if (task->IsInGroup(group)) + { + task->_end += duration; + return true; + } + else + return false; + }); + return *this; + } + + /// Delays all tasks of a group with a random duration between min and max. + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskScheduler& DelayGroup(group_t const group, + std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max) + { + return DelayGroup(group, RandomDurationBetween(min, max)); + } + + /// Reschedule all tasks with a given duration. + template<class _Rep, class _Period> + TaskScheduler& RescheduleAll(std::chrono::duration<_Rep, _Period> const& duration) + { + auto const end = _now + duration; + _task_holder.ModifyIf([end](TaskContainer const& task) -> bool + { + task->_end = end; + return true; + }); + return *this; + } + + /// Reschedule all tasks with a random duration between min and max. + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskScheduler& RescheduleAll(std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max) + { + return RescheduleAll(RandomDurationBetween(min, max)); + } + + /// Reschedule all tasks of a group with the given duration. + template<class _Rep, class _Period> + TaskScheduler& RescheduleGroup(group_t const group, std::chrono::duration<_Rep, _Period> const& duration) + { + auto const end = _now + duration; + _task_holder.ModifyIf([end, group](TaskContainer const& task) -> bool + { + if (task->IsInGroup(group)) + { + task->_end = end; + return true; + } + else + return false; + }); + return *this; + } + + /// Reschedule all tasks of a group with a random duration between min and max. + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskScheduler& RescheduleGroup(group_t const group, + std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max) + { + return RescheduleGroup(group, RandomDurationBetween(min, max)); + } + +private: + /// Insert a new task to the enqueued tasks. + TaskScheduler& InsertTask(TaskContainer task); + + template<class _Rep, class _Period> + TaskScheduler& ScheduleAt(timepoint_t const& end, + std::chrono::duration<_Rep, _Period> const& time, task_handler_t const& task) + { + return InsertTask(TaskContainer(new Task(end + time, time, task))); + } + + /// Schedule an event with a fixed rate. + /// Never call this from within a task context! Use TaskContext::schedule instead! + template<class _Rep, class _Period> + TaskScheduler& ScheduleAt(timepoint_t const& end, + std::chrono::duration<_Rep, _Period> const& time, + group_t const group, task_handler_t const& task) + { + static repeated_t const DEFAULT_REPEATED = 0; + return InsertTask(TaskContainer(new Task(end + time, time, group, DEFAULT_REPEATED, task))); + } + + // Returns a random duration between min and max + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + static std::chrono::milliseconds + RandomDurationBetween(std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max) + { + auto const milli_min = std::chrono::duration_cast<std::chrono::milliseconds>(min); + auto const milli_max = std::chrono::duration_cast<std::chrono::milliseconds>(max); + + // TC specific: use SFMT URandom + return std::chrono::milliseconds(urand(milli_min.count(), milli_max.count())); + } + + /// Dispatch remaining tasks + void Dispatch(success_t const& callback); +}; + +class TaskContext +{ + friend class TaskScheduler; + + /// Associated task + TaskScheduler::TaskContainer _task; + + /// Owner + std::weak_ptr<TaskScheduler> _owner; + + /// Marks the task as consumed + std::shared_ptr<bool> _consumed; + + /// Dispatches an action safe on the TaskScheduler + TaskContext& Dispatch(std::function<TaskScheduler&(TaskScheduler&)> const& apply); + +public: + // Empty constructor + TaskContext() + : _task(), _owner(), _consumed(std::make_shared<bool>(true)) { } + + // Construct from task and owner + explicit TaskContext(TaskScheduler::TaskContainer&& task, std::weak_ptr<TaskScheduler>&& owner) + : _task(task), _owner(owner), _consumed(std::make_shared<bool>(false)) { } + + // Copy construct + TaskContext(TaskContext const& right) + : _task(right._task), _owner(right._owner), _consumed(right._consumed) { } + + // Move construct + TaskContext(TaskContext&& right) + : _task(std::move(right._task)), _owner(std::move(right._owner)), _consumed(std::move(right._consumed)) { } + + // Copy assign + TaskContext& operator= (TaskContext const& right) + { + _task = right._task; + _owner = right._owner; + _consumed = right._consumed; + return *this; + } + + // Move assign + TaskContext& operator= (TaskContext&& right) + { + _task = std::move(right._task); + _owner = std::move(right._owner); + _consumed = std::move(right._consumed); + return *this; + } + + /// Returns true if the owner was deallocated and this context has expired. + bool IsExpired() const; + + /// Returns true if the event is in the given group + bool IsInGroup(TaskScheduler::group_t const group) const; + + /// Sets the event in the given group + TaskContext& SetGroup(TaskScheduler::group_t const group); + + /// Removes the group from the event + TaskContext& ClearGroup(); + + /// Returns the repeat counter which increases every time the task is repeated. + TaskScheduler::repeated_t GetRepeatCounter() const; + + /// Repeats the event and sets a new duration. + /// std::chrono::seconds(5) for example. + /// This will consume the task context, its not possible to repeat the task again + /// from the same task context! + template<class _Rep, class _Period> + TaskContext& Repeat(std::chrono::duration<_Rep, _Period> const& duration) + { + AssertOnConsumed(); + + // Set new duration, in-context timing and increment repeat counter + _task->_duration = duration; + _task->_end += duration; + _task->_repeated += 1; + (*_consumed) = true; + return Dispatch(std::bind(&TaskScheduler::InsertTask, std::placeholders::_1, _task)); + } + + /// Repeats the event with the same duration. + /// This will consume the task context, its not possible to repeat the task again + /// from the same task context! + TaskContext& Repeat() + { + return Repeat(_task->_duration); + } + + /// Repeats the event and set a new duration that is randomized between min and max. + /// std::chrono::seconds(5) for example. + /// This will consume the task context, its not possible to repeat the task again + /// from the same task context! + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskContext& Repeat(std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max) + { + return Repeat(TaskScheduler::RandomDurationBetween(min, max)); + } + + /// Schedule a callable function that is executed at the next update tick from within the context. + /// Its safe to modify the TaskScheduler from within the callable. + TaskContext& Async(std::function<void()> const& callable); + + /// Schedule an event with a fixed rate from within the context. + /// Its possible that the new event is executed immediately! + /// Use TaskScheduler::Async to create a task + /// which will be called at the next update tick. + template<class _Rep, class _Period> + TaskContext& Schedule(std::chrono::duration<_Rep, _Period> const& time, + TaskScheduler::task_handler_t const& task) + { + auto const end = _task->_end; + return Dispatch([end, time, task](TaskScheduler& scheduler) -> TaskScheduler& + { + return scheduler.ScheduleAt<_Rep, _Period>(end, time, task); + }); + } + + /// Schedule an event with a fixed rate from within the context. + /// Its possible that the new event is executed immediately! + /// Use TaskScheduler::Async to create a task + /// which will be called at the next update tick. + template<class _Rep, class _Period> + TaskContext& Schedule(std::chrono::duration<_Rep, _Period> const& time, + TaskScheduler::group_t const group, TaskScheduler::task_handler_t const& task) + { + auto const end = _task->_end; + return Dispatch([end, time, group, task](TaskScheduler& scheduler) -> TaskScheduler& + { + return scheduler.ScheduleAt<_Rep, _Period>(end, time, group, task); + }); + } + + /// Schedule an event with a randomized rate between min and max rate from within the context. + /// Its possible that the new event is executed immediately! + /// Use TaskScheduler::Async to create a task + /// which will be called at the next update tick. + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskContext& Schedule(std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max, TaskScheduler::task_handler_t const& task) + { + return Schedule(TaskScheduler::RandomDurationBetween(min, max), task); + } + + /// Schedule an event with a randomized rate between min and max rate from within the context. + /// Its possible that the new event is executed immediately! + /// Use TaskScheduler::Async to create a task + /// which will be called at the next update tick. + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskContext& Schedule(std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max, TaskScheduler::group_t const group, + TaskScheduler::task_handler_t const& task) + { + return Schedule(TaskScheduler::RandomDurationBetween(min, max), group, task); + } + + /// Cancels all tasks from within the context. + TaskContext& CancelAll(); + + /// Cancel all tasks of a single group from within the context. + TaskContext& CancelGroup(TaskScheduler::group_t const group); + + /// Cancels all groups in the given std::vector from within the context. + /// Hint: Use std::initializer_list for this: "{1, 2, 3, 4}" + TaskContext& CancelGroupsOf(std::vector<TaskScheduler::group_t> const& groups); + + /// Delays all tasks with the given duration from within the context. + template<class _Rep, class _Period> + TaskContext& DelayAll(std::chrono::duration<_Rep, _Period> const& duration) + { + return Dispatch(std::bind(&TaskScheduler::DelayAll<_Rep, _Period>, std::placeholders::_1, duration)); + } + + /// Delays all tasks with a random duration between min and max from within the context. + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskContext& DelayAll(std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max) + { + return DelayAll(TaskScheduler::RandomDurationBetween(min, max)); + } + + /// Delays all tasks of a group with the given duration from within the context. + template<class _Rep, class _Period> + TaskContext& DelayGroup(TaskScheduler::group_t const group, std::chrono::duration<_Rep, _Period> const& duration) + { + return Dispatch(std::bind(&TaskScheduler::DelayGroup<_Rep, _Period>, std::placeholders::_1, group, duration)); + } + + /// Delays all tasks of a group with a random duration between min and max from within the context. + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskContext& DelayGroup(TaskScheduler::group_t const group, + std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max) + { + return DelayGroup(group, TaskScheduler::RandomDurationBetween(min, max)); + } + + /// Reschedule all tasks with the given duration. + template<class _Rep, class _Period> + TaskContext& RescheduleAll(std::chrono::duration<_Rep, _Period> const& duration) + { + return Dispatch(std::bind(&TaskScheduler::RescheduleAll, std::placeholders::_1, duration)); + } + + /// Reschedule all tasks with a random duration between min and max. + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskContext& RescheduleAll(std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max) + { + return RescheduleAll(TaskScheduler::RandomDurationBetween(min, max)); + } + + /// Reschedule all tasks of a group with the given duration. + template<class _Rep, class _Period> + TaskContext& RescheduleGroup(TaskScheduler::group_t const group, std::chrono::duration<_Rep, _Period> const& duration) + { + return Dispatch(std::bind(&TaskScheduler::RescheduleGroup<_Rep, _Period>, std::placeholders::_1, group, duration)); + } + + /// Reschedule all tasks of a group with a random duration between min and max. + template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight> + TaskContext& RescheduleGroup(TaskScheduler::group_t const group, + std::chrono::duration<_RepLeft, _PeriodLeft> const& min, + std::chrono::duration<_RepRight, _PeriodRight> const& max) + { + return RescheduleGroup(group, TaskScheduler::RandomDurationBetween(min, max)); + } + +private: + /// Asserts if the task was consumed already. + void AssertOnConsumed() const; + + /// Invokes the associated hook of the task. + void Invoke(); +}; + +#endif /// _TASK_SCHEDULER_H_ diff --git a/src/common/Utilities/Timer.h b/src/common/Utilities/Timer.h new file mode 100644 index 00000000000..c54903d7be2 --- /dev/null +++ b/src/common/Utilities/Timer.h @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_TIMER_H +#define TRINITY_TIMER_H + +#include <chrono> + +inline uint32 getMSTime() +{ + using namespace std::chrono; + + static const system_clock::time_point ApplicationStartTime = system_clock::now(); + + return uint32(duration_cast<milliseconds>(system_clock::now() - ApplicationStartTime).count()); +} + +inline uint32 getMSTimeDiff(uint32 oldMSTime, uint32 newMSTime) +{ + // getMSTime() have limited data range and this is case when it overflow in this tick + if (oldMSTime > newMSTime) + return (0xFFFFFFFF - oldMSTime) + newMSTime; + else + return newMSTime - oldMSTime; +} + +inline uint32 GetMSTimeDiffToNow(uint32 oldMSTime) +{ + return getMSTimeDiff(oldMSTime, getMSTime()); +} + +struct IntervalTimer +{ +public: + + IntervalTimer() + : _interval(0), _current(0) + { + } + + void Update(time_t diff) + { + _current += diff; + if (_current < 0) + _current = 0; + } + + bool Passed() + { + return _current >= _interval; + } + + void Reset() + { + if (_current >= _interval) + _current %= _interval; + } + + void SetCurrent(time_t current) + { + _current = current; + } + + void SetInterval(time_t interval) + { + _interval = interval; + } + + time_t GetInterval() const + { + return _interval; + } + + time_t GetCurrent() const + { + return _current; + } + +private: + + time_t _interval; + time_t _current; +}; + +struct TimeTracker +{ +public: + + TimeTracker(time_t expiry) + : i_expiryTime(expiry) + { + } + + void Update(time_t diff) + { + i_expiryTime -= diff; + } + + bool Passed() const + { + return i_expiryTime <= 0; + } + + void Reset(time_t interval) + { + i_expiryTime = interval; + } + + time_t GetExpiry() const + { + return i_expiryTime; + } + +private: + + time_t i_expiryTime; +}; + +struct TimeTrackerSmall +{ +public: + + TimeTrackerSmall(uint32 expiry = 0) + : i_expiryTime(expiry) + { + } + + void Update(int32 diff) + { + i_expiryTime -= diff; + } + + bool Passed() const + { + return i_expiryTime <= 0; + } + + void Reset(uint32 interval) + { + i_expiryTime = interval; + } + + int32 GetExpiry() const + { + return i_expiryTime; + } + +private: + + int32 i_expiryTime; +}; + +struct PeriodicTimer +{ +public: + + PeriodicTimer(int32 period, int32 start_time) + : i_period(period), i_expireTime(start_time) + { + } + + bool Update(const uint32 diff) + { + if ((i_expireTime -= diff) > 0) + return false; + + i_expireTime += i_period > int32(diff) ? i_period : diff; + return true; + } + + void SetPeriodic(int32 period, int32 start_time) + { + i_expireTime = start_time; + i_period = period; + } + + // Tracker interface + void TUpdate(int32 diff) { i_expireTime -= diff; } + bool TPassed() const { return i_expireTime <= 0; } + void TReset(int32 diff, int32 period) { i_expireTime += period > diff ? period : diff; } + +private: + + int32 i_period; + int32 i_expireTime; +}; + +#endif diff --git a/src/common/Utilities/Util.cpp b/src/common/Utilities/Util.cpp new file mode 100644 index 00000000000..9f61dd12e4c --- /dev/null +++ b/src/common/Utilities/Util.cpp @@ -0,0 +1,569 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Util.h" +#include "Common.h" +#include "CompilerDefs.h" +#include "utf8.h" +#include "SFMT.h" +#include "Errors.h" // for ASSERT +#include <stdarg.h> +#include <boost/thread/tss.hpp> + +#if COMPILER == COMPILER_GNU + #include <sys/socket.h> + #include <netinet/in.h> + #include <arpa/inet.h> +#endif + +static boost::thread_specific_ptr<SFMTRand> sfmtRand; + +static SFMTRand* GetRng() +{ + SFMTRand* rand = sfmtRand.get(); + + if (!rand) + { + rand = new SFMTRand(); + sfmtRand.reset(rand); + } + + return rand; +} + +int32 irand(int32 min, int32 max) +{ + ASSERT(max >= min); + return int32(GetRng()->IRandom(min, max)); +} + +uint32 urand(uint32 min, uint32 max) +{ + ASSERT(max >= min); + return GetRng()->URandom(min, max); +} + +uint32 urandms(uint32 min, uint32 max) +{ + ASSERT(max >= min); + ASSERT(INT_MAX/IN_MILLISECONDS >= max); + return GetRng()->URandom(min * IN_MILLISECONDS, max * IN_MILLISECONDS); +} + +float frand(float min, float max) +{ + ASSERT(max >= min); + return float(GetRng()->Random() * (max - min) + min); +} + +uint32 rand32() +{ + return GetRng()->BRandom(); +} + +double rand_norm() +{ + return GetRng()->Random(); +} + +double rand_chance() +{ + return GetRng()->Random() * 100.0; +} + +Tokenizer::Tokenizer(const std::string &src, const char sep, uint32 vectorReserve) +{ + m_str = new char[src.length() + 1]; + memcpy(m_str, src.c_str(), src.length() + 1); + + if (vectorReserve) + m_storage.reserve(vectorReserve); + + char* posold = m_str; + char* posnew = m_str; + + for (;;) + { + if (*posnew == sep) + { + m_storage.push_back(posold); + posold = posnew + 1; + + *posnew = '\0'; + } + else if (*posnew == '\0') + { + // Hack like, but the old code accepted these kind of broken strings, + // so changing it would break other things + if (posold != posnew) + m_storage.push_back(posold); + + break; + } + + ++posnew; + } +} + +void stripLineInvisibleChars(std::string &str) +{ + static std::string const invChars = " \t\7\n"; + + size_t wpos = 0; + + bool space = false; + for (size_t pos = 0; pos < str.size(); ++pos) + { + if (invChars.find(str[pos])!=std::string::npos) + { + if (!space) + { + str[wpos++] = ' '; + space = true; + } + } + else + { + if (wpos!=pos) + str[wpos++] = str[pos]; + else + ++wpos; + space = false; + } + } + + if (wpos < str.size()) + str.erase(wpos, str.size()); + if (str.find("|TInterface")!=std::string::npos) + str.clear(); + +} + +#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) +struct tm* localtime_r(const time_t* time, struct tm *result) +{ + localtime_s(result, time); + return result; +} +#endif + +std::string secsToTimeString(uint64 timeInSecs, bool shortText, bool hoursOnly) +{ + uint64 secs = timeInSecs % MINUTE; + uint64 minutes = timeInSecs % HOUR / MINUTE; + uint64 hours = timeInSecs % DAY / HOUR; + uint64 days = timeInSecs / DAY; + + std::ostringstream ss; + if (days) + ss << days << (shortText ? "d" : " Day(s) "); + if (hours || hoursOnly) + ss << hours << (shortText ? "h" : " Hour(s) "); + if (!hoursOnly) + { + if (minutes) + ss << minutes << (shortText ? "m" : " Minute(s) "); + if (secs || (!days && !hours && !minutes) ) + ss << secs << (shortText ? "s" : " Second(s)."); + } + + return ss.str(); +} + +int32 MoneyStringToMoney(const std::string& moneyString) +{ + int32 money = 0; + + if (!(std::count(moneyString.begin(), moneyString.end(), 'g') == 1 || + std::count(moneyString.begin(), moneyString.end(), 's') == 1 || + std::count(moneyString.begin(), moneyString.end(), 'c') == 1)) + return 0; // Bad format + + Tokenizer tokens(moneyString, ' '); + for (Tokenizer::const_iterator itr = tokens.begin(); itr != tokens.end(); ++itr) + { + std::string tokenString(*itr); + size_t gCount = std::count(tokenString.begin(), tokenString.end(), 'g'); + size_t sCount = std::count(tokenString.begin(), tokenString.end(), 's'); + size_t cCount = std::count(tokenString.begin(), tokenString.end(), 'c'); + if (gCount + sCount + cCount != 1) + return 0; + + uint32 amount = atoi(*itr); + if (gCount == 1) + money += amount * 100 * 100; + else if (sCount == 1) + money += amount * 100; + else if (cCount == 1) + money += amount; + } + + return money; +} + +uint32 TimeStringToSecs(const std::string& timestring) +{ + uint32 secs = 0; + uint32 buffer = 0; + uint32 multiplier = 0; + + for (std::string::const_iterator itr = timestring.begin(); itr != timestring.end(); ++itr) + { + if (isdigit(*itr)) + { + buffer*=10; + buffer+= (*itr)-'0'; + } + else + { + switch (*itr) + { + case 'd': multiplier = DAY; break; + case 'h': multiplier = HOUR; break; + case 'm': multiplier = MINUTE; break; + case 's': multiplier = 1; break; + default : return 0; //bad format + } + buffer*=multiplier; + secs+=buffer; + buffer=0; + } + } + + return secs; +} + +std::string TimeToTimestampStr(time_t t) +{ + tm aTm; + localtime_r(&t, &aTm); + // YYYY year + // MM month (2 digits 01-12) + // DD day (2 digits 01-31) + // HH hour (2 digits 00-23) + // MM minutes (2 digits 00-59) + // SS seconds (2 digits 00-59) + char buf[20]; + snprintf(buf, 20, "%04d-%02d-%02d_%02d-%02d-%02d", aTm.tm_year+1900, aTm.tm_mon+1, aTm.tm_mday, aTm.tm_hour, aTm.tm_min, aTm.tm_sec); + return std::string(buf); +} + +/// Check if the string is a valid ip address representation +bool IsIPAddress(char const* ipaddress) +{ + if (!ipaddress) + return false; + + // Let the big boys do it. + // Drawback: all valid ip address formats are recognized e.g.: 12.23, 121234, 0xABCD) + return inet_addr(ipaddress) != INADDR_NONE; +} + +/// create PID file +uint32 CreatePIDFile(const std::string& filename) +{ + FILE* pid_file = fopen (filename.c_str(), "w" ); + if (pid_file == NULL) + return 0; + +#ifdef _WIN32 + DWORD pid = GetCurrentProcessId(); +#else + pid_t pid = getpid(); +#endif + + fprintf(pid_file, "%u", pid ); + fclose(pid_file); + + return (uint32)pid; +} + +size_t utf8length(std::string& utf8str) +{ + try + { + return utf8::distance(utf8str.c_str(), utf8str.c_str()+utf8str.size()); + } + catch(std::exception) + { + utf8str.clear(); + return 0; + } +} + +void utf8truncate(std::string& utf8str, size_t len) +{ + try + { + size_t wlen = utf8::distance(utf8str.c_str(), utf8str.c_str()+utf8str.size()); + if (wlen <= len) + return; + + std::wstring wstr; + wstr.resize(wlen); + utf8::utf8to16(utf8str.c_str(), utf8str.c_str()+utf8str.size(), &wstr[0]); + wstr.resize(len); + char* oend = utf8::utf16to8(wstr.c_str(), wstr.c_str()+wstr.size(), &utf8str[0]); + utf8str.resize(oend-(&utf8str[0])); // remove unused tail + } + catch(std::exception) + { + utf8str.clear(); + } +} + +bool Utf8toWStr(char const* utf8str, size_t csize, wchar_t* wstr, size_t& wsize) +{ + try + { + size_t len = utf8::distance(utf8str, utf8str+csize); + if (len > wsize) + { + if (wsize > 0) + wstr[0] = L'\0'; + wsize = 0; + return false; + } + + wsize = len; + utf8::utf8to16(utf8str, utf8str+csize, wstr); + wstr[len] = L'\0'; + } + catch(std::exception) + { + if (wsize > 0) + wstr[0] = L'\0'; + wsize = 0; + return false; + } + + return true; +} + +bool Utf8toWStr(const std::string& utf8str, std::wstring& wstr) +{ + try + { + if (size_t len = utf8::distance(utf8str.c_str(), utf8str.c_str()+utf8str.size())) + { + wstr.resize(len); + utf8::utf8to16(utf8str.c_str(), utf8str.c_str()+utf8str.size(), &wstr[0]); + } + } + catch(std::exception) + { + wstr.clear(); + return false; + } + + return true; +} + +bool WStrToUtf8(wchar_t* wstr, size_t size, std::string& utf8str) +{ + try + { + std::string utf8str2; + utf8str2.resize(size*4); // allocate for most long case + + if (size) + { + char* oend = utf8::utf16to8(wstr, wstr+size, &utf8str2[0]); + utf8str2.resize(oend-(&utf8str2[0])); // remove unused tail + } + utf8str = utf8str2; + } + catch(std::exception) + { + utf8str.clear(); + return false; + } + + return true; +} + +bool WStrToUtf8(std::wstring const& wstr, std::string& utf8str) +{ + try + { + std::string utf8str2; + utf8str2.resize(wstr.size()*4); // allocate for most long case + + if (wstr.size()) + { + char* oend = utf8::utf16to8(wstr.c_str(), wstr.c_str()+wstr.size(), &utf8str2[0]); + utf8str2.resize(oend-(&utf8str2[0])); // remove unused tail + } + utf8str = utf8str2; + } + catch(std::exception) + { + utf8str.clear(); + return false; + } + + return true; +} + +typedef wchar_t const* const* wstrlist; + +std::wstring GetMainPartOfName(std::wstring const& wname, uint32 declension) +{ + // supported only Cyrillic cases + if (wname.size() < 1 || !isCyrillicCharacter(wname[0]) || declension > 5) + return wname; + + // Important: end length must be <= MAX_INTERNAL_PLAYER_NAME-MAX_PLAYER_NAME (3 currently) + + static wchar_t const a_End[] = { wchar_t(1), wchar_t(0x0430), wchar_t(0x0000)}; + static wchar_t const o_End[] = { wchar_t(1), wchar_t(0x043E), wchar_t(0x0000)}; + static wchar_t const ya_End[] = { wchar_t(1), wchar_t(0x044F), wchar_t(0x0000)}; + static wchar_t const ie_End[] = { wchar_t(1), wchar_t(0x0435), wchar_t(0x0000)}; + static wchar_t const i_End[] = { wchar_t(1), wchar_t(0x0438), wchar_t(0x0000)}; + static wchar_t const yeru_End[] = { wchar_t(1), wchar_t(0x044B), wchar_t(0x0000)}; + static wchar_t const u_End[] = { wchar_t(1), wchar_t(0x0443), wchar_t(0x0000)}; + static wchar_t const yu_End[] = { wchar_t(1), wchar_t(0x044E), wchar_t(0x0000)}; + static wchar_t const oj_End[] = { wchar_t(2), wchar_t(0x043E), wchar_t(0x0439), wchar_t(0x0000)}; + static wchar_t const ie_j_End[] = { wchar_t(2), wchar_t(0x0435), wchar_t(0x0439), wchar_t(0x0000)}; + static wchar_t const io_j_End[] = { wchar_t(2), wchar_t(0x0451), wchar_t(0x0439), wchar_t(0x0000)}; + static wchar_t const o_m_End[] = { wchar_t(2), wchar_t(0x043E), wchar_t(0x043C), wchar_t(0x0000)}; + static wchar_t const io_m_End[] = { wchar_t(2), wchar_t(0x0451), wchar_t(0x043C), wchar_t(0x0000)}; + static wchar_t const ie_m_End[] = { wchar_t(2), wchar_t(0x0435), wchar_t(0x043C), wchar_t(0x0000)}; + static wchar_t const soft_End[] = { wchar_t(1), wchar_t(0x044C), wchar_t(0x0000)}; + static wchar_t const j_End[] = { wchar_t(1), wchar_t(0x0439), wchar_t(0x0000)}; + + static wchar_t const* const dropEnds[6][8] = { + { &a_End[1], &o_End[1], &ya_End[1], &ie_End[1], &soft_End[1], &j_End[1], NULL, NULL }, + { &a_End[1], &ya_End[1], &yeru_End[1], &i_End[1], NULL, NULL, NULL, NULL }, + { &ie_End[1], &u_End[1], &yu_End[1], &i_End[1], NULL, NULL, NULL, NULL }, + { &u_End[1], &yu_End[1], &o_End[1], &ie_End[1], &soft_End[1], &ya_End[1], &a_End[1], NULL }, + { &oj_End[1], &io_j_End[1], &ie_j_End[1], &o_m_End[1], &io_m_End[1], &ie_m_End[1], &yu_End[1], NULL }, + { &ie_End[1], &i_End[1], NULL, NULL, NULL, NULL, NULL, NULL } + }; + + for (wchar_t const* const* itr = &dropEnds[declension][0]; *itr; ++itr) + { + size_t len = size_t((*itr)[-1]); // get length from string size field + + if (wname.substr(wname.size()-len, len)==*itr) + return wname.substr(0, wname.size()-len); + } + + return wname; +} + +bool utf8ToConsole(const std::string& utf8str, std::string& conStr) +{ +#if PLATFORM == PLATFORM_WINDOWS + std::wstring wstr; + if (!Utf8toWStr(utf8str, wstr)) + return false; + + conStr.resize(wstr.size()); + CharToOemBuffW(&wstr[0], &conStr[0], wstr.size()); +#else + // not implemented yet + conStr = utf8str; +#endif + + return true; +} + +bool consoleToUtf8(const std::string& conStr, std::string& utf8str) +{ +#if PLATFORM == PLATFORM_WINDOWS + std::wstring wstr; + wstr.resize(conStr.size()); + OemToCharBuffW(&conStr[0], &wstr[0], conStr.size()); + + return WStrToUtf8(wstr, utf8str); +#else + // not implemented yet + utf8str = conStr; + return true; +#endif +} + +bool Utf8FitTo(const std::string& str, std::wstring const& search) +{ + std::wstring temp; + + if (!Utf8toWStr(str, temp)) + return false; + + // converting to lower case + wstrToLower( temp ); + + if (temp.find(search) == std::wstring::npos) + return false; + + return true; +} + +void utf8printf(FILE* out, const char *str, ...) +{ + va_list ap; + va_start(ap, str); + vutf8printf(out, str, &ap); + va_end(ap); +} + +void vutf8printf(FILE* out, const char *str, va_list* ap) +{ +#if PLATFORM == PLATFORM_WINDOWS + char temp_buf[32*1024]; + wchar_t wtemp_buf[32*1024]; + + size_t temp_len = vsnprintf(temp_buf, 32*1024, str, *ap); + //vsnprintf returns -1 if the buffer is too small + if (temp_len == size_t(-1)) + temp_len = 32*1024-1; + + size_t wtemp_len = 32*1024-1; + Utf8toWStr(temp_buf, temp_len, wtemp_buf, wtemp_len); + + CharToOemBuffW(&wtemp_buf[0], &temp_buf[0], wtemp_len+1); + fprintf(out, "%s", temp_buf); +#else + vfprintf(out, str, *ap); +#endif +} + +std::string ByteArrayToHexStr(uint8 const* bytes, uint32 arrayLen, bool reverse /* = false */) +{ + int32 init = 0; + int32 end = arrayLen; + int8 op = 1; + + if (reverse) + { + init = arrayLen - 1; + end = -1; + op = -1; + } + + std::ostringstream ss; + for (int32 i = init; i != end; i += op) + { + char buffer[4]; + sprintf(buffer, "%02X", bytes[i]); + ss << buffer; + } + + return ss.str(); +} diff --git a/src/common/Utilities/Util.h b/src/common/Utilities/Util.h new file mode 100644 index 00000000000..b748e83408b --- /dev/null +++ b/src/common/Utilities/Util.h @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _UTIL_H +#define _UTIL_H + +#include "Define.h" +#include "Errors.h" + +#include <algorithm> +#include <string> +#include <vector> +#include <list> +#include <map> + +// Searcher for map of structs +template<typename T, class S> struct Finder +{ + T val_; + T S::* idMember_; + + Finder(T val, T S::* idMember) : val_(val), idMember_(idMember) {} + bool operator()(const std::pair<int, S> &obj) { return obj.second.*idMember_ == val_; } +}; + +class Tokenizer +{ +public: + typedef std::vector<char const*> StorageType; + + typedef StorageType::size_type size_type; + + typedef StorageType::const_iterator const_iterator; + typedef StorageType::reference reference; + typedef StorageType::const_reference const_reference; + +public: + Tokenizer(const std::string &src, char const sep, uint32 vectorReserve = 0); + ~Tokenizer() { delete[] m_str; } + + const_iterator begin() const { return m_storage.begin(); } + const_iterator end() const { return m_storage.end(); } + + size_type size() const { return m_storage.size(); } + + reference operator [] (size_type i) { return m_storage[i]; } + const_reference operator [] (size_type i) const { return m_storage[i]; } + +private: + char* m_str; + StorageType m_storage; +}; + +void stripLineInvisibleChars(std::string &src); + +int32 MoneyStringToMoney(const std::string& moneyString); + +struct tm* localtime_r(const time_t* time, struct tm *result); + +std::string secsToTimeString(uint64 timeInSecs, bool shortText = false, bool hoursOnly = false); +uint32 TimeStringToSecs(const std::string& timestring); +std::string TimeToTimestampStr(time_t t); + +/* Return a random number in the range min..max. */ +int32 irand(int32 min, int32 max); + +/* Return a random number in the range min..max (inclusive). */ +uint32 urand(uint32 min, uint32 max); + +/* Return a random millisecond value between min and max seconds. Functionally equivalent to urand(min*IN_MILLISECONDS, max*IN_MILLISECONDS). */ +uint32 urandms(uint32 min, uint32 max); + +/* Return a random number in the range 0 .. UINT32_MAX. */ +uint32 rand32(); + +/* Return a random number in the range min..max */ +float frand(float min, float max); + +/* Return a random double from 0.0 to 1.0 (exclusive). */ +double rand_norm(); + +/* Return a random double from 0.0 to 100.0 (exclusive). */ +double rand_chance(); + +/* Return true if a random roll fits in the specified chance (range 0-100). */ +inline bool roll_chance_f(float chance) +{ + return chance > rand_chance(); +} + +/* Return true if a random roll fits in the specified chance (range 0-100). */ +inline bool roll_chance_i(int chance) +{ + return chance > irand(0, 99); +} + +inline void ApplyPercentModFloatVar(float& var, float val, bool apply) +{ + if (val == -100.0f) // prevent set var to zero + val = -99.99f; + var *= (apply ? (100.0f + val) / 100.0f : 100.0f / (100.0f + val)); +} + +// Percentage calculation +template <class T, class U> +inline T CalculatePct(T base, U pct) +{ + return T(base * static_cast<float>(pct) / 100.0f); +} + +template <class T, class U> +inline T AddPct(T &base, U pct) +{ + return base += CalculatePct(base, pct); +} + +template <class T, class U> +inline T ApplyPct(T &base, U pct) +{ + return base = CalculatePct(base, pct); +} + +template <class T> +inline T RoundToInterval(T& num, T floor, T ceil) +{ + return num = std::min(std::max(num, floor), ceil); +} + +// UTF8 handling +bool Utf8toWStr(const std::string& utf8str, std::wstring& wstr); +// in wsize==max size of buffer, out wsize==real string size +bool Utf8toWStr(char const* utf8str, size_t csize, wchar_t* wstr, size_t& wsize); +inline bool Utf8toWStr(const std::string& utf8str, wchar_t* wstr, size_t& wsize) +{ + return Utf8toWStr(utf8str.c_str(), utf8str.size(), wstr, wsize); +} + +bool WStrToUtf8(std::wstring const& wstr, std::string& utf8str); +// size==real string size +bool WStrToUtf8(wchar_t* wstr, size_t size, std::string& utf8str); + +size_t utf8length(std::string& utf8str); // set string to "" if invalid utf8 sequence +void utf8truncate(std::string& utf8str, size_t len); + +inline bool isBasicLatinCharacter(wchar_t wchar) +{ + if (wchar >= L'a' && wchar <= L'z') // LATIN SMALL LETTER A - LATIN SMALL LETTER Z + return true; + if (wchar >= L'A' && wchar <= L'Z') // LATIN CAPITAL LETTER A - LATIN CAPITAL LETTER Z + return true; + return false; +} + +inline bool isExtendedLatinCharacter(wchar_t wchar) +{ + if (isBasicLatinCharacter(wchar)) + return true; + if (wchar >= 0x00C0 && wchar <= 0x00D6) // LATIN CAPITAL LETTER A WITH GRAVE - LATIN CAPITAL LETTER O WITH DIAERESIS + return true; + if (wchar >= 0x00D8 && wchar <= 0x00DE) // LATIN CAPITAL LETTER O WITH STROKE - LATIN CAPITAL LETTER THORN + return true; + if (wchar == 0x00DF) // LATIN SMALL LETTER SHARP S + return true; + if (wchar >= 0x00E0 && wchar <= 0x00F6) // LATIN SMALL LETTER A WITH GRAVE - LATIN SMALL LETTER O WITH DIAERESIS + return true; + if (wchar >= 0x00F8 && wchar <= 0x00FE) // LATIN SMALL LETTER O WITH STROKE - LATIN SMALL LETTER THORN + return true; + if (wchar >= 0x0100 && wchar <= 0x012F) // LATIN CAPITAL LETTER A WITH MACRON - LATIN SMALL LETTER I WITH OGONEK + return true; + if (wchar == 0x1E9E) // LATIN CAPITAL LETTER SHARP S + return true; + return false; +} + +inline bool isCyrillicCharacter(wchar_t wchar) +{ + if (wchar >= 0x0410 && wchar <= 0x044F) // CYRILLIC CAPITAL LETTER A - CYRILLIC SMALL LETTER YA + return true; + if (wchar == 0x0401 || wchar == 0x0451) // CYRILLIC CAPITAL LETTER IO, CYRILLIC SMALL LETTER IO + return true; + return false; +} + +inline bool isEastAsianCharacter(wchar_t wchar) +{ + if (wchar >= 0x1100 && wchar <= 0x11F9) // Hangul Jamo + return true; + if (wchar >= 0x3041 && wchar <= 0x30FF) // Hiragana + Katakana + return true; + if (wchar >= 0x3131 && wchar <= 0x318E) // Hangul Compatibility Jamo + return true; + if (wchar >= 0x31F0 && wchar <= 0x31FF) // Katakana Phonetic Ext. + return true; + if (wchar >= 0x3400 && wchar <= 0x4DB5) // CJK Ideographs Ext. A + return true; + if (wchar >= 0x4E00 && wchar <= 0x9FC3) // Unified CJK Ideographs + return true; + if (wchar >= 0xAC00 && wchar <= 0xD7A3) // Hangul Syllables + return true; + if (wchar >= 0xFF01 && wchar <= 0xFFEE) // Halfwidth forms + return true; + return false; +} + +inline bool isNumeric(wchar_t wchar) +{ + return (wchar >= L'0' && wchar <=L'9'); +} + +inline bool isNumeric(char c) +{ + return (c >= '0' && c <='9'); +} + +inline bool isNumeric(char const* str) +{ + for (char const* c = str; *c; ++c) + if (!isNumeric(*c)) + return false; + + return true; +} + +inline bool isNumericOrSpace(wchar_t wchar) +{ + return isNumeric(wchar) || wchar == L' '; +} + +inline bool isBasicLatinString(const std::wstring &wstr, bool numericOrSpace) +{ + for (size_t i = 0; i < wstr.size(); ++i) + if (!isBasicLatinCharacter(wstr[i]) && (!numericOrSpace || !isNumericOrSpace(wstr[i]))) + return false; + return true; +} + +inline bool isExtendedLatinString(const std::wstring &wstr, bool numericOrSpace) +{ + for (size_t i = 0; i < wstr.size(); ++i) + if (!isExtendedLatinCharacter(wstr[i]) && (!numericOrSpace || !isNumericOrSpace(wstr[i]))) + return false; + return true; +} + +inline bool isCyrillicString(const std::wstring &wstr, bool numericOrSpace) +{ + for (size_t i = 0; i < wstr.size(); ++i) + if (!isCyrillicCharacter(wstr[i]) && (!numericOrSpace || !isNumericOrSpace(wstr[i]))) + return false; + return true; +} + +inline bool isEastAsianString(const std::wstring &wstr, bool numericOrSpace) +{ + for (size_t i = 0; i < wstr.size(); ++i) + if (!isEastAsianCharacter(wstr[i]) && (!numericOrSpace || !isNumericOrSpace(wstr[i]))) + return false; + return true; +} + +inline wchar_t wcharToUpper(wchar_t wchar) +{ + if (wchar >= L'a' && wchar <= L'z') // LATIN SMALL LETTER A - LATIN SMALL LETTER Z + return wchar_t(uint16(wchar)-0x0020); + if (wchar == 0x00DF) // LATIN SMALL LETTER SHARP S + return wchar_t(0x1E9E); + if (wchar >= 0x00E0 && wchar <= 0x00F6) // LATIN SMALL LETTER A WITH GRAVE - LATIN SMALL LETTER O WITH DIAERESIS + return wchar_t(uint16(wchar)-0x0020); + if (wchar >= 0x00F8 && wchar <= 0x00FE) // LATIN SMALL LETTER O WITH STROKE - LATIN SMALL LETTER THORN + return wchar_t(uint16(wchar)-0x0020); + if (wchar >= 0x0101 && wchar <= 0x012F) // LATIN SMALL LETTER A WITH MACRON - LATIN SMALL LETTER I WITH OGONEK (only %2=1) + { + if (wchar % 2 == 1) + return wchar_t(uint16(wchar)-0x0001); + } + if (wchar >= 0x0430 && wchar <= 0x044F) // CYRILLIC SMALL LETTER A - CYRILLIC SMALL LETTER YA + return wchar_t(uint16(wchar)-0x0020); + if (wchar == 0x0451) // CYRILLIC SMALL LETTER IO + return wchar_t(0x0401); + + return wchar; +} + +inline wchar_t wcharToUpperOnlyLatin(wchar_t wchar) +{ + return isBasicLatinCharacter(wchar) ? wcharToUpper(wchar) : wchar; +} + +inline wchar_t wcharToLower(wchar_t wchar) +{ + if (wchar >= L'A' && wchar <= L'Z') // LATIN CAPITAL LETTER A - LATIN CAPITAL LETTER Z + return wchar_t(uint16(wchar)+0x0020); + if (wchar >= 0x00C0 && wchar <= 0x00D6) // LATIN CAPITAL LETTER A WITH GRAVE - LATIN CAPITAL LETTER O WITH DIAERESIS + return wchar_t(uint16(wchar)+0x0020); + if (wchar >= 0x00D8 && wchar <= 0x00DE) // LATIN CAPITAL LETTER O WITH STROKE - LATIN CAPITAL LETTER THORN + return wchar_t(uint16(wchar)+0x0020); + if (wchar >= 0x0100 && wchar <= 0x012E) // LATIN CAPITAL LETTER A WITH MACRON - LATIN CAPITAL LETTER I WITH OGONEK (only %2=0) + { + if (wchar % 2 == 0) + return wchar_t(uint16(wchar)+0x0001); + } + if (wchar == 0x1E9E) // LATIN CAPITAL LETTER SHARP S + return wchar_t(0x00DF); + if (wchar == 0x0401) // CYRILLIC CAPITAL LETTER IO + return wchar_t(0x0451); + if (wchar >= 0x0410 && wchar <= 0x042F) // CYRILLIC CAPITAL LETTER A - CYRILLIC CAPITAL LETTER YA + return wchar_t(uint16(wchar)+0x0020); + + return wchar; +} + +inline void wstrToUpper(std::wstring& str) +{ + std::transform( str.begin(), str.end(), str.begin(), wcharToUpper ); +} + +inline void wstrToLower(std::wstring& str) +{ + std::transform( str.begin(), str.end(), str.begin(), wcharToLower ); +} + +std::wstring GetMainPartOfName(std::wstring const& wname, uint32 declension); + +bool utf8ToConsole(const std::string& utf8str, std::string& conStr); +bool consoleToUtf8(const std::string& conStr, std::string& utf8str); +bool Utf8FitTo(const std::string& str, std::wstring const& search); +void utf8printf(FILE* out, const char *str, ...); +void vutf8printf(FILE* out, const char *str, va_list* ap); + +bool IsIPAddress(char const* ipaddress); + +uint32 CreatePIDFile(const std::string& filename); + +std::string ByteArrayToHexStr(uint8 const* bytes, uint32 length, bool reverse = false); + +// simple class for not-modifyable list +template <typename T> +class HookList +{ + typedef typename std::list<T>::iterator ListIterator; + private: + typename std::list<T> m_list; + public: + HookList<T> & operator+=(T t) + { + m_list.push_back(t); + return *this; + } + HookList<T> & operator-=(T t) + { + m_list.remove(t); + return *this; + } + size_t size() + { + return m_list.size(); + } + ListIterator begin() + { + return m_list.begin(); + } + ListIterator end() + { + return m_list.end(); + } +}; + +class flag96 +{ +private: + uint32 part[3]; + +public: + flag96(uint32 p1 = 0, uint32 p2 = 0, uint32 p3 = 0) + { + part[0] = p1; + part[1] = p2; + part[2] = p3; + } + + inline bool IsEqual(uint32 p1 = 0, uint32 p2 = 0, uint32 p3 = 0) const + { + return (part[0] == p1 && part[1] == p2 && part[2] == p3); + } + + inline bool HasFlag(uint32 p1 = 0, uint32 p2 = 0, uint32 p3 = 0) const + { + return (part[0] & p1 || part[1] & p2 || part[2] & p3); + } + + inline void Set(uint32 p1 = 0, uint32 p2 = 0, uint32 p3 = 0) + { + part[0] = p1; + part[1] = p2; + part[2] = p3; + } + + inline bool operator <(const flag96 &right) const + { + for (uint8 i = 3; i > 0; --i) + { + if (part[i - 1] < right.part[i - 1]) + return true; + else if (part[i - 1] > right.part[i - 1]) + return false; + } + return false; + } + + inline bool operator ==(const flag96 &right) const + { + return + ( + part[0] == right.part[0] && + part[1] == right.part[1] && + part[2] == right.part[2] + ); + } + + inline bool operator !=(const flag96 &right) const + { + return !this->operator ==(right); + } + + inline flag96 & operator =(const flag96 &right) + { + part[0] = right.part[0]; + part[1] = right.part[1]; + part[2] = right.part[2]; + return *this; + } + + inline flag96 operator &(const flag96 &right) const + { + return flag96(part[0] & right.part[0], part[1] & right.part[1], + part[2] & right.part[2]); + } + + inline flag96 & operator &=(const flag96 &right) + { + part[0] &= right.part[0]; + part[1] &= right.part[1]; + part[2] &= right.part[2]; + return *this; + } + + inline flag96 operator |(const flag96 &right) const + { + return flag96(part[0] | right.part[0], part[1] | right.part[1], + part[2] | right.part[2]); + } + + inline flag96 & operator |=(const flag96 &right) + { + part[0] |= right.part[0]; + part[1] |= right.part[1]; + part[2] |= right.part[2]; + return *this; + } + + inline flag96 operator ~() const + { + return flag96(~part[0], ~part[1], ~part[2]); + } + + inline flag96 operator ^(const flag96 &right) const + { + return flag96(part[0] ^ right.part[0], part[1] ^ right.part[1], + part[2] ^ right.part[2]); + } + + inline flag96 & operator ^=(const flag96 &right) + { + part[0] ^= right.part[0]; + part[1] ^= right.part[1]; + part[2] ^= right.part[2]; + return *this; + } + + inline operator bool() const + { + return (part[0] != 0 || part[1] != 0 || part[2] != 0); + } + + inline bool operator !() const + { + return !this->operator bool(); + } + + inline uint32 & operator [](uint8 el) + { + return part[el]; + } + + inline const uint32 & operator [](uint8 el) const + { + return part[el]; + } +}; + +enum ComparisionType +{ + COMP_TYPE_EQ = 0, + COMP_TYPE_HIGH, + COMP_TYPE_LOW, + COMP_TYPE_HIGH_EQ, + COMP_TYPE_LOW_EQ, + COMP_TYPE_MAX +}; + +template <class T> +bool CompareValues(ComparisionType type, T val1, T val2) +{ + switch (type) + { + case COMP_TYPE_EQ: + return val1 == val2; + case COMP_TYPE_HIGH: + return val1 > val2; + case COMP_TYPE_LOW: + return val1 < val2; + case COMP_TYPE_HIGH_EQ: + return val1 >= val2; + case COMP_TYPE_LOW_EQ: + return val1 <= val2; + default: + // incorrect parameter + ABORT(); + return false; + } +} + +#endif |