From 0c3e3efc00ca19aabbf3fa752088cc006fc00776 Mon Sep 17 00:00:00 2001 From: Saif Date: Sat, 22 Aug 2015 02:17:14 +0500 Subject: [PATCH 01/33] Moved changes to public repository. This will be CAIO's permanent supported repository now. --- .gitmodules | 3 + CAIO LICENSE.txt | 674 +++++++++++++++++++ CAIO README.md | 310 +++++++++ dep/smallfolk_cpp/smallfolk_cpp | 1 + sql/CAIO/Auth.sql | 2 + sql/CAIO/World.sql | 24 + src/common/Logging/Log.h | 15 + src/server/game/Accounts/RBAC.h | 2 + src/server/game/CMakeLists.txt | 1 + src/server/game/Chat/AIOMsg.cpp | 99 +++ src/server/game/Chat/AIOMsg.h | 42 ++ src/server/game/Entities/Player/Player.cpp | 104 +++ src/server/game/Entities/Player/Player.h | 44 ++ src/server/game/Handlers/ChatHandler.cpp | 103 +++ src/server/game/Miscellaneous/Language.h | 8 +- src/server/game/Scripting/ScriptLoader.cpp | 18 +- src/server/game/Scripting/ScriptLoader.h | 1 + src/server/game/Scripting/ScriptMgr.cpp | 240 ++++++- src/server/game/Scripting/ScriptMgr.h | 173 +++++ src/server/game/Server/WorldSession.cpp | 21 + src/server/game/Server/WorldSession.h | 15 + src/server/game/World/World.cpp | 195 ++++++ src/server/game/World/World.h | 85 +++ src/server/scripts/AIO/CMakeLists.txt | 18 + src/server/scripts/CMakeLists.txt | 2 + src/server/scripts/Commands/cs_caio.cpp | 245 +++++++ src/server/worldserver/CMakeLists.txt | 3 + src/server/worldserver/worldserver.conf.dist | 34 + 28 files changed, 2479 insertions(+), 3 deletions(-) create mode 100644 .gitmodules create mode 100644 CAIO LICENSE.txt create mode 100644 CAIO README.md create mode 160000 dep/smallfolk_cpp/smallfolk_cpp create mode 100644 sql/CAIO/Auth.sql create mode 100644 sql/CAIO/World.sql create mode 100644 src/server/game/Chat/AIOMsg.cpp create mode 100644 src/server/game/Chat/AIOMsg.h create mode 100644 src/server/scripts/AIO/CMakeLists.txt create mode 100644 src/server/scripts/Commands/cs_caio.cpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000..5d71b4f21514c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "dep/smallfolk_cpp/smallfolk_cpp"] + path = dep/smallfolk_cpp/smallfolk_cpp + url = https://github.com/Rochet2/smallfolk_cpp.git \ No newline at end of file diff --git a/CAIO LICENSE.txt b/CAIO LICENSE.txt new file mode 100644 index 0000000000000..94a9ed024d385 --- /dev/null +++ b/CAIO LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 3 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/CAIO README.md b/CAIO README.md new file mode 100644 index 0000000000000..54c47f70cff2c --- /dev/null +++ b/CAIO README.md @@ -0,0 +1,310 @@ +## Introduction + +CAIO is a server-client communication system for WoW AddOns. It is an extension of [AIO](https://github.com/Rochet2/AIO) to support C++ server side handling. +CAIO is designed for sending lua addons and data between players and server +Currently CAIO only supports TrinityCore 3.3.5 branch. + +## Supported AIO version + +AIO Version 1.7 + +## Install + ++ Clone this repository/branch or merge with your own TrinityCore 3.3.5 branch ++ Build/Install TrinityCore ++ [Install(Add) (C)AIO scripts](#api-reference) ++ Run SQL files from `TrinityCore_Installation_Dir/sql/CAIO` to insert commands, permissions and strings ++ Copy `AIO_Client` folder from [AIO](https://github.com/Rochet2/AIO) repository to `WoW_Installation_Dir/Interface/AddOns` ++ Copy your client side addons to `TrinityCore_Installation_Dir/lua_client_scripts` + +## Todo + ++ Use LuaVal as script/handler key type to allow types other than just string ++ Add CAIO `Init` time-out configuration ++ Add CAIO buffer time-out configuration ++ Add CAIO error time-out configuration ++ Add CAIO maximum cache size configuration ++ Implement Obfuscation ++ Implement Compression ++ Add individual permissions for each CAIO command + +## API reference + +### Creating a CAIO script + +```cpp +class ExampleCAIOScript : public AIOScript +{ +public: + ExampleCAIOScript() + : AIOScript("ExampleScriptName") + { + using namespace std::placeholders; + + // Loads addon files to addons list and sends them on AIO client initialization + // Looks for the file in path config AIO.ClientScriptPath + AddAddon(World::AIOAddon("ExampleAddon", "example_addon.lua")); + + // You can also add addons to be sent to players with specific permission + AddAddon(World::AIOAddon("AnotherAddon", "example_addon.lua", 192)); //192 refers to gm level 3 RBAC permission + + // Handler function signature: void HandlerFunction(Player *sender, const LuaVal &args) + AddHandler("Print", std::bind(&ExampleCAIOScript::HandlePrint, this, _1, _2)); + AddHandler("Save", std::bind(&ExampleCAIOScript::HandleSave, this, _1, _2)); + + // Initialization handler and arguments + AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleCAIOScript::InitArg, this, _1), std::bind(&ExampleCAIOScript::InitArg, this, _1)); + //Adds additional argument to send to handler + AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleCAIOScript::InitArg2, this, _1)); + AddInitArgs("AnotherScript", "InitB"); //Arguments are not necessary + } + + void HandlePrint(Player *sender, const LuaVal &args) + { + //LuaVal args in a handler function is always a table + //Handler arguments index starts from 4 + LuaVal &InputVal = args[4]; + LuaVal &SliderVal = args[5]; + + //MUST check if the value type is valid or else smallfolk_cpp will + //throw on obtaining that type + if(!InputVal.isstring() || !SliderVal.isnumber()) + { + return; + } + + sender->GetSession()->SendNotification("HandlePrint -> Stored String: %s, Input: %s, Slider Value: %f", + storedString.c_str(), InputVal.str().c_str(), SliderVal.num()); + } + + void HandleSave(Player *sender, const LuaVal &args) + { + //LuaVal args in a handler function is always a table + //Handler arguments index starts from 4 + LuaVal &SaveVal = args.get[4]; + + //MUST check if the value type is valid + if(!SaveVal.isstring()) + { + return; + } + + storedString = SaveVal.str(); + sender->GetSession()->SendNotification("Saved"); + } + + LuaVal InitArg(Player *sender) + { + LuaVal arg = LuaVal(TTABLE); + arg.set("key", 12.3); + arg["key2"] = false; + + return arg; + } + + LuaVal InitArg2(Player *sender) + { + return "LuaVal will implicitly create a string LuaVal for this arg"; + } + +private: + std::string storedString; +}; +``` + +### smallfolk_cpp LuaVal reference + +https://github.com/Rochet2/smallfolk_cpp + +### CAIO reference and functions + +**ScriptMgr.h** + +```cpp +class AIOScript : public ScriptObject +{ +protected: + // Registers an AIO Handler script of scriptName + AIOScript(const char *scriptName); + + // Registers a handler function to call when handling + // handlerName of this script. + void AddHandler(const char *handlerName, HandlerFunc function); + + // Adds a client side handler to call and adds arguments + // to sends with it for AIO client initialization. + // + // You can add additional arguments to the handler by + // calling this function again + void AddInitArgs(const std::string &scriptName, const std::string &handlerName, + ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), + ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); + + // Adds a WoW addon file to the list of addons with a unique + // addon name to send on AIO client initialization. + // Returns true if addon was added, false if addon name is taken. + // + // It is required to call World::ForceReloadPlayerAddons() + // if addons are added after server is fully initialized + // for online players to load the added addons. + bool AddAddon(const World::AIOAddon &addon); + + // Returns pointer to an AIO script by its name and typename. + // Returns null if scriptName doesn't exist or typename was incorrect. + template + ScriptClass *GetScript(const std::string &scriptName); +} +``` + +**AIOMsg.h** + +```cpp +class AIOMsg +{ +public: + //Creates an empty AIOMsg + AIOMsg(); + + //Creates a AIO message and adds one block + AIOMsg(const std::string &scriptName, const std::string &handlerName, + const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + + //Adds another block + //Another block will call another handler in one message + AIOMsg &Add(const std::string &scriptName, const std::string &handlerName, + const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + + //Appends the last block + //You can add additional arguments to the last block + AIOMsg &AppendLast(const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + + //Returns smallfolk dump of the AIO message + std::string dumps() const; +``` + +**Player.h** + +```cpp +//Returns whether AIO client has been initialized +bool AIOInitialized() const; + +// Sends an AIO message to the player +// See: class AIOMsg +void AIOMessage(AIOMsg &msg); + +// Triggers an AIO script handler on the client +// To trigger multiple handlers in one message or to send more +// arguments use Player::AIOMessage +void AIOHandle(const std::string &scriptName, const std::string &handlerName, + const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + +// AIO can only understand smallfolk LuaVal::dumps() format +// Handler functions are called by creating a table as below +// { +// {n, ScriptName, HandlerName(optional), Arg1..N(optional) }, +// {n, AnotherScriptName, AnotherHandlerName(optional), Arg1..N(optional) } +// } +// Where n is number of arguments including handler name as an argument +void SendSimpleAIOMessage(const std::string &message); + +// Forces reload on the player AIO addons +// Syncs player AIO addons with server +void ForceReloadAddons(); + +// Force reset on the player AIO addons +// Player AIO addons and addon data is deleted and downloaded again +void ForceResetAddons(); + +bool isAIOInitOnCooldown() const; +void setAIOIntOnCooldown(bool cd); +``` + +**World.h** + +```cpp +// AIOAddon container constructor +// Permission 195 will load the addon on every player +World::AIOAddon::AIOAddon(const std::string &addonName, const std::string &addonFile, uint32 permission = 195); + +// AIO prefix configured in worldserver.conf +std::string World::GetAIOPrefix() cons; + +// AIO client LUA files path configured in worldserver.conf +std::string World::GetAIOClientScriptPath() const; + +// Forces reload on all player AIO addons +// Syncs player AIO addons with server +void World::ForceReloadPlayerAddons(uint32 permission = 195); + +// Forces reset on all player AIO addons +// Player AIO addons and addon data is deleted and downloaded again +void World::ForceResetPlayerAddons(uint32 permission = 195); + +// Sends an AIO message to all players +// See: class AIOMsg +void World::AIOMessageAll(AIOMsg &msg, uint32 permission = 195); + +// Sends a simple string message to all players + +// AIO can only understand smallfolk LuaVal::dumps() format +// Handler functions are called by creating a table as below +// { +// {n, ScriptName, HandlerName(optional), Arg1..N(optional) }, +// {n, AnotherScriptName, AnotherHandlerName(optional), Arg1..N(optional) } +// } +// Where n is number of arguments including handler name as a argument +void World::SendAllSimpleAIOMessage(const std::string &message, uint32 permission = 195); + +// Reloads client side AIO addon files and force reloads +// all player AIO addons +// Returns true if successful, false if an error occurred +bool World::ReloadAddons(); + +// Adds a WoW AIO addon file to the list of addons with a unique +// addon name to send on AIO client initialization. +// Returns true if addon was added, false if addon name is already taken +// +// It is required to call World::ForceReloadPlayerAddons() +// if addons are added after server is fully initialized +// for online players to load the added addons. +bool World::AddAddon(const AIOAddon &addon); + +// Removes an addon from addon list and force reloads affected players +// Returns permission id if an addon was removed, 0 if addon not found +// +// It is required to call World::ForceReloadPlayerAddons() +// if addons are added after server is fully initialized +// for online players to load the added addons. +uint32 World::RemoveAddon(const std::string &addonName); +``` + +## CAIO game commands + ++ .caio version ++ .caio addaddon $addonName [$permission] "$addonFile" ++ .caio removeaddon $addonName ++ .caio reloadaddons ++ .caio forcereload $playerName ++ .caio forcereset $playerName ++ .caio forcereloadall [$permission] ++ .caio forceresetall [$permission] ++ .caio send $playerName "Message" ++ .caio sendall [$permission] "Message" + +Note: By default every player has permission 195. Permission 195 will be used if not specified. + +## Reporting issues and submitting fixes + +Issues can be reported via the [Github issue tracker](https://github.com/SaiFi0102/TrinityCore/issues). Fixes can be submitted as pull requests on the Github repository. + +## Authors, Contributors & Thanks + ++ Saif + + CAIO ++ Rochet2 + + [AIO](https://github.com/Rochet2/AIO) + + [smallfolk_cpp](https://github.com/Rochet2/smallfolk_cpp) to handle and transmit Lua data in C++ diff --git a/dep/smallfolk_cpp/smallfolk_cpp b/dep/smallfolk_cpp/smallfolk_cpp new file mode 160000 index 0000000000000..91be8769ff224 --- /dev/null +++ b/dep/smallfolk_cpp/smallfolk_cpp @@ -0,0 +1 @@ +Subproject commit 91be8769ff2248ace28080bd5b0121683aa614ab diff --git a/sql/CAIO/Auth.sql b/sql/CAIO/Auth.sql new file mode 100644 index 0000000000000..8cf9785f911db --- /dev/null +++ b/sql/CAIO/Auth.sql @@ -0,0 +1,2 @@ +REPLACE INTO `rbac_permissions` (`id`, `name`) VALUES (5000, 'Command: caio'); +REPLACE INTO `rbac_linked_permissions` (`id`, `linkedId`) VALUES (192, 5000); \ No newline at end of file diff --git a/sql/CAIO/World.sql b/sql/CAIO/World.sql new file mode 100644 index 0000000000000..38e57572c297b --- /dev/null +++ b/sql/CAIO/World.sql @@ -0,0 +1,24 @@ +-- CAIO command result strings +REPLACE INTO `trinity_string` (`entry`, `content_default`, `content_loc1`, `content_loc2`, `content_loc3`, `content_loc4`, `content_loc5`, `content_loc6`, `content_loc7`, `content_loc8`) +VALUES (60002, 'Force reload message sent to %s', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +REPLACE INTO `trinity_string` (`entry`, `content_default`, `content_loc1`, `content_loc2`, `content_loc3`, `content_loc4`, `content_loc5`, `content_loc6`, `content_loc7`, `content_loc8`) +VALUES (60003, 'Force reset message sent to %s', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +REPLACE INTO `trinity_string` (`entry`, `content_default`, `content_loc1`, `content_loc2`, `content_loc3`, `content_loc4`, `content_loc5`, `content_loc6`, `content_loc7`, `content_loc8`) +VALUES (60004, 'There was a problem reloading client addons. Force reload was not sent.', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +REPLACE INTO `trinity_string` (`entry`, `content_default`, `content_loc1`, `content_loc2`, `content_loc3`, `content_loc4`, `content_loc5`, `content_loc6`, `content_loc7`, `content_loc8`) +VALUES (60005, 'Addon with name \'%s\' already exists or file not found.', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +REPLACE INTO `trinity_string` (`entry`, `content_default`, `content_loc1`, `content_loc2`, `content_loc3`, `content_loc4`, `content_loc5`, `content_loc6`, `content_loc7`, `content_loc8`) +VALUES (60006, 'Addon with name \'%s\' not found.', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + +-- CAIO commands +REPLACE INTO `command` (`name`, `permission`, `help`) VALUES ('caio', 5000, 'Syntax: .caio $subcommand\nType .caio to see the list of possible subcommands or .help caio $subcommand to see info on subcommands'); +REPLACE INTO `command` (`name`, `permission`, `help`) VALUES ('caio version', 5000, 'Syntax: .caio version\nShows the AIO version'); +REPLACE INTO `command` (`name`, `permission`, `help`) VALUES ('caio addaddon', 5000, 'Syntax: .caio addaddon $addonName [$permission] "$addonFile"\nAdds an addon to addon list for players with $permission and force reloads all affected player addons'); +REPLACE INTO `command` (`name`, `permission`, `help`) VALUES ('caio removeaddon', 5000, 'Syntax: .caio removeaddon $addonName\nRemoves an addon from addon list and force reloads all affected player addons'); +REPLACE INTO `command` (`name`, `permission`, `help`) VALUES ('caio reloadaddons', 5000, 'Syntax: .caio reloadaddons\nReloads all client addons loaded on the server and forces reload on all players.'); +REPLACE INTO `command` (`name`, `permission`, `help`) VALUES ('caio forcereload', 5000, 'Syntax: .caio forcereload $playerName\nForce reloads player\'s addons. Player addons are synced with server.'); +REPLACE INTO `command` (`name`, `permission`, `help`) VALUES ('caio forcereset', 5000, 'Syntax: .caio forcereset $playerName\nForce resets player\'s addons. Player addons are deleted and downloaded again.'); +REPLACE INTO `command` (`name`, `permission`, `help`) VALUES ('caio forcereloadall', 5000, 'Syntax: .caio forcereloadall [$permission]\nForce reloads players of $permission and above. Affected players\' addons are synced with the server.'); +REPLACE INTO `command` (`name`, `permission`, `help`) VALUES ('caio forceresetall', 5000, 'Syntax: .caio forceresetall [$permission]\nForce resets players of $permission and above. Affected players\' addons are deleted and downloaded again.'); +REPLACE INTO `command` (`name`, `permission`, `help`) VALUES ('caio send', 5000, 'Syntax: .caio send $playerName "Message"\nSends an addon message to player'); +REPLACE INTO `command` (`name`, `permission`, `help`) VALUES ('caio sendall', 5000, 'Syntax: .caio sendall [$permission] "Message"\nSends an addon message to all players of $permission and above'); diff --git a/src/common/Logging/Log.h b/src/common/Logging/Log.h index a15bb4ad485b2..e7a47e6d4832f 100644 --- a/src/common/Logging/Log.h +++ b/src/common/Logging/Log.h @@ -78,6 +78,21 @@ class Log write(std::move(msg)); } + template + void outAIOMessage(uint32 account, LogLevel const level, Format&& fmt, Args&&... args) + { + if (!ShouldLog("AIO", level)) + return; + + std::unique_ptr msg = + Trinity::make_unique(level, "AIO", + Trinity::StringFormat(std::forward(fmt), std::forward(args)...)); + + msg->param1 = account ? std::to_string(account) : "World"; + + write(std::move(msg)); + } + void outCharDump(char const* str, uint32 account_id, uint64 guid, char const* name); void SetRealmId(uint32 id); diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h index bf28d76ab9c95..57ff91f93a529 100644 --- a/src/server/game/Accounts/RBAC.h +++ b/src/server/game/Accounts/RBAC.h @@ -695,6 +695,8 @@ enum RBACPermissions RBAC_PERM_COMMAND_PVPSTATS = 797, RBAC_PERM_COMMAND_MODIFY_XP = 798, + RBAC_PERM_COMMAND_CAIO = 5000, + // custom permissions 1000+ RBAC_PERM_MAX }; diff --git a/src/server/game/CMakeLists.txt b/src/server/game/CMakeLists.txt index ccc69b3ef68bd..d01a743b62480 100644 --- a/src/server/game/CMakeLists.txt +++ b/src/server/game/CMakeLists.txt @@ -112,6 +112,7 @@ include_directories( ${CMAKE_SOURCE_DIR}/dep/SFMT ${CMAKE_SOURCE_DIR}/dep/cppformat ${CMAKE_SOURCE_DIR}/dep/zlib + ${CMAKE_SOURCE_DIR}/dep/smallfolk_cpp ${CMAKE_SOURCE_DIR}/src/common ${CMAKE_SOURCE_DIR}/src/common/Collision ${CMAKE_SOURCE_DIR}/src/common/Collision/Management diff --git a/src/server/game/Chat/AIOMsg.cpp b/src/server/game/Chat/AIOMsg.cpp new file mode 100644 index 0000000000000..0c68d35e37e03 --- /dev/null +++ b/src/server/game/Chat/AIOMsg.cpp @@ -0,0 +1,99 @@ +#include "AIOMsg.h" +#include "Player.h" + +AIOMsg::AIOMsg() + : _val(TTABLE) +{ } + +AIOMsg &AIOMsg::Add(const std::string &scriptName, const std::string &handlerName, const LuaVal &a1, const LuaVal &a2, const LuaVal &a3, const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) +{ + LuaVal block(TTABLE); + uint32 nArgs = 1; + + block.insert(nArgs); + block.insert(scriptName); + block.insert(handlerName); + + if(!a1.isnil()) + { + block.insert(a1); + block.set(1, ++nArgs); + } + if(!a2.isnil()) + { + block.insert(a2); + block.set(1, ++nArgs); + } + if(!a3.isnil()) + { + block.insert(a3); + block.set(1, ++nArgs); + } + if(!a4.isnil()) + { + block.insert(a4); + block.set(1, ++nArgs); + } + if(!a5.isnil()) + { + block.insert(a5); + block.set(1, ++nArgs); + } + if(!a6.isnil()) + { + block.insert(a6); + block.set(1, ++nArgs); + } + + _val.insert(block); + return *this; +} + +AIOMsg &AIOMsg::AppendLast(const LuaVal &a1, const LuaVal &a2, const LuaVal &a3, const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) +{ + unsigned int lastBlock = _val.len(); + if(!lastBlock) + { + return *this; + } + + LuaVal &block = _val[lastBlock]; + LuaVal nArgsVal = block.get(1); + if(!nArgsVal.isnumber()) + { + return *this; + } + + uint32 nArgs = (uint32)nArgsVal.num(); + if(!a1.isnil()) + { + block.insert(a1); + block.set(1, ++nArgs); + } + if(!a2.isnil()) + { + block.insert(a2); + block.set(1, ++nArgs); + } + if(!a3.isnil()) + { + block.insert(a3); + block.set(1, ++nArgs); + } + if(!a4.isnil()) + { + block.insert(a4); + block.set(1, ++nArgs); + } + if(!a5.isnil()) + { + block.insert(a5); + block.set(1, ++nArgs); + } + if(!a6.isnil()) + { + block.insert(a6); + block.set(1, ++nArgs); + } + return *this; +} diff --git a/src/server/game/Chat/AIOMsg.h b/src/server/game/Chat/AIOMsg.h new file mode 100644 index 0000000000000..daea69f90ce9c --- /dev/null +++ b/src/server/game/Chat/AIOMsg.h @@ -0,0 +1,42 @@ +#ifndef AIO_MESSAGE_H +#define AIO_MESSAGE_H + +#include "smallfolk_cpp/smallfolk.h" + +class Player; + +class AIOMsg +{ + public: + //Creates an empty AIOMsg + AIOMsg(); + + //Creates a AIO message and adds one block + AIOMsg(const std::string &scriptName, const std::string &handlerName, + const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()) + : _val(TTABLE) + { + Add(scriptName, handlerName, a1, a2, a3, a4, a5); + } + + //Adds another block + //Another block will call another handler in one message + AIOMsg &Add(const std::string &scriptName, const std::string &handlerName, + const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + + //Appends the last block + //You can add additional arguments to the last block + AIOMsg &AppendLast(const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + + //Returns smallfolk dump of the AIO message + std::string dumps() const { return _val.dumps(); } + + private: + LuaVal _val; + friend class Player; +}; + +#endif diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 362c40a09821a..1ffb01ea91874 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -79,6 +79,7 @@ #include "WorldPacket.h" #include "WorldSession.h" #include "GameObjectAI.h" +#include "AIOMsg.h" #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) @@ -904,6 +905,11 @@ Player::Player(WorldSession* session): Unit(true) _activeCheats = CHEAT_NONE; m_achievementMgr = new AchievementMgr(this); m_reputationMgr = new ReputationMgr(this); + + m_aioInitialized = false; + m_aioInitCd = false; + m_aioInitTimer = 0; + m_messageIdIndex = 1; } Player::~Player() @@ -1898,6 +1904,17 @@ void Player::Update(uint32 p_time) //because we don't want player's ghost teleported from graveyard if (IsHasDelayedTeleport() && IsAlive()) TeleportTo(m_teleport_dest, m_teleport_options); + + //AIO Init cooldown + if(m_aioInitCd) + { + m_aioInitTimer += p_time; + if(m_aioInitTimer >= 5000) + { + m_aioInitCd = false; + m_aioInitTimer = 0; + } + } } void Player::setDeathState(DeathState s) @@ -20543,6 +20560,93 @@ void Player::Whisper(std::string const& text, Language language, Player* target, ChatHandler(GetSession()).PSendSysMessage(LANG_PLAYER_DND, target->GetName().c_str(), target->autoReplyMsg.c_str()); } +void Player::AIOMessage(AIOMsg &msg) +{ + SendSimpleAIOMessage(msg.dumps()); +} + +void Player::AIOHandle(const std::string &scriptName, const std::string &handlerName, const LuaVal &a1, const LuaVal &a2, const LuaVal &a3,const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) +{ + AIOMsg msg(scriptName, handlerName, a1, a2, a3, a4, a5, a6); + SendSimpleAIOMessage(msg.dumps()); +} + +void Player::SendSimpleAIOMessage(const std::string &message) +{ + if(message.empty()) + { + return; + } + std::string aioPrefix = sWorld->GetAIOPrefix(); + size_t shortMsgLen = message.size() + aioPrefix.size() + 4; //+4 for S \t and 2 byte for message id + + //If its a short message + if(shortMsgLen <= 2600) + { + std::string fullmsg = "S" + aioPrefix + "\t\x1\x1" + message; + WorldPacket data(SMSG_MESSAGECHAT, fullmsg.size() + 30); + data << uint8(CHAT_MSG_WHISPER); + data << int32(LANG_ADDON); + data << uint64(GetGUID()); + data << uint32(0); + data << uint64(GetGUID()); + data << uint32(fullmsg.size() + 1); + data << fullmsg; + data << uint8(0); + GetSession()->SendPacket(&data); + return; + } + + //If its a long message + uint16 parts = std::ceilf(float(shortMsgLen + 4) / 2600); + + //parts to string + uint16 high = std::floorf((float)parts / 254); + std::string partsStr(1, high + 1); + partsStr += parts - high * 254 + 1; + + //messageid to string + high = std::floorf((float)m_messageIdIndex / 254); + std::string messageIdStr(1, high + 1); + messageIdStr += m_messageIdIndex - high * 254 + 1; + + //Increase or renew messageIdIndex + if(m_messageIdIndex >= 64769) //2^16 - 767 + { + m_messageIdIndex = 1; + } + else + { + ++m_messageIdIndex; + } + + //Send in parts + size_t cursor = 0; + for(uint16 partId = 1; partId <= parts; ++partId) + { + //partid to string + high = std::floorf((float)partId / 254); + std::string partIdStr(1, high + 1); + partIdStr += partId - high * 254 + 1; + + //send + std::string fullmsg = "S" + aioPrefix + "\t" + messageIdStr + partsStr + partIdStr; + fullmsg += message.substr(cursor, 2600); + WorldPacket data(SMSG_MESSAGECHAT, fullmsg.size() + 30); + data << uint8(CHAT_MSG_WHISPER); + data << int32(LANG_ADDON); + data << uint64(GetGUID()); + data << uint32(0); + data << uint64(GetGUID()); + data << uint32(fullmsg.size() + 1); + data << fullmsg; + data << uint8(0); + GetSession()->SendPacket(&data); + + cursor += 2600; + } +} + Item* Player::GetMItem(uint32 id) { ItemMap::const_iterator itr = mMitems.find(id); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 42fa6dc2ea313..da6ff12e5e9d1 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -29,6 +29,8 @@ #include "SpellMgr.h" #include "Unit.h" +#include "smallfolk_cpp/smallfolk.h" + #include #include #include @@ -52,6 +54,7 @@ class PlayerMenu; class PlayerSocial; class SpellCastTargets; class UpdateMask; +class AIOMsg; struct CharacterCustomizeInfo; @@ -1214,6 +1217,40 @@ class Player : public Unit, public GridObject /// Handles whispers from Addons and players based on sender, receiver's guid and language. void Whisper(std::string const& text, Language language, Player* receiver, bool = false) override; + //Returns whether AIO client has been initialized + bool AIOInitialized() const { return m_aioInitialized; } + + // Sends an AIO message to the player + // See: class AIOMsg + void AIOMessage(AIOMsg &msg); + + // Triggers an AIO script handler on the client + // To trigger multiple handlers in one message or to send more + // arguments use Player::AIOMessage + void AIOHandle(const std::string &scriptName, const std::string &handlerName, + const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + + // AIO can only understand smallfolk LuaVal::dumps() format + // Handler functions are called by creating a table as below + // { + // {n, ScriptName, HandlerName(optional), Arg1..N(optional) }, + // {n, AnotherScriptName, AnotherHandlerName(optional), Arg1..N(optional) } + // } + // Where n is number of arguments including handler name as an argument + void SendSimpleAIOMessage(const std::string &message); + + // Forces reload on the player AIO addons + // Syncs player AIO addons with server + void ForceReloadAddons() { AIOHandle("AIO", "ForceReload"); } + + // Force reset on the player AIO addons + // Player AIO addons and addon data is deleted and downloaded again + void ForceResetAddons() { AIOHandle("AIO", "ForceReset"); } + + bool isAIOInitOnCooldown() const { return m_aioInitCd; } + void setAIOIntOnCooldown(bool cd) { m_aioInitCd = cd; m_aioInitTimer = 0; } + /*********************************************************/ /*** STORAGE SYSTEM ***/ /*********************************************************/ @@ -2637,6 +2674,13 @@ class Player : public Unit, public GridObject uint32 _pendingBindTimer; uint32 _activeCheats; + + bool m_aioInitialized; + bool m_aioInitCd; + uint32 m_aioInitTimer; + uint16 m_messageIdIndex; + + friend class AIOHandlers; }; void AddItemsSetItem(Player* player, Item* item); diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index 2acbaba1f671e..1cc9a225c75f5 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -268,6 +268,109 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) } Player* receiver = ObjectAccessor::FindConnectedPlayerByName(to); + + //AIO + size_t delimPos; + if(lang == LANG_ADDON && receiver) + { + std::string prefix; + bool isAIOMessage = false; + delimPos = msg.find('\t'); + + if(delimPos != std::string::npos) + { + prefix = msg.substr(0, delimPos); + if(prefix == "C" + sWorld->GetAIOPrefix()) + { + isAIOMessage = true; + } + } + + if(isAIOMessage) + { + if(receiver != sender) + { + return; + } + + //Must have meta data + uint16 messageId; + if((msg.size() - delimPos - 1) >= 2) + { + messageId = (msg[delimPos + 1] - 1) * 254 + msg[delimPos + 2] - 1; + + //If its a short message + if(messageId == 0) //messageId = 0 + { + sScriptMgr->OnAddonMessage(sender, msg.substr(delimPos + 3)); + break; + } + } + //Its a long message + if((msg.size() - delimPos - 1) >= 6) + { + uint32 parts = (msg[delimPos + 3] - 1) * 254 + msg[delimPos + 4] - 1; + if(parts < 2) + { + sLog->outAIOMessage(sender->GetGUIDLow(), LOG_LEVEL_ERROR, "HandleAddonMessagechatOpcode: Received AIO addon message with number of parts: %u (< 2). Message Id: %u, Sender: %s", parts, messageId, sender->GetName().c_str()); + return; + } + uint32 maxparts = sWorld->getIntConfig(CONFIG_AIO_MAXPARTS); + if(parts > maxparts) + { + sLog->outAIOMessage(sender->GetGUIDLow(), LOG_LEVEL_ERROR, "HandleAddonMessagechatOpcode: Received AIO addon message with too many parts: %u (> %u). Message Id: %u, Sender: %s", parts, maxparts, messageId, sender->GetName().c_str()); + return; + } + + uint32 partId = (msg[delimPos + 5] - 1) * 254 + msg[delimPos + 6] - 1; + + //Check if message exists + AddonMessageBufferMap::iterator messagePartsItr = _addonMessageBuffer.find(messageId); + if(messagePartsItr == _addonMessageBuffer.end()) + { + messagePartsItr = _addonMessageBuffer.insert(std::make_pair(messageId, LongMessageBufferInfo())).first; + } + else + { + //If message already exist and has different number of parts remove it + if(parts != messagePartsItr->second.Parts) + { + messagePartsItr->second = LongMessageBufferInfo(); + } + } + + messagePartsItr->second.Parts = parts; + messagePartsItr->second.Map[partId] = msg.substr(delimPos + 7); + + //If there are enough parts + if(messagePartsItr->second.Map.size() >= messagePartsItr->second.Parts) + { + //Assemble the parts + std::string actualAIOMessage; + for(AddonPartStringMap::const_iterator itr = messagePartsItr->second.Map.begin(); + itr != messagePartsItr->second.Map.end(); + ++itr) + { + actualAIOMessage += itr->second; + } + + sScriptMgr->OnAddonMessage(sender, actualAIOMessage); + _addonMessageBuffer.erase(messagePartsItr); + break; + } + else //Or else wait for other packets to arrive + { + break; + } + } + else + { + sLog->outAIOMessage(sender->GetGUIDLow(), LOG_LEVEL_ERROR, "HandleAddonMessagechatOpcode: Received AIO long addon message without meta data, Sender: %s", sender->GetName().c_str()); + break; + } + } + } + if (!receiver || (lang != LANG_ADDON && !receiver->isAcceptWhispers() && receiver->GetSession()->HasPermission(rbac::RBAC_PERM_CAN_FILTER_WHISPERS) && !receiver->IsInWhisperWhiteList(sender->GetGUID()))) { SendPlayerNotFoundNotice(to); diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 63390ad61da47..9132aceaa1fad 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1201,6 +1201,12 @@ enum TrinityStrings LANG_BAN_ACCOUNT_YOUPERMBANNEDMESSAGE_WORLD = 11007, LANG_NPCINFO_INHABIT_TYPE = 11008, - LANG_NPCINFO_FLAGS_EXTRA = 11009 + LANG_NPCINFO_FLAGS_EXTRA = 11009, + + LANG_CAIO_FORCERELOAD_SENT = 60002, + LANG_CAIO_FORCERESET_SENT = 60003, + LANG_CAIO_RELOADADDONS_ERROR = 60004, + LANG_CAIO_ADDADDON_ERROR = 60005, + LANG_CAIO_REMOVEADDON_ERROR = 60006, }; #endif diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp index 7c4b11769b51e..399ae12f00f33 100644 --- a/src/server/game/Scripting/ScriptLoader.cpp +++ b/src/server/game/Scripting/ScriptLoader.cpp @@ -77,6 +77,7 @@ void AddSC_tele_commandscript(); void AddSC_ticket_commandscript(); void AddSC_titles_commandscript(); void AddSC_wp_commandscript(); +void AddSC_caio_commandscript(); #ifdef SCRIPTS //world @@ -706,6 +707,7 @@ void AddScripts() AddBattlegroundScripts(); AddOutdoorPvPScripts(); AddCustomScripts(); + AddAIOScripts(); #endif } @@ -769,6 +771,7 @@ void AddCommandScripts() AddSC_ticket_commandscript(); AddSC_titles_commandscript(); AddSC_wp_commandscript(); + AddSC_caio_commandscript(); } void AddWorldScripts() @@ -1421,7 +1424,20 @@ void AddBattlegroundScripts() void AddCustomScripts() { #ifdef SCRIPTS - /* This is where custom scripts should be added. */ + /* This is where custom scripts should be added. */ + +#endif +} + +#ifdef SCRIPTS +/* This is where AIO scripts' loading functions should be declared. */ + +#endif + +void AddAIOScripts() +{ +#ifdef SCRIPTS + /* This is where AIO scripts should be added. */ #endif } diff --git a/src/server/game/Scripting/ScriptLoader.h b/src/server/game/Scripting/ScriptLoader.h index 652ef994c7a78..8893ffdb6fddc 100644 --- a/src/server/game/Scripting/ScriptLoader.h +++ b/src/server/game/Scripting/ScriptLoader.h @@ -31,5 +31,6 @@ void AddPetScripts(); void AddBattlegroundScripts(); void AddOutdoorPvPScripts(); void AddCustomScripts(); +void AddAIOScripts(); #endif diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index aed829a7b574f..cf50852120676 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -33,6 +33,7 @@ #include "Player.h" #include "WorldPacket.h" #include "WorldSession.h" +#include "smallfolk_cpp/smallfolk.h" // namespace // { @@ -182,7 +183,7 @@ struct TSpellSummary uint8 Effects; // set of enum SelectEffect } *SpellSummary; -ScriptMgr::ScriptMgr() : _scriptCount(0), _scheduledScripts(0) +ScriptMgr::ScriptMgr() : _scriptCount(0), _scheduledScripts(0), _aioHandlers(0) { } @@ -197,6 +198,7 @@ void ScriptMgr::Initialize() TC_LOG_INFO("server.loading", "Loading C++ scripts"); FillSpellSummary(); + _aioHandlers = new AIOHandlers(); AddScripts(); #ifdef SCRIPTS @@ -245,6 +247,7 @@ void ScriptMgr::Unload() SCR_CLEAR(GuildScript); SCR_CLEAR(GroupScript); SCR_CLEAR(UnitScript); + SCR_CLEAR(AIOScript); #undef SCR_CLEAR @@ -1625,6 +1628,240 @@ GroupScript::GroupScript(const char* name) ScriptRegistry::AddScript(this); } +AIOScript::AIOScriptByNameMap AIOScript::_scriptByNameMap = AIOScript::AIOScriptByNameMap(); + +void ScriptMgr::OnAddonMessage(Player *sender, const std::string &message) +{ + if(!sender) + return; + + LuaVal Doc = LuaVal::loads(message.c_str()); + if(!Doc.istable()) //Unable to parse or incorrect format + { + return; + } + + LuaVal &Args = Doc[1]; + if(!Args.istable()) + { + return; + } + + LuaVal &scriptNameVal = Args[2]; + LuaVal &handlerNameVal = Args[3]; + if(!Args[1].isnumber() || !scriptNameVal.isstring() || !handlerNameVal.isstring()) + { + return; + } + + std::string scriptName = scriptNameVal.str(); + std::string handlerName = handlerNameVal.str(); + + AIOScript *aioScript = _aioHandlers->GetScript(scriptName); + if(aioScript) + { + aioScript->OnHandle(sender, scriptName, handlerName, Args); + } +} + +AIOScript::AIOScript(const char *name) + : ScriptObject(name) +{ + if(AIOScript::_scriptByNameMap.find("name") != AIOScript::_scriptByNameMap.end()) + { + throw std::runtime_error(std::string("AIO scriptName '") + name + "' already exist. Use another name."); + } + ScriptRegistry::AddScript(this); + AIOScript::_scriptByNameMap[name] = this; +} + +AIOScript::~AIOScript() +{ + AIOScript::_scriptByNameMap.erase(GetName()); +} + +void AIOScript::AddHandler(const char *handlerName, HandlerFunc function) +{ + _handlerMap[handlerName] = function; +} + +void AIOScript::AddInitArgs(const std::string &scriptName, const std::string &handlerName, ArgFunc a1, ArgFunc a2, ArgFunc a3, ArgFunc a4, ArgFunc a5, ArgFunc a6) +{ + AIOHandlers *handler = sScriptMgr->_aioHandlers; + if(!handler) + { + return; + } + + //Look for hook + std::list *list = 0; + for(AIOHandlers::HookListType::iterator itr = handler->_initHookList.begin(); + itr != handler->_initHookList.end(); + ++itr) + { + if(itr->scriptName == scriptName && itr->handlerName == handlerName) + { + list = &itr->argsList; + break; + } + } + + //Add hook + if(!list) + { + handler->_initHookList.push_back(AIOHandlers::InitHookInfo(scriptName, handlerName)); + list = &handler->_initHookList.back().argsList; + } + + //Add args + if(a1) + { + list->push_back(a1); + } + if(a2) + { + list->push_back(a2); + } + if(a3) + { + list->push_back(a3); + } + if(a4) + { + list->push_back(a4); + } + if(a5) + { + list->push_back(a5); + } + if(a6) + { + list->push_back(a6); + } +} + +bool AIOScript::AddAddon(const World::AIOAddon &addon) +{ + return sWorld->AddAddon(addon); +} + +template<> +AIOScript *AIOScript::GetScript(const std::string &scriptName) +{ + AIOScriptByNameMap::const_iterator itr = AIOScript::_scriptByNameMap.find(scriptName); + if(itr == AIOScript::_scriptByNameMap.end()) + { + return 0; + } + return itr->second; +} + +template +ScriptClass *AIOScript::GetScript(const std::string &scriptName) +{ + AIOScriptByNameMap::const_iterator itr = AIOScript::_scriptByNameMap.find(scriptName); + if(itr == AIOScript::_scriptByNameMap.end()) + { + return 0; + } + return dynamic_cast(itr->second); +} + +void AIOScript::OnHandle(Player *sender, const std::string &scriptName, const std::string &handlerName, const LuaVal &args) +{ + if(scriptName != GetName()) + return; + + HandlerMapType::const_iterator itr = _handlerMap.find(handlerName); + if(itr != _handlerMap.end()) + { + itr->second(sender, args); + } +} + +AIOHandlers::AIOHandlers() + : AIOScript("AIO") +{ + AddHandler("Init", std::bind(&AIOHandlers::HandleInit, this, std::placeholders::_1, std::placeholders::_2)); + AddHandler("Error", std::bind(&AIOHandlers::HandleError, this, std::placeholders::_1, std::placeholders::_2)); +} + +void AIOHandlers::HandleInit(Player *sender, const LuaVal &args) +{ + //Init hasn't cooled down + if(sender->isAIOInitOnCooldown()) + { + return; + } + + sender->setAIOIntOnCooldown(true); + LuaVal &versionVal = args[4]; + LuaVal &clientDataVal = args[5]; + if(!versionVal.isnumber() || !clientDataVal.istable()) + { + sLog->outAIOMessage(sender->GetGUIDLow(), LOG_LEVEL_ERROR, "AIOHandlers::HandleInit: Invalid version value or clientData value. Sender: %s, Args: %s", sender->GetName().c_str(), args.dumps().c_str()); + return; + } + + if(versionVal.num() != AIO_VERSION) + { + sender->AIOHandle("AIO", "Init", AIO_VERSION); + return; + } + + LuaVal addonTable(TTABLE); + LuaVal cacheTable(TTABLE); + uint32 nAddons = sWorld->PrepareClientAddons(clientDataVal, addonTable, cacheTable, sender); + + LuaVal argsToSend(TTABLE); + + uint32 blockIndex = 1; + for(HookListType::const_iterator itr = _initHookList.begin(); + itr != _initHookList.end(); + ++itr) + { + uint32 index = 0; + LuaVal HookBlock(TTABLE); + + HookBlock[++index] = (uint32)itr->argsList.size() + 1; + HookBlock[++index] = itr->scriptName; + HookBlock[++index] = itr->handlerName; + for(std::list::const_iterator it = itr->argsList.begin(); + it != itr->argsList.end(); + ++it) + { + HookBlock[++index] = (*it)(sender); + } + + argsToSend[++blockIndex] = HookBlock; + } + + LuaVal AIOInitBlock(TTABLE); + AIOInitBlock[1] = 5; + AIOInitBlock[2] = "AIO"; + AIOInitBlock[3] = "Init"; + AIOInitBlock[4] = AIO_VERSION; + AIOInitBlock[5] = nAddons; + AIOInitBlock[6] = addonTable; + AIOInitBlock[7] = cacheTable; + + argsToSend[1] = AIOInitBlock; + sender->SendSimpleAIOMessage(argsToSend.dumps()); + + sender->m_aioInitialized = true; +} + +void AIOHandlers::HandleError(Player *sender, const LuaVal &args) +{ + LuaVal &msgVal = args[4]; + if(!msgVal.isstring()) + { + return; + } + sLog->outAIOMessage(sender->GetGUIDLow(), LOG_LEVEL_ERROR, "Player %s sent client addon error message: %s", sender->GetName().c_str(), msgVal.str().c_str()); +} + + // Instantiate static members of ScriptRegistry. template std::map ScriptRegistry::ScriptPointerList; template uint32 ScriptRegistry::_scriptIdCounter = 0; @@ -1656,6 +1893,7 @@ template class ScriptRegistry; template class ScriptRegistry; template class ScriptRegistry; template class ScriptRegistry; +template class ScriptRegistry; // Undefine utility macros. #undef GET_SCRIPT_RET diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index a226f1b7ed2b0..e59f11024fe26 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -870,6 +870,173 @@ class GroupScript : public ScriptObject virtual void OnDisband(Group* /*group*/) { } }; +class LuaVal; + +// ##################### Abstract AIO handler script ##################### +// Inherit AIOScript to make an AIO handler script +// +// See smallfork_cpp at https://github.com/Rochet2/smallfolk_cpp for +// reference on how to use LuaVal +// +// Example of use: +// +// class ExampleAIOScript : public AIOScript +// { +// public: +// ExampleAIOScript() +// : AIOScript("ExampleScriptName") +// { +// using namespace std::placeholders; +// +// // Loads addon files to addons list and sends them on AIO client initialization +// // Looks for the file in path config AIO.ClientScriptPath +// AddAddon(World::AIOAddon("ExampleAddon", "example_addon.lua")); +// +// // You can also add addons to be sent to players with specific permission +// AddAddon(World::AIOAddon("AnotherAddon", "example_addon.lua", 192)); //192 refers to admin RBAC permission +// +// // Handler function signature: void HandlerFunction(Player *sender, const LuaVal &args) +// AddHandler("Print", std::bind(&ExampleAIOScript::HandlePrint, this, _1, _2)); +// AddHandler("Save", std::bind(&ExampleAIOScript::HandleSave, this, _1, _2)); +// +// // Initialization handler and arguments +// AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg, this, _1), std::bind(&ExampleAIOScript::InitArg, this, _1)); +// //Adds additional argument to send to handler +// AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg2, this, _1)); +// AddInitArgs("AnotherScript", "InitB"); //Arguments are not necessary +// } +// +// void HandlePrint(Player *sender, const LuaVal &args) +// { +// //LuaVal args in a handler function is always a table +// //Handler arguments index starts from 4 +// LuaVal &InputVal = args[4]; +// LuaVal &SliderVal = args[5]; +// +// //MUST check if the value type is valid or else smallfolk_cpp will +// //throw on obtaining that type +// if(!InputVal.isstring() || !SliderVal.isnumber()) +// { +// return; +// } +// +// sender->GetSession()->SendNotification("HandlePrint -> Stored String: %s, Input: %s, Slider Value: %f", +// storedString.c_str(), InputVal.str().c_str(), SliderVal.num()); +// } +// +// void HandleSave(Player *sender, const LuaVal &args) +// { +// //LuaVal args in a handler function is always a table +// //Handler arguments index starts from 4 +// LuaVal &SaveVal = args.get[4]; +// +// //MUST check if the value type is valid +// if(!SaveVal.isstring()) +// { +// return; +// } +// +// storedString = SaveVal.str(); +// sender->GetSession()->SendNotification("Saved"); +// } +// +// LuaVal InitArg(Player *sender) +// { +// LuaVal arg = LuaVal(TTABLE); +// arg.set("key", 12.3); +// arg["key2"] = false; +// +// return arg; +// } +// +// LuaVal InitArg2(Player *sender) +// { +// return "LuaVal will implicitly create a string LuaVal for this arg"; +// } +// +// private: +// std::string storedString; +// }; +class AIOScript : public ScriptObject +{ + public: + ~AIOScript(); + bool IsDatabaseBound() const { return false; } + + typedef std::function HandlerFunc; + typedef std::function ArgFunc; + + protected: + // Registers an AIO Handler script of scriptName + AIOScript(const char *scriptName); + + // Registers a handler function to call when handling + // handlerName of this script. + void AddHandler(const char *handlerName, HandlerFunc function); + + // Adds a client side handler to call and adds arguments + // to sends with it for AIO client initialization. + // + // You can add additional arguments to the handler by + // calling this function again + void AddInitArgs(const std::string &scriptName, const std::string &handlerName, + ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), + ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); + + // Adds a WoW addon file to the list of addons with a unique + // addon name to send on AIO client initialization. + // Returns true if addon was added, false if addon name is taken. + // + // It is required to call World::ForceReloadPlayerAddons() + // if addons are added after server is fully initialized + // for online players to load the added addons. + bool AddAddon(const World::AIOAddon &addon); + + // Returns pointer to an AIO script by its name and typename. + // Returns null if scriptName doesn't exist or typename was incorrect. + template + ScriptClass *GetScript(const std::string &scriptName); + + template<> + AIOScript *GetScript(const std::string &scriptName); + + private: + void OnHandle(Player *sender, const std::string &scriptName, const std::string &handlerName, const LuaVal &args); + + typedef std::unordered_map HandlerMapType; + HandlerMapType _handlerMap; + + typedef std::unordered_map AIOScriptByNameMap; + static AIOScriptByNameMap _scriptByNameMap; + + friend class ScriptMgr; +}; + +class AIOHandlers : public AIOScript +{ + private: + AIOHandlers(); + void HandleInit(Player *sender, const LuaVal &args); + void HandleError(Player *sender, const LuaVal &args); + + struct InitHookInfo + { + std::string scriptName; + std::string handlerName; + std::list argsList; + + InitHookInfo(const std::string &scriptName, const std::string &handlerName) + : scriptName(scriptName), handlerName(handlerName) + { } + }; + + typedef std::list HookListType; + HookListType _initHookList; + + friend class ScriptMgr; + friend class AIOScript; +}; + // Placed here due to ScriptRegistry::AddScript dependency. #define sScriptMgr ScriptMgr::instance() @@ -886,6 +1053,7 @@ class GroupScript : public ScriptObject class ScriptMgr { friend class ScriptObject; + friend class AIOScript; private: ScriptMgr(); @@ -1137,9 +1305,14 @@ class ScriptMgr uint32 DecreaseScheduledScriptCount(size_t count) { return _scheduledScripts -= count; } bool IsScriptScheduled() const { return _scheduledScripts > 0; } + public: /* AIOScript */ + + void OnAddonMessage(Player *sender, const std::string &message); + private: uint32 _scriptCount; + AIOHandlers *_aioHandlers; //atomic op counter for active scripts amount std::atomic _scheduledScripts; diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index bb22d27221eab..b89a53b0207ea 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -430,6 +430,24 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) return false; //Will remove this session from the world session map } + //AIO clear long message buffer + if(m_Socket && m_Socket->IsOpen()) + { + for(AddonMessageBufferMap::iterator itr = _addonMessageBuffer.begin(); + itr != _addonMessageBuffer.end();) + { + itr->second.Timer += diff; + if(itr->second.Timer >= 30000) + { + _addonMessageBuffer.erase(itr++); + } + else + { + ++itr; + } + } + } + return true; } @@ -568,6 +586,9 @@ void WorldSession::LogoutPlayer(bool save) CharacterDatabase.Execute(stmt); } + //Clear aio long message buffer + _addonMessageBuffer.clear(); + m_playerLogout = false; m_playerSave = false; m_playerRecentlyLogout = true; diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 87a40657e24df..f6b56d71abaf4 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -1064,6 +1064,21 @@ class WorldSession WorldSession(WorldSession const& right) = delete; WorldSession& operator=(WorldSession const& right) = delete; + + //AIO + typedef std::map AddonPartStringMap; + struct LongMessageBufferInfo + { + uint32 Parts; + uint32 Timer; + AddonPartStringMap Map; + + LongMessageBufferInfo() + : Parts(0), Timer(0) + { } + }; + typedef std::map AddonMessageBufferMap; + AddonMessageBufferMap _addonMessageBuffer; }; #endif /// @} diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index b29f39809c65a..d27750ef425eb 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -65,6 +65,10 @@ #include "WeatherMgr.h" #include "WorldSession.h" +#include +#include +#include "smallfolk_cpp/smallfolk.h" +#include "AIOMsg.h" std::atomic World::m_stopEvent(false); uint8 World::m_ExitCode = SHUTDOWN_EXIT_CODE; @@ -1294,6 +1298,15 @@ void World::LoadConfigSettings(bool reload) m_bool_configs[CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA] = sConfigMgr->GetBoolDefault("Calculate.Creature.Zone.Area.Data", false); m_bool_configs[CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA] = sConfigMgr->GetBoolDefault("Calculate.Gameoject.Zone.Area.Data", false); + // AIO Configs + m_int_configs[CONFIG_AIO_MAXPARTS] = sConfigMgr->GetIntDefault("AIO.MaxParts", 4); + m_bool_configs[CONFIG_AIO_OBFUSCATE] = sConfigMgr->GetBoolDefault("AIO.Obfuscate", false); + m_bool_configs[CONFIG_AIO_COMPRESS] = sConfigMgr->GetBoolDefault("AIO.Compress", false); + m_aioclientpath = sConfigMgr->GetStringDefault("AIO.ClientScriptPath", "lua_client_scripts"); + m_aioprefix = sConfigMgr->GetStringDefault("AIO.Prefix", "AIO"); + if(m_aioprefix.size() > 15) + m_aioprefix = m_aioprefix.substr(0, 15); + // call ScriptMgr if we're reloading the configuration if (reload) sScriptMgr->OnConfigLoad(reload); @@ -3272,3 +3285,185 @@ void World::ReloadRBAC() if (WorldSession* session = itr->second) session->InvalidateRBACData(); } + +bool World::AddAddon(const AIOAddon &addon) +{ + if(addon.file.empty()) + return false; + + //Check if addon already exist + for(AddonCodeListType::iterator itr = m_AddonList.begin(); + itr != m_AddonList.end(); + ++itr) + { + if(itr->name == addon.name) + { + return false; + } + } + + AIOAddon copy(addon); + copy.code = ""; + + //Format path + std::string path; + path = sWorld->GetAIOClientScriptPath(); + if(path.back() != '/' && path.back() != '\\') + { + path += '/'; + } + path += copy.file; + + //Get file + std::ifstream in(path, std::ios::in | std::ios::binary); + if(in) + { + in.seekg(0, std::ios::end); + copy.code.resize(in.tellg()); + in.seekg(0, std::ios::beg); + in.read(©.code[0], copy.code.size()); + in.close(); + if(copy.code.empty()) + { + return false; + } + } + else + { + sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO AddAddon: Couldn't open file %s of addon %s", path.c_str(), copy.name.c_str()); + return false; + } + + //Set crc on original file content + boost::crc_32_type crc_result; + crc_result.process_bytes(copy.code.data(), copy.code.length()); + copy.crc = crc_result.checksum(); + + //Process code + char compressPrefix = 'U'; +// if(sWorld->getBoolConfig(CONFIG_AIO_OBFUSCATE)) +// { +// //Obf +// } +// if(sWorld->getBoolConfig(CONFIG_AIO_COMPRESS)) +// { +// compressPrefix = 'C'; +// } + + //Set final code and go + copy.code = std::string(1, compressPrefix) + copy.code; + m_AddonList.push_back(copy); + + sLog->outAIOMessage(0, LOG_LEVEL_INFO, "AIO: Loaded addon %s from file %s", copy.name.c_str(), copy.file.c_str()); + return true; +} + +uint32 World::RemoveAddon(const std::string &addonName) +{ + for(AddonCodeListType::iterator itr = m_AddonList.begin(); + itr != m_AddonList.end(); + ++itr) + { + if(itr->name == addonName) + { + uint32 sec = itr->permission; + m_AddonList.erase(itr); + return sec; + } + } + return 0; +} + +bool World::ReloadAddons() +{ + sLog->outAIOMessage(0, LOG_LEVEL_INFO, "World::ReloadAddons()"); + + AddonCodeListType prevAddonList; + prevAddonList.swap(m_AddonList); + try + { + for(AddonCodeListType::const_iterator itr = prevAddonList.begin(); + itr != prevAddonList.end(); + ++itr) + { + AddAddon(*itr); + } + } + catch(std::exception &e) + { + sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO: Error reloading addons. Exception: %s", e.what()); + m_AddonList.swap(prevAddonList); + return false; + } + catch(...) + { + sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO: Error reloading addons"); + m_AddonList.swap(prevAddonList); + return false; + } + return true; +} + +size_t World::PrepareClientAddons(const LuaVal &clientData, LuaVal &addonsTable, LuaVal &cacheTable, Player *forPlayer) const +{ + uint32 i = 0; + for(AddonCodeListType::const_iterator itr = m_AddonList.begin(); + itr != m_AddonList.end(); + ++itr) + { + if(!forPlayer->GetSession()->HasPermission(itr->permission)) + continue; + + LuaVal &CRCVal = clientData[itr->name]; + if(CRCVal == itr->crc) + { + cacheTable[++i] = itr->name; + } + else + { + LuaVal addonData(TTABLE); + addonData["name"] = itr->name; + addonData["crc"] = itr->crc; + addonData["code"] = itr->code; + addonsTable[++i] = addonData; + } + } + return i; +} + +void World::ForceReloadPlayerAddons(uint32 permission) +{ + for(SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) + { + if(itr->second->GetPlayer() && itr->second->HasPermission(permission)) + itr->second->GetPlayer()->ForceReloadAddons(); + } +} + +void World::ForceResetPlayerAddons(uint32 permission) +{ + for(SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) + { + if(itr->second->GetPlayer() && itr->second->HasPermission(permission)) + itr->second->GetPlayer()->ForceResetAddons(); + } +} + +void World::AIOMessageAll(AIOMsg &msg, uint32 permission) +{ + std::string messageStr = msg.dumps(); + for(SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) + { + if(itr->second->GetPlayer() && itr->second->HasPermission(permission)) + itr->second->GetPlayer()->SendSimpleAIOMessage(messageStr); + } +} + +void World::SendAllSimpleAIOMessage(const std::string &message, uint32 permission) +{ + for(SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) + { + if(itr->second->GetPlayer() && itr->second->HasPermission(permission)) + itr->second->GetPlayer()->SendSimpleAIOMessage(message); + } +} diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index af89adcb04e20..cb2f1b1ce742e 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -35,6 +35,8 @@ #include #include +#define AIO_VERSION 1.7 + class Object; class WorldPacket; class WorldSession; @@ -42,6 +44,9 @@ class Player; class WorldSocket; class SystemMgr; +class LuaVal; +class AIOMsg; + // ServerMessages.dbc enum ServerMessageType { @@ -161,6 +166,8 @@ enum WorldBoolConfigs CONFIG_ALLOW_TRACK_BOTH_RESOURCES, CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA, CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA, + CONFIG_AIO_OBFUSCATE, + CONFIG_AIO_COMPRESS, BOOL_CONFIG_VALUE_COUNT }; @@ -347,6 +354,7 @@ enum WorldIntConfigs CONFIG_CHARTER_COST_ARENA_5v5, CONFIG_NO_GRAY_AGGRO_ABOVE, CONFIG_NO_GRAY_AGGRO_BELOW, + CONFIG_AIO_MAXPARTS, INT_CONFIG_VALUE_COUNT }; @@ -761,6 +769,76 @@ class World void ReloadRBAC(); + struct AIOAddon + { + std::string name; + std::string code; + std::string file; + uint32 crc; + uint32 permission; + + // AIOAddon container constructor + // Permission 195 will load the addon on every player + AIOAddon(const std::string &addonName, const std::string &addonFile, uint32 permission = 195) + : name(addonName), file(addonFile), permission(permission), + crc(0) + { } + }; + + // AIO prefix configured in worldserver.conf + std::string GetAIOPrefix() const { return m_aioprefix; } + + // AIO client LUA files path configured in worldserver.conf + std::string GetAIOClientScriptPath() const { return m_aioclientpath; } + + // Forces reload on all player AIO addons + // Syncs player AIO addons with server + void ForceReloadPlayerAddons(uint32 permission = 195); + + // Forces reset on all player AIO addons + // Player AIO addons and addon data is deleted and downloaded again + void ForceResetPlayerAddons(uint32 permission = 195); + + // Sends an AIO message to all players + // See: class AIOMsg + void AIOMessageAll(AIOMsg &msg, uint32 permission = 195); + + // Sends a simple string message to all players + + // AIO can only understand smallfolk LuaVal::dumps() format + // Handler functions are called by creating a table as below + // { + // {n, ScriptName, HandlerName(optional), Arg1..N(optional) }, + // {n, AnotherScriptName, AnotherHandlerName(optional), Arg1..N(optional) } + // } + // Where n is number of arguments including handler name as a argument + void SendAllSimpleAIOMessage(const std::string &message, uint32 permission = 195); + + // Reloads client side AIO addon files and force reloads + // all player AIO addons + // Returns true if successful, false if an error occurred + bool ReloadAddons(); + + // Adds a WoW AIO addon file to the list of addons with a unique + // addon name to send on AIO client initialization. + // Returns true if addon was added, false if addon name is already taken + // + // It is required to call World::ForceReloadPlayerAddons() + // if addons are added after server is fully initialized + // for online players to load the added addons. + bool AddAddon(const AIOAddon &addon); + + // Removes an addon from addon list and force reloads affected players + // Returns permission id if an addon was removed, 0 if addon not found + // + // It is required to call World::ForceReloadPlayerAddons() + // if addons are added after server is fully initialized + // for online players to load the added addons. + uint32 RemoveAddon(const std::string &addonName); + + // For internal use only + size_t PrepareClientAddons(const LuaVal &clientData, LuaVal &addonsTable, LuaVal &cacheTable, Player *forPlayer) const; + protected: void _UpdateGameTime(); // callback for UpdateRealmCharacters @@ -863,6 +941,13 @@ class World void ProcessQueryCallbacks(); std::deque> m_realmCharCallbacks; + + typedef std::list AddonCodeListType; + AddonCodeListType m_AddonList; + std::string m_aioprefix; + std::string m_aioclientpath; + + friend class AIOScript; }; extern uint32 realmID; diff --git a/src/server/scripts/AIO/CMakeLists.txt b/src/server/scripts/AIO/CMakeLists.txt new file mode 100644 index 0000000000000..a817ac787d5d1 --- /dev/null +++ b/src/server/scripts/AIO/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2008-2013 TrinityCore +# +# 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. + +file(GLOB_RECURSE sources_AIO *.cpp *.h) + +set(scripts_STAT_SRCS + ${scripts_STAT_SRCS} + ${sources_AIO} +) + +message(" -> Prepared: AIO Scripts") diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt index db15ce8c36d36..114f549895e89 100644 --- a/src/server/scripts/CMakeLists.txt +++ b/src/server/scripts/CMakeLists.txt @@ -37,6 +37,7 @@ if(SCRIPTS) include(Northrend/CMakeLists.txt) include(Events/CMakeLists.txt) include(Pet/CMakeLists.txt) + include(AIO/CMakeLists.txt) endif() message(STATUS "SCRIPT PREPARATION COMPLETE") @@ -52,6 +53,7 @@ include_directories( ${CMAKE_SOURCE_DIR}/dep/SFMT ${CMAKE_SOURCE_DIR}/dep/cppformat ${CMAKE_SOURCE_DIR}/dep/zlib + ${CMAKE_SOURCE_DIR}/dep/smallfolk_cpp ${CMAKE_SOURCE_DIR}/src/server/database ${CMAKE_SOURCE_DIR}/src/server/database/Database ${CMAKE_SOURCE_DIR}/src/server/database/Logging diff --git a/src/server/scripts/Commands/cs_caio.cpp b/src/server/scripts/Commands/cs_caio.cpp new file mode 100644 index 0000000000000..3b428a3dd78e9 --- /dev/null +++ b/src/server/scripts/Commands/cs_caio.cpp @@ -0,0 +1,245 @@ +/* ScriptData +Name: caio_commandscript +%Complete : 100 +Comment : All AIO related server side commands +Category : commandscripts +EndScriptData */ + +#include "Player.h" +#include "ScriptMgr.h" +#include "World.h" +#include "Language.h" + +class caio_commandscript : public CommandScript +{ + public: + caio_commandscript() : CommandScript("caio_commandscript") { } + + ChatCommand* GetCommands() const + { + static ChatCommand caioCommandTable[] = + { + { "version", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleVersionCommand, "", NULL }, + { "send", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleSendCommand, "", NULL }, + { "forcereload", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadCommand, "", NULL }, + { "forcereset", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleResetCommand, "", NULL }, + { "sendall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleSendAllCommand, "", NULL }, + { "forcereloadall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadAllCommand, "", NULL }, + { "forceresetall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleResetAllCommand, "", NULL }, + { "reloadaddons", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadAddonsCommand, "", NULL }, + { "addaddon", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleAddAddonCommand, "", NULL }, + { "removeaddon", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleRemoveAddonCommand, "", NULL }, + { NULL, 0, false, NULL, "", NULL } + }; + static ChatCommand commandTable[] = + { + { "caio", rbac::RBAC_PERM_COMMAND_CAIO, true, 0, "", caioCommandTable }, + { NULL, 0, false, NULL, "", NULL } + }; + return commandTable; + } + + static bool HandleVersionCommand(ChatHandler* handler, char const* args) + { + handler->PSendSysMessage("AIO version %f.", AIO_VERSION); + return true; + } + + static bool HandleSendCommand(ChatHandler* handler, char const* args) + { + //Player name + Player* target; + if(!handler->extractPlayerTarget((char*)args, &target)) + return false; + + //Quoted message + char* tailStr = strtok(NULL, ""); + if(!tailStr) + return false; + + char* msg = handler->extractQuotedArg(tailStr); + if(!msg) + return false; + + target->SendSimpleAIOMessage(msg); + handler->PSendSysMessage(LANG_SENDMESSAGE, target->GetName().c_str(), msg); + return true; + }; + + static bool HandleReloadCommand(ChatHandler* handler, char const* args) + { + Player *target = 0; + if(!handler->extractPlayerTarget((char*)args, &target, 0, 0)) + return false; + + target->ForceReloadAddons(); + handler->PSendSysMessage(LANG_CAIO_FORCERELOAD_SENT, target->GetName().c_str()); + return true; + }; + + static bool HandleResetCommand(ChatHandler* handler, char const* args) + { + Player *target = 0; + if(!handler->extractPlayerTarget((char*)args, &target, 0, 0)) + return false; + + target->ForceResetAddons(); + handler->PSendSysMessage(LANG_CAIO_FORCERESET_SENT, target->GetName().c_str()); + return true; + }; + + static bool HandleSendAllCommand(ChatHandler* handler, char const* args) + { + if(!*args) + return false; + + //Get message + char *msg = handler->extractQuotedArg((char*)args); + if(!msg) + return false; + + char *permission = strtok(NULL, ""); + uint32 perm = 195; + if(permission) //Get permission if its there + { + try + { + perm = std::stoi(args); + } + catch(std::exception &) + { + return false; + } + } + + sWorld->SendAllSimpleAIOMessage(msg, perm); + handler->PSendSysMessage(LANG_SENDMESSAGE, "all players", msg); + return true; + }; + + static bool HandleReloadAllCommand(ChatHandler* handler, char const* args) + { + uint32 perm = 195; + if(args && *args) //Get permission if its there + { + try + { + perm = std::stoi(args); + } + catch(std::exception &) + { + return false; + } + } + + //Force reload required players + sWorld->ForceReloadPlayerAddons(perm); + handler->PSendSysMessage(LANG_CAIO_FORCERELOAD_SENT, "all players"); + return true; + }; + + static bool HandleResetAllCommand(ChatHandler* handler, char const* args) + { + uint32 perm = 195; + if(args && *args) //Get permission if its there + { + try + { + perm = std::stoi(args); + } + catch(std::exception &) + { + return false; + } + } + + //Force reset required players + sWorld->ForceResetPlayerAddons(perm); + handler->PSendSysMessage(LANG_CAIO_FORCERESET_SENT, "all players"); + return true; + }; + + static bool HandleReloadAddonsCommand(ChatHandler* handler, char const* args) + { + + //Reload and force reload required players + bool success = sWorld->ReloadAddons(); + if(success) + { + sWorld->ForceReloadPlayerAddons(); + } + else + { + handler->SendSysMessage(LANG_CAIO_RELOADADDONS_ERROR); + } + return true; + } + + static bool HandleAddAddonCommand(ChatHandler* handler, char const* args) + { + if(!*args) + return false; + + //Addon name + char *addonName = strtok((char*)args, " "); + if(!addonName || addonName[0] == '"') + return false; + + //File + char *tailStr = strtok(NULL, ""); + char *addonFile = handler->extractQuotedArg(tailStr); + if(!addonFile) + return false; + + //Permission + char *permission = strtok(NULL, ""); + uint32 perm = 195; + if(permission) + { + try + { + perm = std::stoi(permission); + } + catch(std::exception &) + { + return false; + } + } + + //Add + World::AIOAddon newAddon(addonName, addonFile, perm); + bool added = sWorld->AddAddon(newAddon); + if(added) + { + sWorld->ForceReloadPlayerAddons(perm); + } + else + { + handler->PSendSysMessage(LANG_CAIO_ADDADDON_ERROR, addonName); + } + return true; + } + + static bool HandleRemoveAddonCommand(ChatHandler* handler, char const* args) + { + if(!*args) + return false; + + //Remove and reload required players + uint32 perm = sWorld->RemoveAddon(args); + if(perm != 0) + { + sWorld->ForceReloadPlayerAddons(perm); + } + else + { + handler->PSendSysMessage(LANG_CAIO_REMOVEADDON_ERROR, args); + } + return true; + } +}; + +void AddSC_caio_commandscript() +{ + new caio_commandscript(); +} \ No newline at end of file diff --git a/src/server/worldserver/CMakeLists.txt b/src/server/worldserver/CMakeLists.txt index f513ba0857ffd..f3595ecbfc031 100644 --- a/src/server/worldserver/CMakeLists.txt +++ b/src/server/worldserver/CMakeLists.txt @@ -49,6 +49,7 @@ include_directories( ${CMAKE_SOURCE_DIR}/dep/SFMT ${CMAKE_SOURCE_DIR}/dep/cppformat ${CMAKE_SOURCE_DIR}/dep/process + ${CMAKE_SOURCE_DIR}/dep/smallfolk_cpp ${CMAKE_SOURCE_DIR}/src/server/database ${CMAKE_SOURCE_DIR}/src/server/database/Database ${CMAKE_SOURCE_DIR}/src/server/database/Logging @@ -207,9 +208,11 @@ endif() if( UNIX ) install(TARGETS worldserver DESTINATION bin) install(FILES worldserver.conf.dist DESTINATION ${CONF_DIR}) + install(DIRECTORY "lua_client_scripts" DESTINATION bin) elseif( WIN32 ) install(TARGETS worldserver DESTINATION "${CMAKE_INSTALL_PREFIX}") install(FILES worldserver.conf.dist DESTINATION "${CMAKE_INSTALL_PREFIX}") + install(DIRECTORY "lua_client_scripts" DESTINATION "${CMAKE_INSTALL_PREFIX}") endif() # Generate precompiled header diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 8e06c1c9ca41b..772c5e18ec80c 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -3257,6 +3257,7 @@ Appender.Console=1,3,0 Appender.Server=2,2,0,Server.log,w Appender.GM=2,2,15,gm/gm_%s.log Appender.DBErrors=2,2,0,DBErrors.log +Appender.AIO=2,3,3,AIO/AIO_%s.log # Logger config values: Given a logger "name" # Logger.name @@ -3281,6 +3282,7 @@ Logger.server=3,Console Server Logger.commands.gm=3,Console GM Logger.sql.sql=5,Console DBErrors Logger.sql.updates=3,Console Server +Logger.AIO=3,Console AIO #Logger.achievement=3,Console Server #Logger.addon=3,Console Server @@ -3381,3 +3383,35 @@ PacketSpoof.BanDuration = 86400 # ################################################################################################### + +################################################################################################### +# +# AIO settings +# +# AIO.prefix +# Prefix string used for AIO addon messages +# Default: "AIO" +# +# AIO.MaxParts +# Maximum number of parts a client can send to server +# Each part can have maximum 255 bytes +# Default: 4 +# +# AIO.ClientScriptPath +# Path to client lua scripts to send to the client +# Default: "lua_client_scripts" +# +# AIO.Obfuscate +# Whether to obfuscate before sending addon code +# Default: 1 +# +# AIO.Compress +# Whether to compress before sending addon code +# Default: 1 +# + +AIO.Prefix = "AIO" +AIO.MaxParts = 4 +AIO.ClientScriptPath = "lua_client_scripts" +AIO.Obfuscate = 1 +AIO.Compress = 1 From 08751921d8cfaaeb9610e11e45f01c19f78b9934 Mon Sep 17 00:00:00 2001 From: Saif Date: Sat, 22 Aug 2015 02:31:57 +0500 Subject: [PATCH 02/33] Fixed typo in caio readme --- CAIO README.md => CAIO_README.md | 3 ++- README.md | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) rename CAIO README.md => CAIO_README.md (99%) diff --git a/CAIO README.md b/CAIO_README.md similarity index 99% rename from CAIO README.md rename to CAIO_README.md index 54c47f70cff2c..c770fe8cf711a 100644 --- a/CAIO README.md +++ b/CAIO_README.md @@ -1,7 +1,8 @@ ## Introduction CAIO is a server-client communication system for WoW AddOns. It is an extension of [AIO](https://github.com/Rochet2/AIO) to support C++ server side handling. -CAIO is designed for sending lua addons and data between players and server +AIO is designed for sending lua addons and data between players and server. + Currently CAIO only supports TrinityCore 3.3.5 branch. ## Supported AIO version diff --git a/README.md b/README.md index 2ef13384aca80..1c7a4d98f896d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +## [CAIO README file](https://github.com/SaiFi0102/TrinityCore/blob/CAIO-3.3.5/CAIO_README.md) + # ![logo](http://www.trinitycore.org/f/public/style_images/1_trinitycore.png) TrinityCore [![Issue Stats](http://www.issuestats.com/github/TrinityCore/TrinityCore/badge/issue)](http://www.issuestats.com/github/TrinityCore/TrinityCore) [![Issue Stats](http://www.issuestats.com/github/TrinityCore/TrinityCore/badge/pr)](http://www.issuestats.com/github/TrinityCore/TrinityCore) [![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=1310)](https://www.bountysource.com/trackers/1310-trinity-core?utm_source=1310&utm_medium=shield&utm_campaign=TRACKER_BADGE) From 89acc9ecc8eea5f3e814f868e8a02babe24ea201 Mon Sep 17 00:00:00 2001 From: Saif Date: Sat, 22 Aug 2015 03:05:43 +0500 Subject: [PATCH 03/33] Update CAIO_README.md Compare link --- CAIO_README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CAIO_README.md b/CAIO_README.md index c770fe8cf711a..6e9c71fe85e56 100644 --- a/CAIO_README.md +++ b/CAIO_README.md @@ -3,7 +3,7 @@ CAIO is a server-client communication system for WoW AddOns. It is an extension of [AIO](https://github.com/Rochet2/AIO) to support C++ server side handling. AIO is designed for sending lua addons and data between players and server. -Currently CAIO only supports TrinityCore 3.3.5 branch. +Currently CAIO only supports TrinityCore 3.3.5 branch. [Compare and review](https://github.com/TrinityCore/TrinityCore/compare/3.3.5...SaiFi0102:CAIO-3.3.5). ## Supported AIO version From c1c27091cb584a3673d91e0831e19d63fa97b162 Mon Sep 17 00:00:00 2001 From: Saif Date: Sun, 23 Aug 2015 14:04:28 +0500 Subject: [PATCH 04/33] -Using LuaVal for script key and handler key type -Now handling multi block AIO messages Not tested, gonna test very soon. --- CAIO_README.md | 95 +++++++++------- dep/smallfolk_cpp/smallfolk_cpp | 2 +- src/server/game/Chat/AIOMsg.cpp | 41 ++++--- src/server/game/Chat/AIOMsg.h | 6 +- src/server/game/Entities/Player/Player.cpp | 4 +- src/server/game/Entities/Player/Player.h | 4 +- src/server/game/Scripting/ScriptMgr.cpp | 124 +++++++-------------- src/server/game/Scripting/ScriptMgr.h | 47 ++++---- src/server/game/World/World.h | 2 +- 9 files changed, 147 insertions(+), 178 deletions(-) diff --git a/CAIO_README.md b/CAIO_README.md index 6e9c71fe85e56..a97940249d34b 100644 --- a/CAIO_README.md +++ b/CAIO_README.md @@ -7,7 +7,7 @@ Currently CAIO only supports TrinityCore 3.3.5 branch. [Compare and review](http ## Supported AIO version -AIO Version 1.7 +AIO Version 1.72 ## Install @@ -20,14 +20,13 @@ AIO Version 1.7 ## Todo -+ Use LuaVal as script/handler key type to allow types other than just string + Add CAIO `Init` time-out configuration + Add CAIO buffer time-out configuration + Add CAIO error time-out configuration + Add CAIO maximum cache size configuration + Implement Obfuscation -+ Implement Compression + Add individual permissions for each CAIO command ++ Implement Compression ## API reference @@ -124,36 +123,42 @@ https://github.com/Rochet2/smallfolk_cpp ```cpp class AIOScript : public ScriptObject { +public: + virtual ~AIOScript(); + + // Returns the key of this CAIO script + LuaVal GetKey() const; + protected: // Registers an AIO Handler script of scriptName - AIOScript(const char *scriptName); + AIOScript(const LuaVal &scriptKey); // Registers a handler function to call when handling - // handlerName of this script. - void AddHandler(const char *handlerName, HandlerFunc function); + // handleKey of this script. + void AddHandler(const LuaVal &handlerKey, HandlerFunc function); // Adds a client side handler to call and adds arguments // to sends with it for AIO client initialization. // // You can add additional arguments to the handler by // calling this function again - void AddInitArgs(const std::string &scriptName, const std::string &handlerName, + void AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); // Adds a WoW addon file to the list of addons with a unique - // addon name to send on AIO client initialization. - // Returns true if addon was added, false if addon name is taken. + // addon key to send on AIO client initialization. + // Returns true if addon was added, false if addon key is taken. // // It is required to call World::ForceReloadPlayerAddons() // if addons are added after server is fully initialized // for online players to load the added addons. bool AddAddon(const World::AIOAddon &addon); - // Returns pointer to an AIO script by its name and typename. + // Returns pointer to an AIO script by its key and typename. // Returns null if scriptName doesn't exist or typename was incorrect. template - ScriptClass *GetScript(const std::string &scriptName); + ScriptClass *GetScript(const LuaVal &key); } ``` @@ -167,13 +172,13 @@ public: AIOMsg(); //Creates a AIO message and adds one block - AIOMsg(const std::string &scriptName, const std::string &handlerName, + AIOMsg(const LuaVal &scriptKey, const LuaVal &handlerKey, const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); //Adds another block //Another block will call another handler in one message - AIOMsg &Add(const std::string &scriptName, const std::string &handlerName, + AIOMsg &Add(cconst LuaVal &scriptKey, const LuaVal &handlerKey, const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); @@ -189,39 +194,43 @@ public: **Player.h** ```cpp -//Returns whether AIO client has been initialized -bool AIOInitialized() const; - -// Sends an AIO message to the player -// See: class AIOMsg -void AIOMessage(AIOMsg &msg); - -// Triggers an AIO script handler on the client -// To trigger multiple handlers in one message or to send more -// arguments use Player::AIOMessage -void AIOHandle(const std::string &scriptName, const std::string &handlerName, - const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); - -// AIO can only understand smallfolk LuaVal::dumps() format -// Handler functions are called by creating a table as below -// { -// {n, ScriptName, HandlerName(optional), Arg1..N(optional) }, -// {n, AnotherScriptName, AnotherHandlerName(optional), Arg1..N(optional) } -// } -// Where n is number of arguments including handler name as an argument -void SendSimpleAIOMessage(const std::string &message); +class Player +{ +public: + //Returns whether AIO client has been initialized + bool AIOInitialized() const; -// Forces reload on the player AIO addons -// Syncs player AIO addons with server -void ForceReloadAddons(); + // Sends an AIO message to the player + // See: class AIOMsg + void AIOMessage(AIOMsg &msg); -// Force reset on the player AIO addons -// Player AIO addons and addon data is deleted and downloaded again -void ForceResetAddons(); + // Triggers an AIO handler on the client + // To trigger multiple handlers in one message or to send more + // arguments use Player::AIOMessage + void AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, + const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); -bool isAIOInitOnCooldown() const; -void setAIOIntOnCooldown(bool cd); + // AIO can only understand smallfolk LuaVal::dumps() format + // Handler functions are called by creating a table as below + // { + // {n, ScriptName, HandlerName(optional), Arg1..N(optional) }, + // {n, AnotherScriptName, AnotherHandlerName(optional), Arg1..N(optional) } + // } + // Where n is number of arguments including handler name as an argument + void SendSimpleAIOMessage(const std::string &message); + + // Forces reload on the player AIO addons + // Syncs player AIO addons with server + void ForceReloadAddons(); + + // Force reset on the player AIO addons + // Player AIO addons and addon data is deleted and downloaded again + void ForceResetAddons(); + + bool isAIOInitOnCooldown() const; + void setAIOIntOnCooldown(bool cd); +} ``` **World.h** diff --git a/dep/smallfolk_cpp/smallfolk_cpp b/dep/smallfolk_cpp/smallfolk_cpp index 91be8769ff224..bf45ea83aa806 160000 --- a/dep/smallfolk_cpp/smallfolk_cpp +++ b/dep/smallfolk_cpp/smallfolk_cpp @@ -1 +1 @@ -Subproject commit 91be8769ff2248ace28080bd5b0121683aa614ab +Subproject commit bf45ea83aa80621163c354ab6a3757271bda7b53 diff --git a/src/server/game/Chat/AIOMsg.cpp b/src/server/game/Chat/AIOMsg.cpp index 0c68d35e37e03..345828c4d0fee 100644 --- a/src/server/game/Chat/AIOMsg.cpp +++ b/src/server/game/Chat/AIOMsg.cpp @@ -5,46 +5,47 @@ AIOMsg::AIOMsg() : _val(TTABLE) { } -AIOMsg &AIOMsg::Add(const std::string &scriptName, const std::string &handlerName, const LuaVal &a1, const LuaVal &a2, const LuaVal &a3, const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) +AIOMsg &AIOMsg::Add(const LuaVal &scriptKey, const LuaVal &handlerKey, const LuaVal &a1, const LuaVal &a2, const LuaVal &a3, const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) { LuaVal block(TTABLE); uint32 nArgs = 1; - block.insert(nArgs); - block.insert(scriptName); - block.insert(handlerName); + block[1]; + block[2] = scriptKey; + block[3] = handlerKey; if(!a1.isnil()) { block.insert(a1); - block.set(1, ++nArgs); + ++nArgs; } if(!a2.isnil()) { block.insert(a2); - block.set(1, ++nArgs); + ++nArgs; } if(!a3.isnil()) { block.insert(a3); - block.set(1, ++nArgs); + ++nArgs; } if(!a4.isnil()) { block.insert(a4); - block.set(1, ++nArgs); + ++nArgs; } if(!a5.isnil()) { block.insert(a5); - block.set(1, ++nArgs); + ++nArgs; } if(!a6.isnil()) { block.insert(a6); - block.set(1, ++nArgs); + ++nArgs; } + block[1] = nArgs; _val.insert(block); return *this; } @@ -53,47 +54,45 @@ AIOMsg &AIOMsg::AppendLast(const LuaVal &a1, const LuaVal &a2, const LuaVal &a3, { unsigned int lastBlock = _val.len(); if(!lastBlock) - { return *this; - } LuaVal &block = _val[lastBlock]; LuaVal nArgsVal = block.get(1); if(!nArgsVal.isnumber()) - { return *this; - } - uint32 nArgs = (uint32)nArgsVal.num(); + unsigned int nArgs = nArgsVal.num(); if(!a1.isnil()) { block.insert(a1); - block.set(1, ++nArgs); + ++nArgs; } if(!a2.isnil()) { block.insert(a2); - block.set(1, ++nArgs); + ++nArgs; } if(!a3.isnil()) { block.insert(a3); - block.set(1, ++nArgs); + ++nArgs; } if(!a4.isnil()) { block.insert(a4); - block.set(1, ++nArgs); + ++nArgs; } if(!a5.isnil()) { block.insert(a5); - block.set(1, ++nArgs); + ++nArgs; } if(!a6.isnil()) { block.insert(a6); - block.set(1, ++nArgs); + ++nArgs; } + + block[1] = nArgs; return *this; } diff --git a/src/server/game/Chat/AIOMsg.h b/src/server/game/Chat/AIOMsg.h index daea69f90ce9c..a41cc8178be28 100644 --- a/src/server/game/Chat/AIOMsg.h +++ b/src/server/game/Chat/AIOMsg.h @@ -12,17 +12,17 @@ class AIOMsg AIOMsg(); //Creates a AIO message and adds one block - AIOMsg(const std::string &scriptName, const std::string &handlerName, + AIOMsg(const LuaVal &scriptKey, const LuaVal &handlerKey, const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()) : _val(TTABLE) { - Add(scriptName, handlerName, a1, a2, a3, a4, a5); + Add(scriptKey, handlerKey, a1, a2, a3, a4, a5); } //Adds another block //Another block will call another handler in one message - AIOMsg &Add(const std::string &scriptName, const std::string &handlerName, + AIOMsg &Add(const LuaVal &scriptKey, const LuaVal &handlerKey, const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 1ffb01ea91874..2233d859a92de 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -20565,9 +20565,9 @@ void Player::AIOMessage(AIOMsg &msg) SendSimpleAIOMessage(msg.dumps()); } -void Player::AIOHandle(const std::string &scriptName, const std::string &handlerName, const LuaVal &a1, const LuaVal &a2, const LuaVal &a3,const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) +void Player::AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, const LuaVal &a1, const LuaVal &a2, const LuaVal &a3, const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) { - AIOMsg msg(scriptName, handlerName, a1, a2, a3, a4, a5, a6); + AIOMsg msg(scriptKey, handlerKey, a1, a2, a3, a4, a5, a6); SendSimpleAIOMessage(msg.dumps()); } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index da6ff12e5e9d1..1ff1b634c3f82 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1224,10 +1224,10 @@ class Player : public Unit, public GridObject // See: class AIOMsg void AIOMessage(AIOMsg &msg); - // Triggers an AIO script handler on the client + // Triggers an AIO handler on the client // To trigger multiple handlers in one message or to send more // arguments use Player::AIOMessage - void AIOHandle(const std::string &scriptName, const std::string &handlerName, + void AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index cf50852120676..91c8dca87c29a 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -1628,70 +1628,51 @@ GroupScript::GroupScript(const char* name) ScriptRegistry::AddScript(this); } -AIOScript::AIOScriptByNameMap AIOScript::_scriptByNameMap = AIOScript::AIOScriptByNameMap(); +AIOScript::AIOScriptByKeyMap AIOScript::_scriptByKeyMap = AIOScript::AIOScriptByKeyMap(); void ScriptMgr::OnAddonMessage(Player *sender, const std::string &message) { if(!sender) return; - LuaVal Doc = LuaVal::loads(message.c_str()); - if(!Doc.istable()) //Unable to parse or incorrect format - { - return; - } - - LuaVal &Args = Doc[1]; - if(!Args.istable()) - { + LuaVal mainTable = LuaVal::loads(message); + if(!mainTable.istable()) //Unable to parse or incorrect format return; - } - LuaVal &scriptNameVal = Args[2]; - LuaVal &handlerNameVal = Args[3]; - if(!Args[1].isnumber() || !scriptNameVal.isstring() || !handlerNameVal.isstring()) + //Call handlers from all blocks in order + for(size_t i = 1; i <= mainTable.tbl().size(); ++i) { - return; - } + LuaVal &block = mainTable[1]; + if(!block.istable()) + continue; - std::string scriptName = scriptNameVal.str(); - std::string handlerName = handlerNameVal.str(); + LuaVal &scriptKeyVal = block[2]; + LuaVal &handlerKeyVal = block[3]; + if(!block[1].isnumber() || scriptKeyVal.isnil() || handlerKeyVal.isnil()) + continue; - AIOScript *aioScript = _aioHandlers->GetScript(scriptName); - if(aioScript) - { - aioScript->OnHandle(sender, scriptName, handlerName, Args); + if(AIOScript *aioScript = _aioHandlers->GetScript(scriptKeyVal)) + aioScript->OnHandle(sender, handlerKeyVal, block); } } -AIOScript::AIOScript(const char *name) - : ScriptObject(name) +AIOScript::AIOScript(const LuaVal &scriptKey) +: ScriptObject(scriptKey.tostring().c_str()), _key(scriptKey) { - if(AIOScript::_scriptByNameMap.find("name") != AIOScript::_scriptByNameMap.end()) + if(AIOScript::_scriptByKeyMap.find(scriptKey) != AIOScript::_scriptByKeyMap.end()) { - throw std::runtime_error(std::string("AIO scriptName '") + name + "' already exist. Use another name."); + sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '%s' of type tag '%i' already exist. Use another key.", scriptKey.tostring().c_str(), scriptKey.GetTypeTag()); + ASSERT(false); } ScriptRegistry::AddScript(this); - AIOScript::_scriptByNameMap[name] = this; + AIOScript::_scriptByKeyMap[scriptKey] = this; } -AIOScript::~AIOScript() -{ - AIOScript::_scriptByNameMap.erase(GetName()); -} - -void AIOScript::AddHandler(const char *handlerName, HandlerFunc function) -{ - _handlerMap[handlerName] = function; -} - -void AIOScript::AddInitArgs(const std::string &scriptName, const std::string &handlerName, ArgFunc a1, ArgFunc a2, ArgFunc a3, ArgFunc a4, ArgFunc a5, ArgFunc a6) +void AIOScript::AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, ArgFunc a1, ArgFunc a2, ArgFunc a3, ArgFunc a4, ArgFunc a5, ArgFunc a6) { AIOHandlers *handler = sScriptMgr->_aioHandlers; if(!handler) - { return; - } //Look for hook std::list *list = 0; @@ -1699,7 +1680,7 @@ void AIOScript::AddInitArgs(const std::string &scriptName, const std::string &ha itr != handler->_initHookList.end(); ++itr) { - if(itr->scriptName == scriptName && itr->handlerName == handlerName) + if(itr->scriptKey == scriptKey && itr->handlerKey == handlerKey) { list = &itr->argsList; break; @@ -1709,73 +1690,51 @@ void AIOScript::AddInitArgs(const std::string &scriptName, const std::string &ha //Add hook if(!list) { - handler->_initHookList.push_back(AIOHandlers::InitHookInfo(scriptName, handlerName)); + handler->_initHookList.push_back(AIOHandlers::InitHookInfo(scriptKey, handlerKey)); list = &handler->_initHookList.back().argsList; } //Add args if(a1) - { list->push_back(a1); - } if(a2) - { list->push_back(a2); - } if(a3) - { list->push_back(a3); - } if(a4) - { list->push_back(a4); - } if(a5) - { list->push_back(a5); - } if(a6) - { list->push_back(a6); - } -} - -bool AIOScript::AddAddon(const World::AIOAddon &addon) -{ - return sWorld->AddAddon(addon); } template<> -AIOScript *AIOScript::GetScript(const std::string &scriptName) +AIOScript *AIOScript::GetScript(const LuaVal &scriptKey) { - AIOScriptByNameMap::const_iterator itr = AIOScript::_scriptByNameMap.find(scriptName); - if(itr == AIOScript::_scriptByNameMap.end()) - { + AIOScriptByKeyMap::const_iterator itr = AIOScript::_scriptByKeyMap.find(scriptKey); + if(itr == AIOScript::_scriptByKeyMap.end()) return 0; - } + return itr->second; } template -ScriptClass *AIOScript::GetScript(const std::string &scriptName) +ScriptClass *AIOScript::GetScript(const LuaVal &scriptKey) { - AIOScriptByNameMap::const_iterator itr = AIOScript::_scriptByNameMap.find(scriptName); - if(itr == AIOScript::_scriptByNameMap.end()) - { + AIOScriptByKeyMap::const_iterator itr = AIOScript::_scriptByKeyMap.find(scriptKey); + if(itr == AIOScript::_scriptByKeyMap.end()) return 0; - } + return dynamic_cast(itr->second); } -void AIOScript::OnHandle(Player *sender, const std::string &scriptName, const std::string &handlerName, const LuaVal &args) +void AIOScript::OnHandle(Player *sender, const LuaVal &handlerKey, const LuaVal &args) { - if(scriptName != GetName()) - return; - - HandlerMapType::const_iterator itr = _handlerMap.find(handlerName); + HandlerMapType::const_iterator itr = _handlerMap.find(handlerKey); if(itr != _handlerMap.end()) { - itr->second(sender, args); + itr->second(sender, args); //Call the handler function } } @@ -1790,9 +1749,7 @@ void AIOHandlers::HandleInit(Player *sender, const LuaVal &args) { //Init hasn't cooled down if(sender->isAIOInitOnCooldown()) - { return; - } sender->setAIOIntOnCooldown(true); LuaVal &versionVal = args[4]; @@ -1820,12 +1777,12 @@ void AIOHandlers::HandleInit(Player *sender, const LuaVal &args) itr != _initHookList.end(); ++itr) { - uint32 index = 0; + uint32 index = 3; LuaVal HookBlock(TTABLE); - HookBlock[++index] = (uint32)itr->argsList.size() + 1; - HookBlock[++index] = itr->scriptName; - HookBlock[++index] = itr->handlerName; + HookBlock[1] = (uint32)itr->argsList.size() + 1; + HookBlock[2] = itr->scriptKey; + HookBlock[3] = itr->handlerKey; for(std::list::const_iterator it = itr->argsList.begin(); it != itr->argsList.end(); ++it) @@ -1855,10 +1812,9 @@ void AIOHandlers::HandleError(Player *sender, const LuaVal &args) { LuaVal &msgVal = args[4]; if(!msgVal.isstring()) - { return; - } - sLog->outAIOMessage(sender->GetGUIDLow(), LOG_LEVEL_ERROR, "Player %s sent client addon error message: %s", sender->GetName().c_str(), msgVal.str().c_str()); + + sLog->outAIOMessage(sender->GetGUIDLow(), LOG_LEVEL_ERROR, "%s Received client addon error: %s", sender->GetSession()->GetPlayerInfo().c_str(), msgVal.str().c_str()); } diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index e59f11024fe26..4f92c98fb1e6f 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -27,6 +27,8 @@ #include "World.h" #include "Weather.h" +#include "smallfolk_cpp/smallfolk.h" + class AccountMgr; class AuctionHouseObject; class AuraScript; @@ -870,8 +872,6 @@ class GroupScript : public ScriptObject virtual void OnDisband(Group* /*group*/) { } }; -class LuaVal; - // ##################### Abstract AIO handler script ##################### // Inherit AIOScript to make an AIO handler script // @@ -960,7 +960,10 @@ class LuaVal; class AIOScript : public ScriptObject { public: - ~AIOScript(); + virtual ~AIOScript() { AIOScript::_scriptByKeyMap.erase(GetKey()); } + + // Returns the key of this CAIO script + LuaVal GetKey() const { return _key; } bool IsDatabaseBound() const { return false; } typedef std::function HandlerFunc; @@ -968,46 +971,48 @@ class AIOScript : public ScriptObject protected: // Registers an AIO Handler script of scriptName - AIOScript(const char *scriptName); + AIOScript(const LuaVal &scriptKey); // Registers a handler function to call when handling - // handlerName of this script. - void AddHandler(const char *handlerName, HandlerFunc function); + // handleKey of this script. + void AddHandler(const LuaVal &handlerKey, HandlerFunc function) { _handlerMap[handlerKey] = function; } // Adds a client side handler to call and adds arguments // to sends with it for AIO client initialization. // // You can add additional arguments to the handler by // calling this function again - void AddInitArgs(const std::string &scriptName, const std::string &handlerName, + void AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); // Adds a WoW addon file to the list of addons with a unique - // addon name to send on AIO client initialization. - // Returns true if addon was added, false if addon name is taken. + // addon key to send on AIO client initialization. + // Returns true if addon was added, false if addon key is taken. // // It is required to call World::ForceReloadPlayerAddons() // if addons are added after server is fully initialized // for online players to load the added addons. - bool AddAddon(const World::AIOAddon &addon); + bool AddAddon(const World::AIOAddon &addon) { return sWorld->AddAddon(addon); } - // Returns pointer to an AIO script by its name and typename. + // Returns pointer to an AIO script by its key and typename. // Returns null if scriptName doesn't exist or typename was incorrect. template - ScriptClass *GetScript(const std::string &scriptName); + ScriptClass *GetScript(const LuaVal &key); template<> - AIOScript *GetScript(const std::string &scriptName); + AIOScript *GetScript(const LuaVal &key); private: - void OnHandle(Player *sender, const std::string &scriptName, const std::string &handlerName, const LuaVal &args); + void OnHandle(Player *sender, const LuaVal &handlerKey, const LuaVal &args); + + LuaVal _key; - typedef std::unordered_map HandlerMapType; + typedef std::unordered_map HandlerMapType; HandlerMapType _handlerMap; - typedef std::unordered_map AIOScriptByNameMap; - static AIOScriptByNameMap _scriptByNameMap; + typedef std::unordered_map AIOScriptByKeyMap; + static AIOScriptByKeyMap _scriptByKeyMap; friend class ScriptMgr; }; @@ -1021,12 +1026,12 @@ class AIOHandlers : public AIOScript struct InitHookInfo { - std::string scriptName; - std::string handlerName; + LuaVal scriptKey; + LuaVal handlerKey; std::list argsList; - InitHookInfo(const std::string &scriptName, const std::string &handlerName) - : scriptName(scriptName), handlerName(handlerName) + InitHookInfo(const LuaVal &scriptKey, const LuaVal &handlerKey) + : scriptKey(scriptKey), handlerKey(handlerKey) { } }; diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index cb2f1b1ce742e..9dd78dc3f7325 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -35,7 +35,7 @@ #include #include -#define AIO_VERSION 1.7 +#define AIO_VERSION 1.72 class Object; class WorldPacket; From c0cd8c7b05c91fd401ad37015d7d3b6928c7c375 Mon Sep 17 00:00:00 2001 From: Saif Date: Sun, 23 Aug 2015 17:48:35 +0500 Subject: [PATCH 05/33] Readme typo --- CAIO_README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CAIO_README.md b/CAIO_README.md index a97940249d34b..e40b96091fc87 100644 --- a/CAIO_README.md +++ b/CAIO_README.md @@ -81,7 +81,7 @@ public: { //LuaVal args in a handler function is always a table //Handler arguments index starts from 4 - LuaVal &SaveVal = args.get[4]; + LuaVal &SaveVal = args[4]; //MUST check if the value type is valid if(!SaveVal.isstring()) From f39140aa606e72b0446561ddddde7034888d6923 Mon Sep 17 00:00:00 2001 From: Saif Date: Tue, 25 Aug 2015 02:55:36 +0500 Subject: [PATCH 06/33] Fixed a bug with AIOMsg::Add() --- src/server/game/Chat/AIOMsg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/game/Chat/AIOMsg.cpp b/src/server/game/Chat/AIOMsg.cpp index 345828c4d0fee..c620f0b34fd95 100644 --- a/src/server/game/Chat/AIOMsg.cpp +++ b/src/server/game/Chat/AIOMsg.cpp @@ -10,7 +10,7 @@ AIOMsg &AIOMsg::Add(const LuaVal &scriptKey, const LuaVal &handlerKey, const Lua LuaVal block(TTABLE); uint32 nArgs = 1; - block[1]; + block[1] = 0; block[2] = scriptKey; block[3] = handlerKey; From 85ce08db3fdea3e963d8c3188b3b02a3f2be024b Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Sun, 31 May 2026 20:46:54 +0300 Subject: [PATCH 07/33] CAIO: merge TC 3.3.5, enforce 255-byte AIO packets, and harden protocol handling. - Merge upstream TrinityCore 3.3.5 and fix CAIO build (chat packets, cs_caio, logging, smallfolk). - Cap AIO.MsgMaxLen at 255; fix long-message chunk sizing; add buffer/init config. - Remove compression/obfuscation paths; optional ExampleWindow via WITH_CAIO_EXAMPLES. - Point smallfolk_cpp submodule at LuaVal copy-assignment fix (cfa8c64). --- CAIO_README.md | 30 +- cmake/options.cmake | 1 + cmake/showoptions.cmake | 6 + dep/smallfolk_cpp/smallfolk_cpp | 2 +- doc/CAIO_MESSAGE_FORMAT.md | 9 +- src/common/Logging/Log.cpp | 5 + src/common/Logging/Log.h | 5 +- src/server/game/Entities/Player/Player.cpp | 90 ++-- src/server/game/Entities/Player/Player.h | 2 +- src/server/game/Handlers/ChatHandler.cpp | 15 +- src/server/game/Scripting/ScriptMgr.cpp | 59 +-- src/server/game/Scripting/ScriptMgr.h | 4 +- src/server/game/Server/WorldSession.cpp | 2 +- src/server/game/Server/WorldSession.h | 9 +- src/server/game/World/World.cpp | 29 +- src/server/game/World/World.h | 22 +- src/server/scripts/AIO/ExampleWindow.cpp | 17 +- src/server/scripts/AIO/aio_script_loader.cpp | 4 + src/server/scripts/CMakeLists.txt | 8 + src/server/scripts/Commands/cs_caio.cpp | 448 +++++++++---------- src/server/worldserver/worldserver.conf.dist | 25 +- 21 files changed, 403 insertions(+), 389 deletions(-) diff --git a/CAIO_README.md b/CAIO_README.md index bf132cb5043dd..ff07b76e85341 100644 --- a/CAIO_README.md +++ b/CAIO_README.md @@ -18,17 +18,31 @@ AIO version **1.75** — must match `AIO_VERSION` in your server and client `AIO + Run SQL files from `TrinityCore_Installation_Dir/sql/CAIO` (`Auth.sql` on auth DB, `World.sql` on world DB) + Copy `AIO_Client` from your AIO tree to `WoW_Installation_Dir/Interface/AddOns/AIO_Client` (use the same AIO repo/commit as the server expects) + Copy server-side client addon sources into `TrinityCore_Installation_Dir/lua_client_scripts` (one folder per addon, e.g. `lua_client_scripts/ExampleWindow/ExampleWindow.lua`) -+ Set `AIO.Obfuscate`, `AIO.Compress`, and `AIO.MsgMaxLen` in `worldserver.conf` to match your `AIO.lua` settings ++ Set `AIO.MsgMaxLen` to **255** in `worldserver.conf` (WoW addon whisper limit; matches client `AIO.lua` when `AIO_SERVER` is false) ++ Optional: build with `-DWITH_CAIO_EXAMPLES=ON` to include the `ExampleWindow` test script + +## Build notes (TrinityCore 3.3.5 + CAIO) + ++ OpenSSL **3.x** is detected by upstream `cmake/macros/FindOpenSSL.cmake` after the 3.3.5 merge (no manual `-D_OPENSSL_VERSION` needed in most cases). ++ Boost **1.86** with MSVC may still fail on very old 3.3.5 code paths; **Boost 1.81** is a safe choice if you hit `std::_snprintf` errors in Boost headers. ++ Example CMake configure: + +```powershell +cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DSCRIPTS=static -DTOOLS=0 ` + -DOPENSSL_INCLUDE_DIR="C:/Program Files/OpenSSL-Win64/include" ` + -DSSL_EAY_RELEASE="C:/Program Files/OpenSSL-Win64/lib/VC/x64/MD/libssl.lib" ` + -DLIB_EAY_RELEASE="C:/Program Files/OpenSSL-Win64/lib/VC/x64/MD/libcrypto.lib" ` + -DMYSQL_INCLUDE_DIR="C:/Program Files/MySQL/MySQL Server 8.0/include" ` + -DMYSQL_LIBRARY="C:/Program Files/MySQL/MySQL Server 8.0/lib/libmysql.lib" ` + -DBoost_DIR="C:/local/boost_1_81_0/lib64-msvc-14.3/cmake/Boost-1.81.0" +``` ## Todo -+ Add CAIO `Init` time-out configuration -+ Add CAIO buffer time-out configuration -+ Add CAIO error time-out configuration -+ Add CAIO maximum cache size configuration -+ Implement Obfuscation -+ Add individual permissions for each CAIO command -+ Implement Compression ++ Add CAIO error time-out configuration (distinct from `AIO.BufferTimeout` reassembly timeout) ++ Implement obfuscation (optional, deferred) ++ Implement compression (optional, deferred) ++ Add individual RBAC permissions per `.caio` subcommand (optional; all subcommands use `RBAC_PERM_COMMAND_CAIO` today) ## API reference diff --git a/cmake/options.cmake b/cmake/options.cmake index e939528584f0a..005d74770001c 100644 --- a/cmake/options.cmake +++ b/cmake/options.cmake @@ -33,6 +33,7 @@ foreach(SCRIPT_MODULE ${SCRIPT_MODULE_LIST}) set_property(CACHE ${SCRIPT_MODULE_VARIABLE} PROPERTY STRINGS default disabled static dynamic) endforeach() +option(WITH_CAIO_EXAMPLES "Build optional CAIO ExampleWindow test script" 0) option(TOOLS "Build map/vmap/mmap extraction/assembler tools" 1) option(USE_SCRIPTPCH "Use precompiled headers when compiling scripts" 1) option(USE_COREPCH "Use precompiled headers when compiling servers" 1) diff --git a/cmake/showoptions.cmake b/cmake/showoptions.cmake index 242b2500cdefc..9206f803f0d79 100644 --- a/cmake/showoptions.cmake +++ b/cmake/showoptions.cmake @@ -34,6 +34,12 @@ else() message("* Build with scripts : No") endif() +if(WITH_CAIO_EXAMPLES) + message("* CAIO ExampleWindow : Yes") +else() + message("* CAIO ExampleWindow : No (default)") +endif() + if(TOOLS) message("* Build map/vmap tools : Yes (default)") else() diff --git a/dep/smallfolk_cpp/smallfolk_cpp b/dep/smallfolk_cpp/smallfolk_cpp index bf45ea83aa806..cfa8c64790ed7 160000 --- a/dep/smallfolk_cpp/smallfolk_cpp +++ b/dep/smallfolk_cpp/smallfolk_cpp @@ -1 +1 @@ -Subproject commit bf45ea83aa80621163c354ab6a3757271bda7b53 +Subproject commit cfa8c64790ed74e52b79a5de4574ec142b0ae0da diff --git a/doc/CAIO_MESSAGE_FORMAT.md b/doc/CAIO_MESSAGE_FORMAT.md index 743ba063c6bc7..07b6f39f85a9a 100644 --- a/doc/CAIO_MESSAGE_FORMAT.md +++ b/doc/CAIO_MESSAGE_FORMAT.md @@ -23,8 +23,13 @@ Use `AIOMsg` / `AIO.Msg():Add(...)` rather than building tables manually. - Prefix: `S` + `AIO.Prefix` + `\t` (server→client) or `C` + prefix + `\t` (client→server). - Short message: two bytes `\1\1` then the smallfolk string. -- Long message: `messageId` (2) + `parts` (2) + `partId` (2) + chunk; chunk size is `AIO.MsgMaxLen` in `worldserver.conf` (default **2560**, same as `AIO_MsgLen` in server `AIO.lua`). +- Long message: 2-byte message id + 2-byte part count + 2-byte part id + chunk. +- Each whisper packet must fit in **255 bytes** total (WoW addon limit). Configure `AIO.MsgMaxLen` (default **255**). Long payloads split using `chunkLen = MsgMaxLen - headerBytes` where `headerBytes = 1 + len(prefix) + 1 + 6`. ## Version handshake -On init, client sends `AIO` / `Init` with `AIO_VERSION`. Server must define the same `AIO_VERSION` in `World.h` (currently **1.75**). +On init, client sends `AIO` / `Init` with protocol version **1.75**. Server defines `AIO_VERSION` / `AIO_VERSION_STRING` in `World.h`. + +## Compression / obfuscation + +Not implemented on this branch. Addon files are sent with an `U` (uncompressed) prefix only. diff --git a/src/common/Logging/Log.cpp b/src/common/Logging/Log.cpp index 304cee0fbfa74..1cd98cc057755 100644 --- a/src/common/Logging/Log.cpp +++ b/src/common/Logging/Log.cpp @@ -224,6 +224,11 @@ void Log::OutCommandImpl(uint32 account, Trinity::FormatStringView messageFormat write(std::make_unique(LOG_LEVEL_INFO, "commands.gm", Trinity::StringVFormat(messageFormat, messageFormatArgs), Trinity::ToString(account))); } +void Log::OutAIOMessageImpl(uint32 account, LogLevel level, Trinity::FormatStringView messageFormat, Trinity::FormatArgs messageFormatArgs) +{ + write(std::make_unique(level, "AIO", Trinity::StringVFormat(messageFormat, messageFormatArgs), account ? Trinity::ToString(account) : "World")); +} + void Log::write(std::unique_ptr msg) const { Logger const* logger = GetLoggerByType(msg->type); diff --git a/src/common/Logging/Log.h b/src/common/Logging/Log.h index 15666b4b383f3..d6ee52f671186 100644 --- a/src/common/Logging/Log.h +++ b/src/common/Logging/Log.h @@ -92,9 +92,7 @@ class TC_COMMON_API Log if (!ShouldLog("AIO", level)) return; - write(std::make_unique(level, "AIO", - Trinity::StringFormat(fmt, std::forward(args)...), - account ? std::to_string(account) : "World")); + OutAIOMessageImpl(account, level, fmt, Trinity::MakeFormatArgs(args...)); } void OutCharDump(char const* str, uint32 account_id, uint64 guid, char const* name); @@ -124,6 +122,7 @@ class TC_COMMON_API Log void RegisterAppender(uint8 index, AppenderCreatorFn appenderCreateFn); void OutMessageImpl(std::string_view filter, LogLevel level, Trinity::FormatStringView messageFormat, Trinity::FormatArgs messageFormatArgs); void OutCommandImpl(uint32 account, Trinity::FormatStringView messageFormat, Trinity::FormatArgs messageFormatArgs); + void OutAIOMessageImpl(uint32 account, LogLevel level, Trinity::FormatStringView messageFormat, Trinity::FormatArgs messageFormatArgs); std::unordered_map appenderFactory; std::unordered_map> appenders; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index e1329df1e74b0..9edbe819a61a5 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -1249,10 +1249,10 @@ void Player::Update(uint32 p_time) TeleportTo(m_teleport_dest, m_teleport_options); //AIO Init cooldown - if(m_aioInitCd) + if (m_aioInitCd) { m_aioInitTimer += p_time; - if(m_aioInitTimer >= 5000) + if (m_aioInitTimer >= sWorld->getIntConfig(CONFIG_AIO_INIT_COOLDOWN)) { m_aioInitCd = false; m_aioInitTimer = 0; @@ -20585,80 +20585,56 @@ void Player::AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, const SendSimpleAIOMessage(msg.dumps()); } -void Player::SendSimpleAIOMessage(const std::string &message) +void Player::SendSimpleAIOMessage(std::string const& message) { - if(message.empty()) - { + if (message.empty()) return; - } - std::string aioPrefix = sWorld->GetAIOPrefix(); - size_t shortMsgLen = message.size() + aioPrefix.size() + 4; //+4 for S \t and 2 byte for message id - uint32 msgMaxLen = sWorld->getIntConfig(CONFIG_AIO_MSG_MAX_LEN); - //If its a short message - if(shortMsgLen <= msgMaxLen) + std::string const& aioPrefix = sWorld->GetAIOPrefix(); + uint32 const maxPacketLen = std::min(sWorld->getIntConfig(CONFIG_AIO_MSG_MAX_LEN), AIO_MAX_WHISPER_LENGTH); + uint32 const shortHeaderLen = 1 + uint32(aioPrefix.size()) + 1 + 2; // S + prefix + tab + short id + uint32 const longHeaderLen = 1 + uint32(aioPrefix.size()) + 1 + 6; // S + prefix + tab + long meta + + if (shortHeaderLen + message.size() <= maxPacketLen) { std::string fullmsg = "S" + aioPrefix + "\t\x1\x1" + message; - WorldPacket data(SMSG_MESSAGECHAT, fullmsg.size() + 30); - data << uint8(CHAT_MSG_WHISPER); - data << int32(LANG_ADDON); - data << uint64(GetGUID()); - data << uint32(0); - data << uint64(GetGUID()); - data << uint32(fullmsg.size() + 1); - data << fullmsg; - data << uint8(0); - GetSession()->SendPacket(&data); + WorldPackets::Chat::Chat packet; + packet.Initialize(CHAT_MSG_WHISPER, LANG_ADDON, this, this, fullmsg); + SendDirectMessage(packet.Write()); return; } - //If its a long message - uint16 parts = std::ceilf(float(shortMsgLen + 4) / float(msgMaxLen)); + uint32 const chunkLen = maxPacketLen > longHeaderLen ? maxPacketLen - longHeaderLen : 1; + uint16 const parts = uint16(std::ceil(float(message.size()) / float(chunkLen))); - //parts to string - uint16 high = std::floorf((float)parts / 254); - std::string partsStr(1, high + 1); - partsStr += parts - high * 254 + 1; + uint16 high = uint16(std::floor(float(parts) / 254.0f)); + std::string partsStr(1, char(high + 1)); + partsStr += char(parts - high * 254 + 1); - //messageid to string - high = std::floorf((float)m_messageIdIndex / 254); - std::string messageIdStr(1, high + 1); - messageIdStr += m_messageIdIndex - high * 254 + 1; + high = uint16(std::floor(float(m_messageIdIndex) / 254.0f)); + std::string messageIdStr(1, char(high + 1)); + messageIdStr += char(m_messageIdIndex - high * 254 + 1); - //Increase or renew messageIdIndex - if(m_messageIdIndex >= 64769) //2^16 - 767 - { + if (m_messageIdIndex >= 64769) // 2^16 - 767 m_messageIdIndex = 1; - } else - { ++m_messageIdIndex; - } - //Send in parts size_t cursor = 0; - for(uint16 partId = 1; partId <= parts; ++partId) + for (uint16 partId = 1; partId <= parts; ++partId) { - //partid to string - high = std::floorf((float)partId / 254); - std::string partIdStr(1, high + 1); - partIdStr += partId - high * 254 + 1; + high = uint16(std::floor(float(partId) / 254.0f)); + std::string partIdStr(1, char(high + 1)); + partIdStr += char(partId - high * 254 + 1); - //send std::string fullmsg = "S" + aioPrefix + "\t" + messageIdStr + partsStr + partIdStr; - fullmsg += message.substr(cursor, msgMaxLen); - WorldPacket data(SMSG_MESSAGECHAT, fullmsg.size() + 30); - data << uint8(CHAT_MSG_WHISPER); - data << int32(LANG_ADDON); - data << uint64(GetGUID()); - data << uint32(0); - data << uint64(GetGUID()); - data << uint32(fullmsg.size() + 1); - data << fullmsg; - data << uint8(0); - GetSession()->SendPacket(&data); - - cursor += msgMaxLen; + fullmsg += message.substr(cursor, chunkLen); + + WorldPackets::Chat::Chat packet; + packet.Initialize(CHAT_MSG_WHISPER, LANG_ADDON, this, this, fullmsg); + SendDirectMessage(packet.Write()); + + cursor += chunkLen; } } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index f919440f489a9..83f13b0c9c08d 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1071,8 +1071,8 @@ class TC_GAME_API Player : public Unit, public GridObject void Whisper(uint32 textId, Player* target, bool isBossWhisper = false) override; void WhisperAddon(std::string const& text, Player* receiver); - //Returns whether AIO client has been initialized bool AIOInitialized() const { return m_aioInitialized; } + void SetAIOInitialized(bool initialized) { m_aioInitialized = initialized; } // Sends an AIO message to the player // See: class AIOMsg diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index 3def7042dd95f..d4e05784b460d 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -322,7 +322,7 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms uint32 parts = (msg[delimPos + 3] - 1) * 254 + msg[delimPos + 4] - 1; if (parts < 2) { - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "HandleAddonMessagechatOpcode: Received AIO addon message with number of parts: %u (< 2). Message Id: %u, Sender: %s", parts, messageId, sender->GetName().c_str()); + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "HandleAddonMessagechatOpcode: Received AIO addon message with number of parts: %u (< 2). Sender: %s", parts, sender->GetName().c_str()); return; } @@ -334,6 +334,8 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms } uint32 partId = (msg[delimPos + 5] - 1) * 254 + msg[delimPos + 6] - 1; + std::string const partPayload = msg.substr(delimPos + 7); + uint32 const maxBufferSize = sWorld->getIntConfig(CONFIG_AIO_MAX_BUFFER_SIZE); AddonMessageBufferMap::iterator messagePartsItr = _addonMessageBuffer.find(messageId); if (messagePartsItr == _addonMessageBuffer.end()) @@ -342,7 +344,16 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms messagePartsItr->second = LongMessageBufferInfo(); messagePartsItr->second.Parts = parts; - messagePartsItr->second.Map[partId] = msg.substr(delimPos + 7); + messagePartsItr->second.Timer = 0; + messagePartsItr->second.BufferedBytes += uint32(partPayload.size()); + if (messagePartsItr->second.BufferedBytes > maxBufferSize) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "HandleAddonMessagechatOpcode: AIO reassembly buffer exceeded %u bytes. Message Id: %u, Sender: %s", maxBufferSize, messageId, sender->GetName().c_str()); + _addonMessageBuffer.erase(messagePartsItr); + return; + } + + messagePartsItr->second.Map[partId] = std::move(partPayload); if (messagePartsItr->second.Map.size() >= messagePartsItr->second.Parts) { diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index f3227da679ac4..21f2d2dcaa633 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -40,6 +40,9 @@ #include "Transport.h" #include "Vehicle.h" #include "Weather.h" +#include "World.h" + +#include #include "WorldPacket.h" #include "WorldSession.h" #include "smallfolk_cpp/smallfolk.h" @@ -2593,13 +2596,13 @@ void ScriptMgr::OnAddonMessage(Player *sender, const std::string &message) //Call handlers from all blocks in order for(size_t i = 1; i <= mainTable.tbl().size(); ++i) { - LuaVal &block = mainTable[i]; + LuaVal block = mainTable.get(static_cast(i)); if(!block.istable()) continue; - LuaVal &scriptKeyVal = block[2]; - LuaVal &handlerKeyVal = block[3]; - if(!block[1].isnumber() || scriptKeyVal.isnil() || handlerKeyVal.isnil()) + LuaVal scriptKeyVal = block.get(2); + LuaVal handlerKeyVal = block.get(3); + if(!block.get(1).isnumber() || scriptKeyVal.isnil() || handlerKeyVal.isnil()) continue; if(AIOScript *aioScript = _aioHandlers->GetScript(scriptKeyVal)) @@ -2615,7 +2618,7 @@ AIOScript::AIOScript(const LuaVal &scriptKey) sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '%s' of type tag '%i' already exist. Use another key.", scriptKey.tostring().c_str(), scriptKey.GetTypeTag()); ASSERT(false); } - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); AIOScript::_scriptByKeyMap[scriptKey] = this; } @@ -2680,6 +2683,11 @@ ScriptClass *AIOScript::GetScript(const LuaVal &scriptKey) return dynamic_cast(itr->second); } +bool AIOScript::AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission) +{ + return sWorld->AddAddon(World::AIOAddon(addonName, addonFile, permission)); +} + void AIOScript::OnHandle(Player *sender, const LuaVal &handlerKey, const LuaVal &args) { HandlerMapType::const_iterator itr = _handlerMap.find(handlerKey); @@ -2703,15 +2711,15 @@ void AIOHandlers::HandleInit(Player *sender, const LuaVal &args) return; sender->setAIOIntOnCooldown(true); - LuaVal &versionVal = args[4]; - LuaVal &clientDataVal = args[5]; + LuaVal versionVal = args.get(4); + LuaVal clientDataVal = args.get(5); if(!versionVal.isnumber() || !clientDataVal.istable()) { sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIOHandlers::HandleInit: Invalid version value or clientData value. Sender: %s, Args: %s", sender->GetName().c_str(), args.dumps().c_str()); return; } - if(versionVal.num() != AIO_VERSION) + if (std::abs(versionVal.num() - AIO_VERSION) > 0.01) { sender->AIOHandle("AIO", "Init", AIO_VERSION); return; @@ -2756,49 +2764,18 @@ void AIOHandlers::HandleInit(Player *sender, const LuaVal &args) argsToSend[1] = AIOInitBlock; sender->SendSimpleAIOMessage(argsToSend.dumps()); - sender->m_aioInitialized = true; + sender->SetAIOInitialized(true); } void AIOHandlers::HandleError(Player *sender, const LuaVal &args) { - LuaVal &msgVal = args[4]; + LuaVal msgVal = args.get(4); if(!msgVal.isstring()) return; sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "%s Received client addon error: %s", sender->GetSession()->GetPlayerInfo().c_str(), msgVal.str().c_str()); } - -// Instantiate static members of ScriptRegistry. -template std::map ScriptRegistry::ScriptPointerList; -template uint32 ScriptRegistry::_scriptIdCounter = 0; - -// Specialize for each script type class like so: -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; -template class ScriptRegistry; void PlayerScript::OnPVPKill(Player* /*killer*/, Player* /*killed*/) { } diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 4f34d113444fc..e11b0b1721471 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -27,6 +27,8 @@ #include "smallfolk_cpp/smallfolk.h" +#include "World.h" + class AccountMgr; class AuctionHouseObject; class Aura; @@ -938,7 +940,7 @@ class AIOScript : public ScriptObject // It is required to call World::ForceReloadPlayerAddons() // if addons are added after server is fully initialized // for online players to load the added addons. - bool AddAddon(const World::AIOAddon &addon) { return sWorld->AddAddon(addon); } + bool AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); // Returns pointer to an AIO script by its key and typename. // Returns null if scriptName doesn't exist or typename was incorrect. diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index c58676921ea0a..5a7b39668b748 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -483,7 +483,7 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) itr != _addonMessageBuffer.end();) { itr->second.Timer += diff; - if(itr->second.Timer >= 30000) + if (itr->second.Timer >= sWorld->getIntConfig(CONFIG_AIO_BUFFER_TIMEOUT)) { _addonMessageBuffer.erase(itr++); } diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index b48cf634a3b17..b53bf459300fe 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -1353,13 +1353,10 @@ class TC_GAME_API WorldSession typedef std::map AddonPartStringMap; struct LongMessageBufferInfo { - uint32 Parts; - uint32 Timer; + uint32 Parts = 0; + uint32 Timer = 0; + uint32 BufferedBytes = 0; AddonPartStringMap Map; - - LongMessageBufferInfo() - : Parts(0), Timer(0) - { } }; typedef std::map AddonMessageBufferMap; AddonMessageBufferMap _addonMessageBuffer; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 3edcaf91e92c1..19d565321a014 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1567,11 +1567,12 @@ void World::LoadConfigSettings(bool reload) m_bool_configs[CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA] = sConfigMgr->GetBoolDefault("Calculate.Creature.Zone.Area.Data", false); m_bool_configs[CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA] = sConfigMgr->GetBoolDefault("Calculate.Gameoject.Zone.Area.Data", false); - // AIO + // AIO (255 = max WoW addon whisper length; must match client AIO_MsgLen when using client-side AIO) m_int_configs[CONFIG_AIO_MAXPARTS] = sConfigMgr->GetIntDefault("AIO.MaxParts", 4); - m_int_configs[CONFIG_AIO_MSG_MAX_LEN] = sConfigMgr->GetIntDefault("AIO.MsgMaxLen", 2560); - m_bool_configs[CONFIG_AIO_OBFUSCATE] = sConfigMgr->GetBoolDefault("AIO.Obfuscate", true); - m_bool_configs[CONFIG_AIO_COMPRESS] = sConfigMgr->GetBoolDefault("AIO.Compress", true); + m_int_configs[CONFIG_AIO_MSG_MAX_LEN] = std::min(sConfigMgr->GetIntDefault("AIO.MsgMaxLen", AIO_MAX_WHISPER_LENGTH), AIO_MAX_WHISPER_LENGTH); + m_int_configs[CONFIG_AIO_INIT_COOLDOWN] = sConfigMgr->GetIntDefault("AIO.InitCooldown", 5000); + m_int_configs[CONFIG_AIO_BUFFER_TIMEOUT] = sConfigMgr->GetIntDefault("AIO.BufferTimeout", 30000); + m_int_configs[CONFIG_AIO_MAX_BUFFER_SIZE] = sConfigMgr->GetIntDefault("AIO.MaxBufferSize", 1048576); m_aioclientpath = sConfigMgr->GetStringDefault("AIO.ClientScriptPath", "lua_client_scripts"); m_aioprefix = sConfigMgr->GetStringDefault("AIO.Prefix", "AIO"); if (m_aioprefix.size() > 15) @@ -3654,19 +3655,8 @@ bool World::AddAddon(AIOAddon const& addon) crc_result.process_bytes(copy.code.data(), copy.code.length()); copy.crc = crc_result.checksum(); - //Process code - char compressPrefix = 'U'; -// if(sWorld->getBoolConfig(CONFIG_AIO_OBFUSCATE)) -// { -// //Obf -// } -// if(sWorld->getBoolConfig(CONFIG_AIO_COMPRESS)) -// { -// compressPrefix = 'C'; -// } - - //Set final code and go - copy.code = std::string(1, compressPrefix) + copy.code; + // Uncompressed addon payload (compression/obfuscation not implemented) + copy.code = std::string(1, 'U') + copy.code; m_AddonList.push_back(copy); sLog->outAIOMessage(0, LOG_LEVEL_INFO, "AIO: Loaded addon %s from file %s", copy.name.c_str(), copy.file.c_str()); @@ -3721,6 +3711,9 @@ bool World::ReloadAddons() size_t World::PrepareClientAddons(const LuaVal &clientData, LuaVal &addonsTable, LuaVal &cacheTable, Player *forPlayer) const { + if (!clientData.istable()) + return 0; + uint32 i = 0; for(AddonCodeListType::const_iterator itr = m_AddonList.begin(); itr != m_AddonList.end(); @@ -3729,7 +3722,7 @@ size_t World::PrepareClientAddons(const LuaVal &clientData, LuaVal &addonsTable, if(!forPlayer->GetSession()->HasPermission(itr->permission)) continue; - LuaVal &CRCVal = clientData[itr->name]; + LuaVal CRCVal = clientData.get(itr->name); if(CRCVal == itr->crc) { cacheTable[++i] = itr->name; diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 52a22039081e1..83cfac7929bb9 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -38,6 +38,11 @@ #define AIO_VERSION 1.75 #define AIO_VERSION_STRING "1.75" +// RBAC permission id used when distributing addons/messages to players (see sql/CAIO/Auth.sql) +constexpr uint32 AIO_DEFAULT_ADDON_PERMISSION = 195; +// WoW addon whisper payload limit (matches client AIO.lua when AIO_SERVER is false) +constexpr uint32 AIO_MAX_WHISPER_LENGTH = 255; + class Object; class Player; class WorldPacket; @@ -169,8 +174,6 @@ enum WorldBoolConfigs : uint32 CONFIG_ALLOW_TRACK_BOTH_RESOURCES, CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA, CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA, - CONFIG_AIO_OBFUSCATE, - CONFIG_AIO_COMPRESS, CONFIG_RESET_DUEL_COOLDOWNS, CONFIG_RESET_DUEL_HEALTH_MANA, CONFIG_BASEMAP_LOAD_GRIDS, @@ -399,6 +402,9 @@ enum WorldIntConfigs : uint32 CONFIG_NO_GRAY_AGGRO_BELOW, CONFIG_AIO_MAXPARTS, CONFIG_AIO_MSG_MAX_LEN, + CONFIG_AIO_INIT_COOLDOWN, + CONFIG_AIO_BUFFER_TIMEOUT, + CONFIG_AIO_MAX_BUFFER_SIZE, CONFIG_AUCTION_GETALL_DELAY, CONFIG_AUCTION_SEARCH_DELAY, CONFIG_TALENTS_INSPECTING, @@ -789,17 +795,17 @@ class TC_GAME_API World uint32 crc; uint32 permission; - AIOAddon(std::string const& addonName, std::string const& addonFile, uint32 permission = 195) - : name(addonName), file(addonFile), permission(permission), crc(0) { } + AIOAddon(std::string const& addonName, std::string const& addonFile, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION) + : name(addonName), file(addonFile), crc(0), permission(permission) { } }; std::string GetAIOPrefix() const { return m_aioprefix; } std::string GetAIOClientScriptPath() const { return m_aioclientpath; } - void ForceReloadPlayerAddons(uint32 permission = 195); - void ForceResetPlayerAddons(uint32 permission = 195); - void AIOMessageAll(AIOMsg& msg, uint32 permission = 195); - void SendAllSimpleAIOMessage(std::string const& message, uint32 permission = 195); + void ForceReloadPlayerAddons(uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); + void ForceResetPlayerAddons(uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); + void AIOMessageAll(AIOMsg& msg, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); + void SendAllSimpleAIOMessage(std::string const& message, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); bool ReloadAddons(); bool AddAddon(AIOAddon const& addon); uint32 RemoveAddon(std::string const& addonName); diff --git a/src/server/scripts/AIO/ExampleWindow.cpp b/src/server/scripts/AIO/ExampleWindow.cpp index 50e8afbf331eb..72d9645ecfe77 100644 --- a/src/server/scripts/AIO/ExampleWindow.cpp +++ b/src/server/scripts/AIO/ExampleWindow.cpp @@ -22,9 +22,9 @@ class ExampleWindowScript : public AIOScript private: void HandlePrint(Player* sender, const LuaVal& args) { - LuaVal& btn = args[4]; - LuaVal& inp = args[5]; - LuaVal& val = args[6]; + LuaVal btn = args.get(4); + LuaVal inp = args.get(5); + LuaVal val = args.get(6); if (!btn.isstring() || !inp.isstring() || !val.isnumber()) return; @@ -33,8 +33,13 @@ class ExampleWindowScript : public AIOScript try { - int size = std::stoi(inp.str()); - std::string payload(size, 'b'); + long size = std::stol(inp.str()); + if (size < 0 || size > 8192) + { + ChatHandler(sender->GetSession()).SendSysMessage("ExampleWindow: payload size must be between 0 and 8192."); + return; + } + std::string payload(size_t(size), 'b'); sender->AIOHandle("AIOExample", "bullshit", payload); } catch (...) @@ -45,7 +50,7 @@ class ExampleWindowScript : public AIOScript void HandleBullshit(Player* sender, const LuaVal& args) { - LuaVal& payload = args[4]; + LuaVal payload = args.get(4); if (!payload.isstring()) return; diff --git a/src/server/scripts/AIO/aio_script_loader.cpp b/src/server/scripts/AIO/aio_script_loader.cpp index 32b2c6a19f053..b2ae745ac43f4 100644 --- a/src/server/scripts/AIO/aio_script_loader.cpp +++ b/src/server/scripts/AIO/aio_script_loader.cpp @@ -15,11 +15,15 @@ * with this program. If not, see . */ +#ifdef WITH_CAIO_EXAMPLES void AddSC_ExampleWindow(); +#endif // The name of this function should match: // void Add${NameOfDirectory}Scripts() void AddAIOScripts() { +#ifdef WITH_CAIO_EXAMPLES AddSC_ExampleWindow(); +#endif } diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt index fb4ba169b4ce5..09a16aa927097 100644 --- a/src/server/scripts/CMakeLists.txt +++ b/src/server/scripts/CMakeLists.txt @@ -203,6 +203,10 @@ set(WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES ${DYNAMIC_SCRIPT_MODULE_PROJ ConfigureScriptLoader("static" SCRIPT_MODULE_PRIVATE_SCRIPTLOADER OFF ${STATIC_SCRIPT_MODULES}) +if(NOT WITH_CAIO_EXAMPLES) + list(FILTER PRIVATE_SOURCES EXCLUDE REGEX "[/\\\\]AIO[/\\\\]ExampleWindow\\.cpp$") +endif() + add_library(scripts STATIC ScriptLoader.h ${SCRIPT_MODULE_PRIVATE_SCRIPTLOADER} @@ -218,6 +222,10 @@ target_include_directories(scripts PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +if(WITH_CAIO_EXAMPLES) + target_compile_definitions(scripts PRIVATE WITH_CAIO_EXAMPLES) +endif() + set_target_properties(scripts PROPERTIES COMPILE_WARNING_AS_ERROR ${WITH_WARNINGS_AS_ERRORS} diff --git a/src/server/scripts/Commands/cs_caio.cpp b/src/server/scripts/Commands/cs_caio.cpp index 4d55e9ba628aa..88f1e1aa2c365 100644 --- a/src/server/scripts/Commands/cs_caio.cpp +++ b/src/server/scripts/Commands/cs_caio.cpp @@ -5,241 +5,233 @@ Comment : All AIO related server side commands Category : commandscripts EndScriptData */ +#include "Chat.h" +#include "Language.h" #include "Player.h" +#include "RBAC.h" #include "ScriptMgr.h" #include "World.h" -#include "Language.h" +#include "smallfolk_cpp/smallfolk.h" class caio_commandscript : public CommandScript { - public: - caio_commandscript() : CommandScript("caio_commandscript") { } - - ChatCommand* GetCommands() const - { - static ChatCommand caioCommandTable[] = - { - { "version", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleVersionCommand, "", NULL }, - { "send", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleSendCommand, "", NULL }, - { "forcereload", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadCommand, "", NULL }, - { "forcereset", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleResetCommand, "", NULL }, - { "sendall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleSendAllCommand, "", NULL }, - { "forcereloadall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadAllCommand, "", NULL }, - { "forceresetall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleResetAllCommand, "", NULL }, - { "reloadaddons", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadAddonsCommand, "", NULL }, - { "addaddon", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleAddAddonCommand, "", NULL }, - { "removeaddon", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleRemoveAddonCommand, "", NULL }, - { NULL, 0, false, NULL, "", NULL } - }; - static ChatCommand commandTable[] = - { - { "caio", rbac::RBAC_PERM_COMMAND_CAIO, true, 0, "", caioCommandTable }, - { NULL, 0, false, NULL, "", NULL } - }; - return commandTable; - } - - static bool HandleVersionCommand(ChatHandler* handler, char const* /*args*/) - { - handler->PSendSysMessage("AIO version %s (protocol %.2f).", AIO_VERSION_STRING, AIO_VERSION); - return true; - } - - static bool HandleSendCommand(ChatHandler* handler, char const* args) - { - //Player name - Player* target; - if(!handler->extractPlayerTarget((char*)args, &target)) - return false; - - //Quoted message - char* tailStr = strtok(NULL, ""); - if(!tailStr) - return false; - - char* msg = handler->extractQuotedArg(tailStr); - if(!msg) - return false; - - target->SendSimpleAIOMessage(msg); - handler->PSendSysMessage(LANG_SENDMESSAGE, target->GetName().c_str(), msg); - return true; - }; - - static bool HandleReloadCommand(ChatHandler* handler, char const* args) - { - Player *target = 0; - if(!handler->extractPlayerTarget((char*)args, &target, 0, 0)) - return false; - - target->ForceReloadAddons(); - handler->PSendSysMessage(LANG_CAIO_FORCERELOAD_SENT, target->GetName().c_str()); - return true; - }; - - static bool HandleResetCommand(ChatHandler* handler, char const* args) - { - Player *target = 0; - if(!handler->extractPlayerTarget((char*)args, &target, 0, 0)) - return false; - - target->ForceResetAddons(); - handler->PSendSysMessage(LANG_CAIO_FORCERESET_SENT, target->GetName().c_str()); - return true; - }; - - static bool HandleSendAllCommand(ChatHandler* handler, char const* args) - { - if(!*args) - return false; - - //Get message - char *msg = handler->extractQuotedArg((char*)args); - if(!msg) - return false; - - char *permission = strtok(NULL, ""); - uint32 perm = 195; - if(permission) //Get permission if its there - { - try - { - perm = std::stoi(permission); - } - catch(std::exception &) - { - return false; - } - } - - sWorld->SendAllSimpleAIOMessage(msg, perm); - handler->PSendSysMessage(LANG_SENDMESSAGE, "all players", msg); - return true; - }; - - static bool HandleReloadAllCommand(ChatHandler* handler, char const* args) - { - uint32 perm = 195; - if(args && *args) //Get permission if its there - { - try - { - perm = std::stoi(args); - } - catch(std::exception &) - { - return false; - } - } - - //Force reload required players - sWorld->ForceReloadPlayerAddons(perm); - handler->PSendSysMessage(LANG_CAIO_FORCERELOAD_SENT, "all players"); - return true; - }; - - static bool HandleResetAllCommand(ChatHandler* handler, char const* args) - { - uint32 perm = 195; - if(args && *args) //Get permission if its there - { - try - { - perm = std::stoi(args); - } - catch(std::exception &) - { - return false; - } - } - - //Force reset required players - sWorld->ForceResetPlayerAddons(perm); - handler->PSendSysMessage(LANG_CAIO_FORCERESET_SENT, "all players"); - return true; - }; - - static bool HandleReloadAddonsCommand(ChatHandler* handler, char const* args) - { - - //Reload and force reload required players - bool success = sWorld->ReloadAddons(); - if(success) - { - sWorld->ForceReloadPlayerAddons(); - } - else - { - handler->SendSysMessage(LANG_CAIO_RELOADADDONS_ERROR); - } - return true; - } - - static bool HandleAddAddonCommand(ChatHandler* handler, char const* args) - { - if(!*args) - return false; - - //Addon name - char *addonName = strtok((char*)args, " "); - if(!addonName || addonName[0] == '"') - return false; - - //File - char *tailStr = strtok(NULL, ""); - char *addonFile = handler->extractQuotedArg(tailStr); - if(!addonFile) - return false; - - //Permission - char *permission = strtok(NULL, ""); - uint32 perm = 195; - if(permission) - { - try - { - perm = std::stoi(permission); - } - catch(std::exception &) - { - return false; - } - } - - //Add - World::AIOAddon newAddon(addonName, addonFile, perm); - bool added = sWorld->AddAddon(newAddon); - if(added) - { - sWorld->ForceReloadPlayerAddons(perm); - } - else - { - handler->PSendSysMessage(LANG_CAIO_ADDADDON_ERROR, addonName); - } - return true; - } - - static bool HandleRemoveAddonCommand(ChatHandler* handler, char const* args) - { - if(!*args) - return false; - - //Remove and reload required players - uint32 perm = sWorld->RemoveAddon(args); - if(perm != 0) - { - sWorld->ForceReloadPlayerAddons(perm); - } - else - { - handler->PSendSysMessage(LANG_CAIO_REMOVEADDON_ERROR, args); - } - return true; - } +public: + caio_commandscript() : CommandScript("caio_commandscript") { } + + std::vector GetCommands() const override + { + static std::vector caioCommandTable = + { + { "version", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleVersionCommand, "" }, + { "send", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleSendCommand, "" }, + { "forcereload", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadCommand, "" }, + { "forcereset", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleResetCommand, "" }, + { "sendall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleSendAllCommand, "" }, + { "forcereloadall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadAllCommand, "" }, + { "forceresetall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleResetAllCommand, "" }, + { "reloadaddons", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadAddonsCommand, "" }, + { "addaddon", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleAddAddonCommand, "" }, + { "removeaddon", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleRemoveAddonCommand, "" }, + }; + + static std::vector commandTable = + { + { "caio", rbac::RBAC_PERM_COMMAND_CAIO, true, nullptr, "", caioCommandTable }, + }; + + return commandTable; + } + + static bool HandleVersionCommand(ChatHandler* handler, char const* /*args*/) + { + handler->PSendSysMessage("AIO version %s (protocol %.2f).", AIO_VERSION_STRING, AIO_VERSION); + return true; + } + + static bool HandleSendCommand(ChatHandler* handler, char const* args) + { + Player* target = nullptr; + if (!handler->extractPlayerTarget((char*)args, &target)) + return false; + + char* tailStr = strtok(nullptr, ""); + if (!tailStr) + return false; + + char* msg = handler->extractQuotedArg(tailStr); + if (!msg) + return false; + + if (!LuaVal::loads(msg).istable()) + { + handler->SendSysMessage("CAIO: message must be smallfolk-serialized table data (use AIOMsg or AIO.Msg on the server)."); + return false; + } + + target->SendSimpleAIOMessage(msg); + handler->PSendSysMessage(LANG_SENDMESSAGE, target->GetName().c_str(), msg); + return true; + } + + static bool HandleReloadCommand(ChatHandler* handler, char const* args) + { + Player* target = nullptr; + if (!handler->extractPlayerTarget((char*)args, &target, nullptr, nullptr)) + return false; + + target->ForceReloadAddons(); + handler->PSendSysMessage(LANG_CAIO_FORCERELOAD_SENT, target->GetName().c_str()); + return true; + } + + static bool HandleResetCommand(ChatHandler* handler, char const* args) + { + Player* target = nullptr; + if (!handler->extractPlayerTarget((char*)args, &target, nullptr, nullptr)) + return false; + + target->ForceResetAddons(); + handler->PSendSysMessage(LANG_CAIO_FORCERESET_SENT, target->GetName().c_str()); + return true; + } + + static bool HandleSendAllCommand(ChatHandler* handler, char const* args) + { + if (!*args) + return false; + + char* msg = handler->extractQuotedArg((char*)args); + if (!msg) + return false; + + if (!LuaVal::loads(msg).istable()) + { + handler->SendSysMessage("CAIO: message must be smallfolk-serialized table data (use AIOMsg or AIO.Msg on the server)."); + return false; + } + + char* permission = strtok(nullptr, ""); + uint32 perm = AIO_DEFAULT_ADDON_PERMISSION; + if (permission) + { + try + { + perm = std::stoi(permission); + } + catch (std::exception&) + { + return false; + } + } + + sWorld->SendAllSimpleAIOMessage(msg, perm); + handler->PSendSysMessage(LANG_SENDMESSAGE, "all players", msg); + return true; + } + + static bool HandleReloadAllCommand(ChatHandler* handler, char const* args) + { + uint32 perm = AIO_DEFAULT_ADDON_PERMISSION; + if (args && *args) + { + try + { + perm = std::stoi(args); + } + catch (std::exception&) + { + return false; + } + } + + sWorld->ForceReloadPlayerAddons(perm); + handler->PSendSysMessage(LANG_CAIO_FORCERELOAD_SENT, "all players"); + return true; + } + + static bool HandleResetAllCommand(ChatHandler* handler, char const* args) + { + uint32 perm = AIO_DEFAULT_ADDON_PERMISSION; + if (args && *args) + { + try + { + perm = std::stoi(args); + } + catch (std::exception&) + { + return false; + } + } + + sWorld->ForceResetPlayerAddons(perm); + handler->PSendSysMessage(LANG_CAIO_FORCERESET_SENT, "all players"); + return true; + } + + static bool HandleReloadAddonsCommand(ChatHandler* handler, char const* /*args*/) + { + if (sWorld->ReloadAddons()) + sWorld->ForceReloadPlayerAddons(); + else + handler->SendSysMessage(LANG_CAIO_RELOADADDONS_ERROR); + + return true; + } + + static bool HandleAddAddonCommand(ChatHandler* handler, char const* args) + { + if (!*args) + return false; + + char* addonName = strtok((char*)args, " "); + if (!addonName || addonName[0] == '"') + return false; + + char* tailStr = strtok(nullptr, ""); + char* addonFile = handler->extractQuotedArg(tailStr); + if (!addonFile) + return false; + + char* permission = strtok(nullptr, ""); + uint32 perm = AIO_DEFAULT_ADDON_PERMISSION; + if (permission) + { + try + { + perm = std::stoi(permission); + } + catch (std::exception&) + { + return false; + } + } + + World::AIOAddon newAddon(addonName, addonFile, perm); + if (sWorld->AddAddon(newAddon)) + sWorld->ForceReloadPlayerAddons(perm); + else + handler->PSendSysMessage(LANG_CAIO_ADDADDON_ERROR, addonName); + + return true; + } + + static bool HandleRemoveAddonCommand(ChatHandler* handler, char const* args) + { + if (!*args) + return false; + + uint32 perm = sWorld->RemoveAddon(args); + if (perm != 0) + sWorld->ForceReloadPlayerAddons(perm); + else + handler->PSendSysMessage(LANG_CAIO_REMOVEADDON_ERROR, args); + + return true; + } }; void AddSC_caio_commandscript() { - new caio_commandscript(); -} \ No newline at end of file + new caio_commandscript(); +} diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 76dbdb2755fa1..a4ce2176f000a 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -4111,9 +4111,21 @@ PacketSpoof.BanDuration = 86400 # Default: 4 # # AIO.MsgMaxLen -# Maximum payload size (bytes) for a single server->client AIO addon message part -# Should match AIO_MsgLen in your AIO.lua (default 2560 on server in AIO 1.75) -# Default: 2560 +# Maximum size (bytes) of a single addon whisper packet (entire payload including AIO headers). +# Must be 255 or less to match the WoW client addon message limit used by AIO.lua. +# Default: 255 +# +# AIO.InitCooldown +# Milliseconds before a player can call AIO Init again after a successful init. +# Default: 5000 +# +# AIO.BufferTimeout +# Milliseconds before incomplete incoming long AIO messages are discarded. +# Default: 30000 +# +# AIO.MaxBufferSize +# Maximum total bytes buffered per long AIO message while reassembling parts. +# Default: 1048576 # # AIO.ClientScriptPath # Path to client lua scripts to send to the client @@ -4122,10 +4134,11 @@ PacketSpoof.BanDuration = 86400 AIO.Prefix = "AIO" AIO.MaxParts = 4 -AIO.MsgMaxLen = 2560 +AIO.MsgMaxLen = 255 +AIO.InitCooldown = 5000 +AIO.BufferTimeout = 30000 +AIO.MaxBufferSize = 1048576 AIO.ClientScriptPath = "lua_client_scripts" -AIO.Obfuscate = 1 -AIO.Compress = 1 # ################################################################################################### From b2a5f5781aff60456e7628370dcc8f761f5c8fcd Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Sun, 31 May 2026 21:19:14 +0300 Subject: [PATCH 08/33] Fix GCC CI, AIO reassembly, and init/removeaddon bugs. - Remove invalid in-class template specialization (GCC -Werror). - CI: checkout submodules recursively; default MaxParts 64. - Validate long-message parts; ordered concat; init cooldown after validation. - World::RemoveAddon returns bool; log missing AIO handlers. --- .github/workflows/gcc-build.yml | 4 ++- CAIO_README.md | 2 +- src/server/game/Handlers/ChatHandler.cpp | 26 +++++++++++++++++--- src/server/game/Scripting/ScriptMgr.cpp | 19 ++++++-------- src/server/game/Scripting/ScriptMgr.h | 3 --- src/server/game/World/World.cpp | 18 +++++++------- src/server/game/World/World.h | 2 +- src/server/scripts/Commands/cs_caio.cpp | 4 +-- src/server/worldserver/worldserver.conf.dist | 4 +-- 9 files changed, 48 insertions(+), 34 deletions(-) diff --git a/.github/workflows/gcc-build.yml b/.github/workflows/gcc-build.yml index 1f6ade7000282..b36ca72961621 100644 --- a/.github/workflows/gcc-build.yml +++ b/.github/workflows/gcc-build.yml @@ -8,7 +8,9 @@ jobs: build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + with: + submodules: recursive - name: Dependencies run: | sudo apt-get update && sudo apt-get install -yq libboost-all-dev g++-11 diff --git a/CAIO_README.md b/CAIO_README.md index ff07b76e85341..428b6e41b92e5 100644 --- a/CAIO_README.md +++ b/CAIO_README.md @@ -305,7 +305,7 @@ bool World::AddAddon(const AIOAddon &addon); // It is required to call World::ForceReloadPlayerAddons() // if addons are added after server is fully initialized // for online players to load the added addons. -uint32 World::RemoveAddon(const std::string &addonName); +bool World::RemoveAddon(std::string const& addonName, uint32* permission = nullptr); ``` ## CAIO game commands diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index d4e05784b460d..946492f70dad1 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -334,6 +334,12 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms } uint32 partId = (msg[delimPos + 5] - 1) * 254 + msg[delimPos + 6] - 1; + if (partId < 1 || partId > parts) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "HandleAddonMessagechatOpcode: Invalid AIO part id %u for %u parts. Message Id: %u, Sender: %s", partId, parts, messageId, sender->GetName().c_str()); + return; + } + std::string const partPayload = msg.substr(delimPos + 7); uint32 const maxBufferSize = sWorld->getIntConfig(CONFIG_AIO_MAX_BUFFER_SIZE); @@ -355,11 +361,25 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms messagePartsItr->second.Map[partId] = std::move(partPayload); - if (messagePartsItr->second.Map.size() >= messagePartsItr->second.Parts) + bool haveAllParts = messagePartsItr->second.Map.size() >= messagePartsItr->second.Parts; + if (haveAllParts) + { + for (uint32 expectedPart = 1; expectedPart <= parts; ++expectedPart) + { + if (messagePartsItr->second.Map.find(expectedPart) == messagePartsItr->second.Map.end()) + { + haveAllParts = false; + break; + } + } + } + + if (haveAllParts) { std::string actualAIOMessage; - for (AddonPartStringMap::const_iterator itr = messagePartsItr->second.Map.begin(); itr != messagePartsItr->second.Map.end(); ++itr) - actualAIOMessage += itr->second; + actualAIOMessage.reserve(messagePartsItr->second.BufferedBytes); + for (uint32 expectedPart = 1; expectedPart <= parts; ++expectedPart) + actualAIOMessage += messagePartsItr->second.Map.find(expectedPart)->second; sScriptMgr->OnAddonMessage(sender, actualAIOMessage); _addonMessageBuffer.erase(messagePartsItr); diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 21f2d2dcaa633..3ac415738420b 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -2605,7 +2605,7 @@ void ScriptMgr::OnAddonMessage(Player *sender, const std::string &message) if(!block.get(1).isnumber() || scriptKeyVal.isnil() || handlerKeyVal.isnil()) continue; - if(AIOScript *aioScript = _aioHandlers->GetScript(scriptKeyVal)) + if (AIOScript* aioScript = _aioHandlers->GetScript(scriptKeyVal)) aioScript->OnHandle(sender, handlerKeyVal, block); } } @@ -2663,16 +2663,6 @@ void AIOScript::AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, A list->push_back(a6); } -template<> -AIOScript *AIOScript::GetScript(const LuaVal &scriptKey) -{ - AIOScriptByKeyMap::const_iterator itr = AIOScript::_scriptByKeyMap.find(scriptKey); - if(itr == AIOScript::_scriptByKeyMap.end()) - return 0; - - return itr->second; -} - template ScriptClass *AIOScript::GetScript(const LuaVal &scriptKey) { @@ -2694,7 +2684,11 @@ void AIOScript::OnHandle(Player *sender, const LuaVal &handlerKey, const LuaVal if(itr != _handlerMap.end()) { itr->second(sender, args); //Call the handler function + return; } + + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIO: No handler '%s' on script '%s'. Sender: %s", + handlerKey.tostring().c_str(), _key.tostring().c_str(), sender->GetName().c_str()); } AIOHandlers::AIOHandlers() @@ -2710,7 +2704,6 @@ void AIOHandlers::HandleInit(Player *sender, const LuaVal &args) if(sender->isAIOInitOnCooldown()) return; - sender->setAIOIntOnCooldown(true); LuaVal versionVal = args.get(4); LuaVal clientDataVal = args.get(5); if(!versionVal.isnumber() || !clientDataVal.istable()) @@ -2725,6 +2718,8 @@ void AIOHandlers::HandleInit(Player *sender, const LuaVal &args) return; } + sender->setAIOIntOnCooldown(true); + LuaVal addonTable(TTABLE); LuaVal cacheTable(TTABLE); uint32 nAddons = sWorld->PrepareClientAddons(clientDataVal, addonTable, cacheTable, sender); diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index e11b0b1721471..90b556778fdc5 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -947,9 +947,6 @@ class AIOScript : public ScriptObject template ScriptClass *GetScript(const LuaVal &key); - template<> - AIOScript *GetScript(const LuaVal &key); - private: void OnHandle(Player *sender, const LuaVal &handlerKey, const LuaVal &args); diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 19d565321a014..64a34dba432d0 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -20,6 +20,7 @@ */ #include "World.h" +#include #include "AccountMgr.h" #include "AchievementMgr.h" #include "AddonMgr.h" @@ -1568,7 +1569,7 @@ void World::LoadConfigSettings(bool reload) m_bool_configs[CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA] = sConfigMgr->GetBoolDefault("Calculate.Gameoject.Zone.Area.Data", false); // AIO (255 = max WoW addon whisper length; must match client AIO_MsgLen when using client-side AIO) - m_int_configs[CONFIG_AIO_MAXPARTS] = sConfigMgr->GetIntDefault("AIO.MaxParts", 4); + m_int_configs[CONFIG_AIO_MAXPARTS] = sConfigMgr->GetIntDefault("AIO.MaxParts", 64); m_int_configs[CONFIG_AIO_MSG_MAX_LEN] = std::min(sConfigMgr->GetIntDefault("AIO.MsgMaxLen", AIO_MAX_WHISPER_LENGTH), AIO_MAX_WHISPER_LENGTH); m_int_configs[CONFIG_AIO_INIT_COOLDOWN] = sConfigMgr->GetIntDefault("AIO.InitCooldown", 5000); m_int_configs[CONFIG_AIO_BUFFER_TIMEOUT] = sConfigMgr->GetIntDefault("AIO.BufferTimeout", 30000); @@ -3663,20 +3664,19 @@ bool World::AddAddon(AIOAddon const& addon) return true; } -uint32 World::RemoveAddon(const std::string &addonName) +bool World::RemoveAddon(std::string const& addonName, uint32* permission) { - for(AddonCodeListType::iterator itr = m_AddonList.begin(); - itr != m_AddonList.end(); - ++itr) + for (AddonCodeListType::iterator itr = m_AddonList.begin(); itr != m_AddonList.end(); ++itr) { - if(itr->name == addonName) + if (itr->name == addonName) { - uint32 sec = itr->permission; + if (permission) + *permission = itr->permission; m_AddonList.erase(itr); - return sec; + return true; } } - return 0; + return false; } bool World::ReloadAddons() diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 83cfac7929bb9..ae5be8a5544ff 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -808,7 +808,7 @@ class TC_GAME_API World void SendAllSimpleAIOMessage(std::string const& message, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); bool ReloadAddons(); bool AddAddon(AIOAddon const& addon); - uint32 RemoveAddon(std::string const& addonName); + bool RemoveAddon(std::string const& addonName, uint32* permission = nullptr); size_t PrepareClientAddons(LuaVal const& clientData, LuaVal& addonsTable, LuaVal& cacheTable, Player* forPlayer) const; void RemoveOldCorpses(); diff --git a/src/server/scripts/Commands/cs_caio.cpp b/src/server/scripts/Commands/cs_caio.cpp index 88f1e1aa2c365..f9d73dea4f4f7 100644 --- a/src/server/scripts/Commands/cs_caio.cpp +++ b/src/server/scripts/Commands/cs_caio.cpp @@ -221,8 +221,8 @@ class caio_commandscript : public CommandScript if (!*args) return false; - uint32 perm = sWorld->RemoveAddon(args); - if (perm != 0) + uint32 perm = AIO_DEFAULT_ADDON_PERMISSION; + if (sWorld->RemoveAddon(args, &perm)) sWorld->ForceReloadPlayerAddons(perm); else handler->PSendSysMessage(LANG_CAIO_REMOVEADDON_ERROR, args); diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index a4ce2176f000a..e2fe46e0bb694 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -4108,7 +4108,7 @@ PacketSpoof.BanDuration = 86400 # # AIO.MaxParts # Maximum number of parts a client can send to server -# Default: 4 +# Default: 64 (large init payloads may need many 255-byte parts) # # AIO.MsgMaxLen # Maximum size (bytes) of a single addon whisper packet (entire payload including AIO headers). @@ -4133,7 +4133,7 @@ PacketSpoof.BanDuration = 86400 # AIO.Prefix = "AIO" -AIO.MaxParts = 4 +AIO.MaxParts = 64 AIO.MsgMaxLen = 255 AIO.InitCooldown = 5000 AIO.BufferTimeout = 30000 From d02d6c1bf3ea7003c0650c118326f8c6b2c28720 Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Mon, 1 Jun 2026 00:36:36 +0300 Subject: [PATCH 09/33] Update smallfolk_cpp submodule to master (v2.0.1). - Link smallfolk_cpp::smallfolk from dep/CMakeLists.txt; drop header-only include path. - Adapt CAIO to v2 API: smallfolk.h, LuaVal::nil, typetag(), len(), int get(). --- CAIO_README.md | 6 ++++-- dep/CMakeLists.txt | 5 +++++ dep/smallfolk_cpp/smallfolk_cpp | 2 +- src/server/game/CMakeLists.txt | 7 ++----- src/server/game/Chat/AIOMsg.h | 22 +++++++++++----------- src/server/game/Entities/Player/Player.h | 6 +++--- src/server/game/Scripting/ScriptMgr.cpp | 8 ++++---- src/server/game/Scripting/ScriptMgr.h | 2 +- src/server/game/World/World.cpp | 2 +- src/server/scripts/AIO/ExampleWindow.cpp | 2 +- src/server/scripts/Commands/cs_caio.cpp | 2 +- 11 files changed, 34 insertions(+), 30 deletions(-) diff --git a/CAIO_README.md b/CAIO_README.md index 428b6e41b92e5..f354f5207d857 100644 --- a/CAIO_README.md +++ b/CAIO_README.md @@ -12,7 +12,7 @@ AIO version **1.75** — must match `AIO_VERSION` in your server and client `AIO ## Install + Clone this repository/branch or merge with your own TrinityCore 3.3.5 branch -+ `git submodule update --init --recursive` (required for `dep/smallfolk_cpp`) ++ `git submodule update --init --recursive` (required for `dep/smallfolk_cpp/smallfolk_cpp`, tracks [smallfolk_cpp](https://github.com/Rochet2/smallfolk_cpp) **master**, currently v2.x) + Build/Install TrinityCore + [Install(Add) (C)AIO scripts](#api-reference) + Run SQL files from `TrinityCore_Installation_Dir/sql/CAIO` (`Auth.sql` on auth DB, `World.sql` on world DB) @@ -130,7 +130,9 @@ private: ### smallfolk_cpp LuaVal reference -https://github.com/Rochet2/smallfolk_cpp +https://github.com/Rochet2/smallfolk_cpp (v2.x on `master` — linked as CMake target `smallfolk_cpp::smallfolk`, include `smallfolk.h`) + +Use `LuaVal::nil` (not `LuaVal::nil()`) for default optional arguments. Type tag accessor is `typetag()`. ### CAIO reference and functions diff --git a/dep/CMakeLists.txt b/dep/CMakeLists.txt index 869f4ef298165..4d0d859c7783e 100644 --- a/dep/CMakeLists.txt +++ b/dep/CMakeLists.txt @@ -31,6 +31,11 @@ if(SERVERS) add_subdirectory(mysql) add_subdirectory(readline) add_subdirectory(gsoap) + set(SMALLFOLK_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set(SMALLFOLK_BUILD_BENCHMARK OFF CACHE BOOL "" FORCE) + set(SMALLFOLK_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(SMALLFOLK_ENABLE_CLANG_TIDY OFF CACHE BOOL "" FORCE) + add_subdirectory(smallfolk_cpp/smallfolk_cpp) endif() if(SERVERS AND BUILD_EFSW) diff --git a/dep/smallfolk_cpp/smallfolk_cpp b/dep/smallfolk_cpp/smallfolk_cpp index cfa8c64790ed7..750580db99847 160000 --- a/dep/smallfolk_cpp/smallfolk_cpp +++ b/dep/smallfolk_cpp/smallfolk_cpp @@ -1 +1 @@ -Subproject commit cfa8c64790ed74e52b79a5de4574ec142b0ae0da +Subproject commit 750580db99847f6043fdf952d572f0c40acc3e55 diff --git a/src/server/game/CMakeLists.txt b/src/server/game/CMakeLists.txt index 1f30e79647c9b..1918cf9a17d73 100644 --- a/src/server/game/CMakeLists.txt +++ b/src/server/game/CMakeLists.txt @@ -34,14 +34,11 @@ target_include_directories(game-interface INTERFACE ${PUBLIC_INCLUDES}) -target_include_directories(game-interface - INTERFACE - ${CMAKE_SOURCE_DIR}/dep/smallfolk_cpp) - target_link_libraries(game-interface INTERFACE shared - Detour) + Detour + smallfolk_cpp::smallfolk) add_library(game ${PRIVATE_SOURCES}) diff --git a/src/server/game/Chat/AIOMsg.h b/src/server/game/Chat/AIOMsg.h index ea203ecbaf6b3..658114f353f3b 100644 --- a/src/server/game/Chat/AIOMsg.h +++ b/src/server/game/Chat/AIOMsg.h @@ -1,7 +1,7 @@ #ifndef AIO_MESSAGE_H #define AIO_MESSAGE_H -#include "smallfolk_cpp/smallfolk.h" +#include "smallfolk.h" class Player; @@ -13,16 +13,16 @@ class AIOMsg //Creates a AIO message and adds one block AIOMsg(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()) + const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, + const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil) : _val(TTABLE) { Add(scriptKey, handlerKey, a1, a2, a3, a4, a5, a6); } AIOMsg(const char* scriptKey, const char* handlerKey, - const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()) + const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, + const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil) : _val(TTABLE) { Add(LuaVal(scriptKey), LuaVal(handlerKey), a1, a2, a3, a4, a5, a6); @@ -31,20 +31,20 @@ class AIOMsg //Adds another block //Another block will call another handler in one message AIOMsg &Add(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, + const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil); AIOMsg &Add(const char* scriptKey, const char* handlerKey, - const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()) + const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, + const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil) { return Add(LuaVal(scriptKey), LuaVal(handlerKey), a1, a2, a3, a4, a5, a6); } //Appends the last block //You can add additional arguments to the last block - AIOMsg &AppendLast(const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + AIOMsg &AppendLast(const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, + const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil); //Returns smallfolk dump of the AIO message std::string dumps() const { return _val.dumps(); } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 83f13b0c9c08d..1fa5f18717558 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -34,7 +34,7 @@ #include #include -#include "smallfolk_cpp/smallfolk.h" +#include "smallfolk.h" #include #include @@ -1082,8 +1082,8 @@ class TC_GAME_API Player : public Unit, public GridObject // To trigger multiple handlers in one message or to send more // arguments use Player::AIOMessage void AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, + const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil); // AIO can only understand smallfolk LuaVal::dumps() format // Handler functions are called by creating a table as below diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 3ac415738420b..0de308bfd5e0e 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -45,7 +45,7 @@ #include #include "WorldPacket.h" #include "WorldSession.h" -#include "smallfolk_cpp/smallfolk.h" +#include "smallfolk.h" // Trait which indicates whether this script type // must be assigned in the database. @@ -2594,9 +2594,9 @@ void ScriptMgr::OnAddonMessage(Player *sender, const std::string &message) return; //Call handlers from all blocks in order - for(size_t i = 1; i <= mainTable.tbl().size(); ++i) + for (size_t i = 1; i <= mainTable.len(); ++i) { - LuaVal block = mainTable.get(static_cast(i)); + LuaVal block = mainTable.get(static_cast(i)); if(!block.istable()) continue; @@ -2615,7 +2615,7 @@ AIOScript::AIOScript(const LuaVal &scriptKey) { if(AIOScript::_scriptByKeyMap.find(scriptKey) != AIOScript::_scriptByKeyMap.end()) { - sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '%s' of type tag '%i' already exist. Use another key.", scriptKey.tostring().c_str(), scriptKey.GetTypeTag()); + sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '%s' of type tag '%i' already exist. Use another key.", scriptKey.tostring().c_str(), scriptKey.typetag()); ASSERT(false); } ScriptRegistry::Instance()->AddScript(this); diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 90b556778fdc5..1841628f469ec 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -25,7 +25,7 @@ #include #include -#include "smallfolk_cpp/smallfolk.h" +#include "smallfolk.h" #include "World.h" diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 64a34dba432d0..b1b1a915fac7f 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -93,7 +93,7 @@ #include #include #include "AIOMsg.h" -#include "smallfolk_cpp/smallfolk.h" +#include "smallfolk.h" TC_GAME_API std::atomic World::m_stopEvent(false); TC_GAME_API uint8 World::m_ExitCode = SHUTDOWN_EXIT_CODE; diff --git a/src/server/scripts/AIO/ExampleWindow.cpp b/src/server/scripts/AIO/ExampleWindow.cpp index 72d9645ecfe77..e4a8959a0b9a4 100644 --- a/src/server/scripts/AIO/ExampleWindow.cpp +++ b/src/server/scripts/AIO/ExampleWindow.cpp @@ -1,7 +1,7 @@ #include "ScriptMgr.h" #include "Player.h" #include "Chat.h" -#include "smallfolk_cpp/smallfolk.h" +#include "smallfolk.h" // Optional CAIO test script (from Rochet2). Pairs with AIO Examples/TestWindow client addons. // Place ExampleWindow.lua (and related client files) under lua_client_scripts/ExampleWindow/ diff --git a/src/server/scripts/Commands/cs_caio.cpp b/src/server/scripts/Commands/cs_caio.cpp index f9d73dea4f4f7..b6b0f9a8ca24d 100644 --- a/src/server/scripts/Commands/cs_caio.cpp +++ b/src/server/scripts/Commands/cs_caio.cpp @@ -11,7 +11,7 @@ EndScriptData */ #include "RBAC.h" #include "ScriptMgr.h" #include "World.h" -#include "smallfolk_cpp/smallfolk.h" +#include "smallfolk.h" class caio_commandscript : public CommandScript { From b460ede22a8ab4f37ff55a245ca84e3df50f7eec Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Mon, 1 Jun 2026 03:05:00 +0300 Subject: [PATCH 10/33] Add AIO.MsgCacheTime and MsgCacheDelay for long-message reassembly. Match stock AIO.lua AIO_MSG_CACHE_TIME (15s) and AIO_MSG_CACHE_DELAY (5s). Replace the old single BufferTimeout sweep with periodic cache eviction on WorldSession. --- src/server/game/Server/WorldSession.cpp | 24 +++++++++++--------- src/server/game/Server/WorldSession.h | 1 + src/server/game/World/World.cpp | 4 +++- src/server/game/World/World.h | 3 ++- src/server/worldserver/worldserver.conf.dist | 13 ++++++++--- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 5a7b39668b748..cb467583da945 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -476,20 +476,22 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) return false; //Will remove this session from the world session map } - //AIO clear long message buffer - if(m_Socket && m_Socket->IsOpen()) + // AIO: drop incomplete long messages (AIO.lua AIO_MSG_CACHE_TIME / AIO_MSG_CACHE_DELAY) + if (m_Socket && m_Socket->IsOpen() && !_addonMessageBuffer.empty()) { - for(AddonMessageBufferMap::iterator itr = _addonMessageBuffer.begin(); - itr != _addonMessageBuffer.end();) + _aioMsgCacheSweepTimer += diff; + uint32 const sweepDelay = sWorld->getIntConfig(CONFIG_AIO_MSG_CACHE_DELAY); + if (_aioMsgCacheSweepTimer >= sweepDelay) { - itr->second.Timer += diff; - if (itr->second.Timer >= sWorld->getIntConfig(CONFIG_AIO_BUFFER_TIMEOUT)) + _aioMsgCacheSweepTimer = 0; + uint32 const cacheTime = sWorld->getIntConfig(CONFIG_AIO_MSG_CACHE_TIME); + for (AddonMessageBufferMap::iterator itr = _addonMessageBuffer.begin(); itr != _addonMessageBuffer.end();) { - _addonMessageBuffer.erase(itr++); - } - else - { - ++itr; + itr->second.Timer += sweepDelay; + if (itr->second.Timer >= cacheTime) + _addonMessageBuffer.erase(itr++); + else + ++itr; } } } diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index b53bf459300fe..9835dfb214514 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -1360,6 +1360,7 @@ class TC_GAME_API WorldSession }; typedef std::map AddonMessageBufferMap; AddonMessageBufferMap _addonMessageBuffer; + uint32 _aioMsgCacheSweepTimer = 0; }; #endif /// @} diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index b1b1a915fac7f..ec2aa8a3057c0 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1572,7 +1572,9 @@ void World::LoadConfigSettings(bool reload) m_int_configs[CONFIG_AIO_MAXPARTS] = sConfigMgr->GetIntDefault("AIO.MaxParts", 64); m_int_configs[CONFIG_AIO_MSG_MAX_LEN] = std::min(sConfigMgr->GetIntDefault("AIO.MsgMaxLen", AIO_MAX_WHISPER_LENGTH), AIO_MAX_WHISPER_LENGTH); m_int_configs[CONFIG_AIO_INIT_COOLDOWN] = sConfigMgr->GetIntDefault("AIO.InitCooldown", 5000); - m_int_configs[CONFIG_AIO_BUFFER_TIMEOUT] = sConfigMgr->GetIntDefault("AIO.BufferTimeout", 30000); + // Matches AIO.lua AIO_MSG_CACHE_TIME / AIO_MSG_CACHE_DELAY (server-side reassembly) + m_int_configs[CONFIG_AIO_MSG_CACHE_TIME] = sConfigMgr->GetIntDefault("AIO.MsgCacheTime", 15000); + m_int_configs[CONFIG_AIO_MSG_CACHE_DELAY] = sConfigMgr->GetIntDefault("AIO.MsgCacheDelay", 5000); m_int_configs[CONFIG_AIO_MAX_BUFFER_SIZE] = sConfigMgr->GetIntDefault("AIO.MaxBufferSize", 1048576); m_aioclientpath = sConfigMgr->GetStringDefault("AIO.ClientScriptPath", "lua_client_scripts"); m_aioprefix = sConfigMgr->GetStringDefault("AIO.Prefix", "AIO"); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index ae5be8a5544ff..bb1cce9d7d5e8 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -403,7 +403,8 @@ enum WorldIntConfigs : uint32 CONFIG_AIO_MAXPARTS, CONFIG_AIO_MSG_MAX_LEN, CONFIG_AIO_INIT_COOLDOWN, - CONFIG_AIO_BUFFER_TIMEOUT, + CONFIG_AIO_MSG_CACHE_TIME, + CONFIG_AIO_MSG_CACHE_DELAY, CONFIG_AIO_MAX_BUFFER_SIZE, CONFIG_AUCTION_GETALL_DELAY, CONFIG_AUCTION_SEARCH_DELAY, diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index e2fe46e0bb694..6b2f05e6b5669 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -4119,9 +4119,15 @@ PacketSpoof.BanDuration = 86400 # Milliseconds before a player can call AIO Init again after a successful init. # Default: 5000 # -# AIO.BufferTimeout +# AIO.MsgCacheTime # Milliseconds before incomplete incoming long AIO messages are discarded. -# Default: 30000 +# Matches AIO.lua AIO_MSG_CACHE_TIME on the stock server. +# Default: 15000 +# +# AIO.MsgCacheDelay +# Milliseconds between sweeps of incomplete long AIO message buffers. +# Matches AIO.lua AIO_MSG_CACHE_DELAY on the stock server. +# Default: 5000 # # AIO.MaxBufferSize # Maximum total bytes buffered per long AIO message while reassembling parts. @@ -4136,7 +4142,8 @@ AIO.Prefix = "AIO" AIO.MaxParts = 64 AIO.MsgMaxLen = 255 AIO.InitCooldown = 5000 -AIO.BufferTimeout = 30000 +AIO.MsgCacheTime = 15000 +AIO.MsgCacheDelay = 5000 AIO.MaxBufferSize = 1048576 AIO.ClientScriptPath = "lua_client_scripts" From c5cdb023dcaa1cb08a50eb4625a179ff1c49c162 Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Mon, 1 Jun 2026 03:05:07 +0300 Subject: [PATCH 11/33] Enforce AIO block argument limit and drop unused init flag. Reject incoming blocks with n > 15 like stock AIO_SERVER. Remove m_aioInitialized, which was never read (AIO_INITED is client-only in AIO.lua). --- src/server/game/Entities/Player/Player.cpp | 1 - src/server/game/Entities/Player/Player.h | 4 ---- src/server/game/Scripting/ScriptMgr.cpp | 13 ++++++++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 9edbe819a61a5..98c60644ce2b9 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -340,7 +340,6 @@ Player::Player(WorldSession* session): Unit(true) m_achievementMgr = new AchievementMgr(this); m_reputationMgr = new ReputationMgr(this); - m_aioInitialized = false; m_aioInitCd = false; m_aioInitTimer = 0; m_messageIdIndex = 1; diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 1fa5f18717558..61fd843672de5 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1071,9 +1071,6 @@ class TC_GAME_API Player : public Unit, public GridObject void Whisper(uint32 textId, Player* target, bool isBossWhisper = false) override; void WhisperAddon(std::string const& text, Player* receiver); - bool AIOInitialized() const { return m_aioInitialized; } - void SetAIOInitialized(bool initialized) { m_aioInitialized = initialized; } - // Sends an AIO message to the player // See: class AIOMsg void AIOMessage(AIOMsg &msg); @@ -2591,7 +2588,6 @@ class TC_GAME_API Player : public Unit, public GridObject uint32 _activeCheats; - bool m_aioInitialized; bool m_aioInitCd; uint32 m_aioInitTimer; uint16 m_messageIdIndex; diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 0de308bfd5e0e..282f77a6a61b9 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -2600,11 +2600,20 @@ void ScriptMgr::OnAddonMessage(Player *sender, const std::string &message) if(!block.istable()) continue; + LuaVal nArgsVal = block.get(1); LuaVal scriptKeyVal = block.get(2); LuaVal handlerKeyVal = block.get(3); - if(!block.get(1).isnumber() || scriptKeyVal.isnil() || handlerKeyVal.isnil()) + if (!nArgsVal.isnumber() || scriptKeyVal.isnil() || handlerKeyVal.isnil()) continue; + if (nArgsVal.num() > 15.0) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "AIO: Block from '%s' has over 15 arguments (n=%.0f). Sender: %s", + scriptKeyVal.tostring().c_str(), nArgsVal.num(), sender->GetName().c_str()); + continue; + } + if (AIOScript* aioScript = _aioHandlers->GetScript(scriptKeyVal)) aioScript->OnHandle(sender, handlerKeyVal, block); } @@ -2758,8 +2767,6 @@ void AIOHandlers::HandleInit(Player *sender, const LuaVal &args) argsToSend[1] = AIOInitBlock; sender->SendSimpleAIOMessage(argsToSend.dumps()); - - sender->SetAIOInitialized(true); } void AIOHandlers::HandleError(Player *sender, const LuaVal &args) From d49cbe49ba843581305bbcd12a5a62b57281cf4f Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Mon, 1 Jun 2026 03:05:09 +0300 Subject: [PATCH 12/33] Document stock AIO server parity and future AddOnInit work. Clarify LANG_ADDON whisper transport, message cache settings, and block limits. Add AddOnInit vs AddInitArgs as an explicit future todo. --- CAIO_README.md | 13 +++++++++---- doc/CAIO_MESSAGE_FORMAT.md | 4 +++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CAIO_README.md b/CAIO_README.md index f354f5207d857..fbae57004e3d1 100644 --- a/CAIO_README.md +++ b/CAIO_README.md @@ -37,9 +37,17 @@ cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DSCRIPTS=static -DTOOLS=0 -DBoost_DIR="C:/local/boost_1_81_0/lib64-msvc-14.3/cmake/Boost-1.81.0" ``` +## Stock AIO server parity (C++ vs `AIO.lua` with `AIO_SERVER = true`) + ++ **Transport:** `LANG_ADDON` whispers with `S`/`C` prefix are required on 3.3.5; the client receives them as `CHAT_MSG_ADDON` (same as stock Eluna server). Not a CAIO gap. ++ **Init hooks:** C++ `AddInitArgs` appends extra handler blocks to the init reply. For full init-message rewriting (stock `AIO.AddOnInit`), see Todo below. ++ **Pre-init gating:** Stock server does **not** queue pre-init blocks (`AIO_INITED` is client-only). CAIO matches that. ++ **Block arg limit:** Server rejects blocks with `n > 15` (same as stock server Lua). ++ **Message cache:** `AIO.MsgCacheTime` / `AIO.MsgCacheDelay` match `AIO_MSG_CACHE_TIME` / `AIO_MSG_CACHE_DELAY` in `AIO.lua`. + ## Todo -+ Add CAIO error time-out configuration (distinct from `AIO.BufferTimeout` reassembly timeout) ++ **`AIO.AddOnInit` parity (future):** Stock Lua server (`AIO_SERVER = true`) allows `AIO.AddOnInit(func)` where `func(initmsg, player)` receives the full outgoing init message and may replace or rewrite it before send. C++ only has `AddInitArgs`, which appends separate handler blocks and does not mutate the core `AIO`/`Init` block (version, addon table, cache table). A future C++ API would need an init-message hook with read/write access to the assembled init payload. + Implement obfuscation (optional, deferred) + Implement compression (optional, deferred) + Add individual RBAC permissions per `.caio` subcommand (optional; all subcommands use `RBAC_PERM_COMMAND_CAIO` today) @@ -215,9 +223,6 @@ public: class Player { public: - //Returns whether AIO client has been initialized - bool AIOInitialized() const; - // Sends an AIO message to the player // See: class AIOMsg void AIOMessage(AIOMsg &msg); diff --git a/doc/CAIO_MESSAGE_FORMAT.md b/doc/CAIO_MESSAGE_FORMAT.md index 07b6f39f85a9a..9935990b1490a 100644 --- a/doc/CAIO_MESSAGE_FORMAT.md +++ b/doc/CAIO_MESSAGE_FORMAT.md @@ -13,7 +13,7 @@ One whisper addon payload is a **smallfolk** dump of an array of blocks: } ``` -- `n` — number of arguments **including** `handlerKey` (CAIO sets `block[1]` accordingly). +- `n` — number of arguments **including** `handlerKey` (CAIO sets `block[1]` accordingly). Server rejects `n > 15` (stock `AIO_SERVER` limit). - `scriptKey` — block name (`AIO`, `AIOExample`, …); must be registered with `AIO.RegisterEvent` on the client or `AIOScript` on the server. - Arguments from index 4 onward are handler parameters (`unpack(data, 3, n+2)` on the Lua side). @@ -21,6 +21,8 @@ Use `AIOMsg` / `AIO.Msg():Add(...)` rather than building tables manually. ## Wire encoding (3.3.5) +On TrinityCore, AIO uses **`CHAT_MSG_WHISPER` with `LANG_ADDON`** (not a separate addon channel packet). The client still sees **`CHAT_MSG_ADDON`**; this matches stock `AIO.lua` server behaviour. + - Prefix: `S` + `AIO.Prefix` + `\t` (server→client) or `C` + prefix + `\t` (client→server). - Short message: two bytes `\1\1` then the smallfolk string. - Long message: 2-byte message id + 2-byte part count + 2-byte part id + chunk. From bfd1398f52723d7f58b8879b3579ee2180a52998 Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Mon, 1 Jun 2026 03:36:12 +0300 Subject: [PATCH 13/33] Style: normalize CAIO formatting to TrinityCore 3.3.5 Replace tabs with 4-space indentation and align comment and control-flow spacing with surrounding core code. --- src/server/game/Chat/AIOMsg.cpp | 158 +++++------ src/server/game/Chat/AIOMsg.h | 90 +++---- src/server/game/Entities/Player/Player.cpp | 130 ++++----- src/server/game/Entities/Player/Player.h | 60 ++--- src/server/game/Scripting/ScriptMgr.cpp | 284 ++++++++++---------- src/server/game/Scripting/ScriptMgr.h | 248 +++++++++--------- src/server/game/Server/WorldSession.cpp | 44 ++-- src/server/game/Server/WorldSession.h | 24 +- src/server/game/World/World.cpp | 290 ++++++++++----------- src/server/scripts/AIO/ExampleWindow.cpp | 108 ++++---- 10 files changed, 716 insertions(+), 720 deletions(-) diff --git a/src/server/game/Chat/AIOMsg.cpp b/src/server/game/Chat/AIOMsg.cpp index c620f0b34fd95..ac781485db278 100644 --- a/src/server/game/Chat/AIOMsg.cpp +++ b/src/server/game/Chat/AIOMsg.cpp @@ -2,97 +2,97 @@ #include "Player.h" AIOMsg::AIOMsg() - : _val(TTABLE) + : _val(TTABLE) { } AIOMsg &AIOMsg::Add(const LuaVal &scriptKey, const LuaVal &handlerKey, const LuaVal &a1, const LuaVal &a2, const LuaVal &a3, const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) { - LuaVal block(TTABLE); - uint32 nArgs = 1; + LuaVal block(TTABLE); + uint32 nArgs = 1; - block[1] = 0; - block[2] = scriptKey; - block[3] = handlerKey; + block[1] = 0; + block[2] = scriptKey; + block[3] = handlerKey; - if(!a1.isnil()) - { - block.insert(a1); - ++nArgs; - } - if(!a2.isnil()) - { - block.insert(a2); - ++nArgs; - } - if(!a3.isnil()) - { - block.insert(a3); - ++nArgs; - } - if(!a4.isnil()) - { - block.insert(a4); - ++nArgs; - } - if(!a5.isnil()) - { - block.insert(a5); - ++nArgs; - } - if(!a6.isnil()) - { - block.insert(a6); - ++nArgs; - } + if (!a1.isnil()) + { + block.insert(a1); + ++nArgs; + } + if (!a2.isnil()) + { + block.insert(a2); + ++nArgs; + } + if (!a3.isnil()) + { + block.insert(a3); + ++nArgs; + } + if (!a4.isnil()) + { + block.insert(a4); + ++nArgs; + } + if (!a5.isnil()) + { + block.insert(a5); + ++nArgs; + } + if (!a6.isnil()) + { + block.insert(a6); + ++nArgs; + } - block[1] = nArgs; - _val.insert(block); - return *this; + block[1] = nArgs; + _val.insert(block); + return *this; } AIOMsg &AIOMsg::AppendLast(const LuaVal &a1, const LuaVal &a2, const LuaVal &a3, const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) { - unsigned int lastBlock = _val.len(); - if(!lastBlock) - return *this; + unsigned int lastBlock = _val.len(); + if (!lastBlock) + return *this; - LuaVal &block = _val[lastBlock]; - LuaVal nArgsVal = block.get(1); - if(!nArgsVal.isnumber()) - return *this; + LuaVal &block = _val[lastBlock]; + LuaVal nArgsVal = block.get(1); + if (!nArgsVal.isnumber()) + return *this; - unsigned int nArgs = nArgsVal.num(); - if(!a1.isnil()) - { - block.insert(a1); - ++nArgs; - } - if(!a2.isnil()) - { - block.insert(a2); - ++nArgs; - } - if(!a3.isnil()) - { - block.insert(a3); - ++nArgs; - } - if(!a4.isnil()) - { - block.insert(a4); - ++nArgs; - } - if(!a5.isnil()) - { - block.insert(a5); - ++nArgs; - } - if(!a6.isnil()) - { - block.insert(a6); - ++nArgs; - } + unsigned int nArgs = nArgsVal.num(); + if (!a1.isnil()) + { + block.insert(a1); + ++nArgs; + } + if (!a2.isnil()) + { + block.insert(a2); + ++nArgs; + } + if (!a3.isnil()) + { + block.insert(a3); + ++nArgs; + } + if (!a4.isnil()) + { + block.insert(a4); + ++nArgs; + } + if (!a5.isnil()) + { + block.insert(a5); + ++nArgs; + } + if (!a6.isnil()) + { + block.insert(a6); + ++nArgs; + } - block[1] = nArgs; - return *this; + block[1] = nArgs; + return *this; } diff --git a/src/server/game/Chat/AIOMsg.h b/src/server/game/Chat/AIOMsg.h index 658114f353f3b..32d4f43e2ad09 100644 --- a/src/server/game/Chat/AIOMsg.h +++ b/src/server/game/Chat/AIOMsg.h @@ -7,51 +7,51 @@ class Player; class AIOMsg { - public: - //Creates an empty AIOMsg - AIOMsg(); - - //Creates a AIO message and adds one block - AIOMsg(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, - const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil) - : _val(TTABLE) - { - Add(scriptKey, handlerKey, a1, a2, a3, a4, a5, a6); - } - - AIOMsg(const char* scriptKey, const char* handlerKey, - const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, - const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil) - : _val(TTABLE) - { - Add(LuaVal(scriptKey), LuaVal(handlerKey), a1, a2, a3, a4, a5, a6); - } - - //Adds another block - //Another block will call another handler in one message - AIOMsg &Add(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, - const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil); - - AIOMsg &Add(const char* scriptKey, const char* handlerKey, - const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, - const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil) - { - return Add(LuaVal(scriptKey), LuaVal(handlerKey), a1, a2, a3, a4, a5, a6); - } - - //Appends the last block - //You can add additional arguments to the last block - AIOMsg &AppendLast(const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, - const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil); - - //Returns smallfolk dump of the AIO message - std::string dumps() const { return _val.dumps(); } - - private: - LuaVal _val; - friend class Player; + public: + //Creates an empty AIOMsg + AIOMsg(); + + //Creates a AIO message and adds one block + AIOMsg(const LuaVal &scriptKey, const LuaVal &handlerKey, + const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, + const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil) + : _val(TTABLE) + { + Add(scriptKey, handlerKey, a1, a2, a3, a4, a5, a6); + } + + AIOMsg(const char* scriptKey, const char* handlerKey, + const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, + const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil) + : _val(TTABLE) + { + Add(LuaVal(scriptKey), LuaVal(handlerKey), a1, a2, a3, a4, a5, a6); + } + + //Adds another block + //Another block will call another handler in one message + AIOMsg &Add(const LuaVal &scriptKey, const LuaVal &handlerKey, + const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, + const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil); + + AIOMsg &Add(const char* scriptKey, const char* handlerKey, + const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, + const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil) + { + return Add(LuaVal(scriptKey), LuaVal(handlerKey), a1, a2, a3, a4, a5, a6); + } + + //Appends the last block + //You can add additional arguments to the last block + AIOMsg &AppendLast(const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, + const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil); + + //Returns smallfolk dump of the AIO message + std::string dumps() const { return _val.dumps(); } + + private: + LuaVal _val; + friend class Player; }; #endif diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 98c60644ce2b9..5bf430e405667 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -340,9 +340,9 @@ Player::Player(WorldSession* session): Unit(true) m_achievementMgr = new AchievementMgr(this); m_reputationMgr = new ReputationMgr(this); - m_aioInitCd = false; - m_aioInitTimer = 0; - m_messageIdIndex = 1; + m_aioInitCd = false; + m_aioInitTimer = 0; + m_messageIdIndex = 1; } Player::~Player() @@ -1247,16 +1247,16 @@ void Player::Update(uint32 p_time) if (IsHasDelayedTeleport()) TeleportTo(m_teleport_dest, m_teleport_options); - //AIO Init cooldown - if (m_aioInitCd) - { - m_aioInitTimer += p_time; - if (m_aioInitTimer >= sWorld->getIntConfig(CONFIG_AIO_INIT_COOLDOWN)) - { - m_aioInitCd = false; - m_aioInitTimer = 0; - } - } + // AIO init cooldown + if (m_aioInitCd) + { + m_aioInitTimer += p_time; + if (m_aioInitTimer >= sWorld->getIntConfig(CONFIG_AIO_INIT_COOLDOWN)) + { + m_aioInitCd = false; + m_aioInitTimer = 0; + } + } } void Player::Heartbeat() @@ -20575,66 +20575,66 @@ void Player::Whisper(uint32 textId, Player* target, bool /*isBossWhisper*/) void Player::AIOMessage(AIOMsg &msg) { - SendSimpleAIOMessage(msg.dumps()); + SendSimpleAIOMessage(msg.dumps()); } void Player::AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, const LuaVal &a1, const LuaVal &a2, const LuaVal &a3, const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) { - AIOMsg msg(scriptKey, handlerKey, a1, a2, a3, a4, a5, a6); - SendSimpleAIOMessage(msg.dumps()); + AIOMsg msg(scriptKey, handlerKey, a1, a2, a3, a4, a5, a6); + SendSimpleAIOMessage(msg.dumps()); } void Player::SendSimpleAIOMessage(std::string const& message) { - if (message.empty()) - return; - - std::string const& aioPrefix = sWorld->GetAIOPrefix(); - uint32 const maxPacketLen = std::min(sWorld->getIntConfig(CONFIG_AIO_MSG_MAX_LEN), AIO_MAX_WHISPER_LENGTH); - uint32 const shortHeaderLen = 1 + uint32(aioPrefix.size()) + 1 + 2; // S + prefix + tab + short id - uint32 const longHeaderLen = 1 + uint32(aioPrefix.size()) + 1 + 6; // S + prefix + tab + long meta - - if (shortHeaderLen + message.size() <= maxPacketLen) - { - std::string fullmsg = "S" + aioPrefix + "\t\x1\x1" + message; - WorldPackets::Chat::Chat packet; - packet.Initialize(CHAT_MSG_WHISPER, LANG_ADDON, this, this, fullmsg); - SendDirectMessage(packet.Write()); - return; - } - - uint32 const chunkLen = maxPacketLen > longHeaderLen ? maxPacketLen - longHeaderLen : 1; - uint16 const parts = uint16(std::ceil(float(message.size()) / float(chunkLen))); - - uint16 high = uint16(std::floor(float(parts) / 254.0f)); - std::string partsStr(1, char(high + 1)); - partsStr += char(parts - high * 254 + 1); - - high = uint16(std::floor(float(m_messageIdIndex) / 254.0f)); - std::string messageIdStr(1, char(high + 1)); - messageIdStr += char(m_messageIdIndex - high * 254 + 1); - - if (m_messageIdIndex >= 64769) // 2^16 - 767 - m_messageIdIndex = 1; - else - ++m_messageIdIndex; - - size_t cursor = 0; - for (uint16 partId = 1; partId <= parts; ++partId) - { - high = uint16(std::floor(float(partId) / 254.0f)); - std::string partIdStr(1, char(high + 1)); - partIdStr += char(partId - high * 254 + 1); - - std::string fullmsg = "S" + aioPrefix + "\t" + messageIdStr + partsStr + partIdStr; - fullmsg += message.substr(cursor, chunkLen); - - WorldPackets::Chat::Chat packet; - packet.Initialize(CHAT_MSG_WHISPER, LANG_ADDON, this, this, fullmsg); - SendDirectMessage(packet.Write()); - - cursor += chunkLen; - } + if (message.empty()) + return; + + std::string const& aioPrefix = sWorld->GetAIOPrefix(); + uint32 const maxPacketLen = std::min(sWorld->getIntConfig(CONFIG_AIO_MSG_MAX_LEN), AIO_MAX_WHISPER_LENGTH); + uint32 const shortHeaderLen = 1 + uint32(aioPrefix.size()) + 1 + 2; // S + prefix + tab + short id + uint32 const longHeaderLen = 1 + uint32(aioPrefix.size()) + 1 + 6; // S + prefix + tab + long meta + + if (shortHeaderLen + message.size() <= maxPacketLen) + { + std::string fullmsg = "S" + aioPrefix + "\t\x1\x1" + message; + WorldPackets::Chat::Chat packet; + packet.Initialize(CHAT_MSG_WHISPER, LANG_ADDON, this, this, fullmsg); + SendDirectMessage(packet.Write()); + return; + } + + uint32 const chunkLen = maxPacketLen > longHeaderLen ? maxPacketLen - longHeaderLen : 1; + uint16 const parts = uint16(std::ceil(float(message.size()) / float(chunkLen))); + + uint16 high = uint16(std::floor(float(parts) / 254.0f)); + std::string partsStr(1, char(high + 1)); + partsStr += char(parts - high * 254 + 1); + + high = uint16(std::floor(float(m_messageIdIndex) / 254.0f)); + std::string messageIdStr(1, char(high + 1)); + messageIdStr += char(m_messageIdIndex - high * 254 + 1); + + if (m_messageIdIndex >= 64769) // 2^16 - 767 + m_messageIdIndex = 1; + else + ++m_messageIdIndex; + + size_t cursor = 0; + for (uint16 partId = 1; partId <= parts; ++partId) + { + high = uint16(std::floor(float(partId) / 254.0f)); + std::string partIdStr(1, char(high + 1)); + partIdStr += char(partId - high * 254 + 1); + + std::string fullmsg = "S" + aioPrefix + "\t" + messageIdStr + partsStr + partIdStr; + fullmsg += message.substr(cursor, chunkLen); + + WorldPackets::Chat::Chat packet; + packet.Initialize(CHAT_MSG_WHISPER, LANG_ADDON, this, this, fullmsg); + SendDirectMessage(packet.Write()); + + cursor += chunkLen; + } } Item* Player::GetMItem(ObjectGuid::LowType id) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 61fd843672de5..628275d7bf8f6 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1071,36 +1071,36 @@ class TC_GAME_API Player : public Unit, public GridObject void Whisper(uint32 textId, Player* target, bool isBossWhisper = false) override; void WhisperAddon(std::string const& text, Player* receiver); - // Sends an AIO message to the player - // See: class AIOMsg - void AIOMessage(AIOMsg &msg); - - // Triggers an AIO handler on the client - // To trigger multiple handlers in one message or to send more - // arguments use Player::AIOMessage - void AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, - const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil); - - // AIO can only understand smallfolk LuaVal::dumps() format - // Handler functions are called by creating a table as below - // { - // {n, ScriptName, HandlerName(optional), Arg1..N(optional) }, - // {n, AnotherScriptName, AnotherHandlerName(optional), Arg1..N(optional) } - // } - // Where n is number of arguments including handler name as an argument - void SendSimpleAIOMessage(const std::string &message); - - // Forces reload on the player AIO addons - // Syncs player AIO addons with server - void ForceReloadAddons() { AIOHandle("AIO", "ForceReload"); } - - // Force reset on the player AIO addons - // Player AIO addons and addon data is deleted and downloaded again - void ForceResetAddons() { AIOHandle("AIO", "ForceReset"); } - - bool isAIOInitOnCooldown() const { return m_aioInitCd; } - void setAIOIntOnCooldown(bool cd) { m_aioInitCd = cd; m_aioInitTimer = 0; } + // Sends an AIO message to the player + // See: class AIOMsg + void AIOMessage(AIOMsg &msg); + + // Triggers an AIO handler on the client + // To trigger multiple handlers in one message or to send more + // arguments use Player::AIOMessage + void AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, + const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, + const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil); + + // AIO can only understand smallfolk LuaVal::dumps() format + // Handler functions are called by creating a table as below + // { + // {n, ScriptName, HandlerName(optional), Arg1..N(optional) }, + // {n, AnotherScriptName, AnotherHandlerName(optional), Arg1..N(optional) } + // } + // Where n is number of arguments including handler name as an argument + void SendSimpleAIOMessage(const std::string &message); + + // Forces reload on the player AIO addons + // Syncs player AIO addons with server + void ForceReloadAddons() { AIOHandle("AIO", "ForceReload"); } + + // Force reset on the player AIO addons + // Player AIO addons and addon data is deleted and downloaded again + void ForceResetAddons() { AIOHandle("AIO", "ForceReset"); } + + bool isAIOInitOnCooldown() const { return m_aioInitCd; } + void setAIOIntOnCooldown(bool cd) { m_aioInitCd = cd; m_aioInitTimer = 0; } /*********************************************************/ /*** STORAGE SYSTEM ***/ diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 282f77a6a61b9..01eeb4be67a7a 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -2584,102 +2584,102 @@ PlayerScript::PlayerScript(char const* name) AIOScript::AIOScriptByKeyMap AIOScript::_scriptByKeyMap = AIOScript::AIOScriptByKeyMap(); -void ScriptMgr::OnAddonMessage(Player *sender, const std::string &message) +void ScriptMgr::OnAddonMessage(Player* sender, std::string const& message) { - if(!sender) - return; + if (!sender) + return; - LuaVal mainTable = LuaVal::loads(message); - if(!mainTable.istable()) //Unable to parse or incorrect format - return; + LuaVal mainTable = LuaVal::loads(message); + if (!mainTable.istable()) // Unable to parse or incorrect format + return; - //Call handlers from all blocks in order - for (size_t i = 1; i <= mainTable.len(); ++i) - { - LuaVal block = mainTable.get(static_cast(i)); - if(!block.istable()) - continue; + // Call handlers from all blocks in order + for (size_t i = 1; i <= mainTable.len(); ++i) + { + LuaVal block = mainTable.get(static_cast(i)); + if (!block.istable()) + continue; - LuaVal nArgsVal = block.get(1); - LuaVal scriptKeyVal = block.get(2); - LuaVal handlerKeyVal = block.get(3); - if (!nArgsVal.isnumber() || scriptKeyVal.isnil() || handlerKeyVal.isnil()) - continue; + LuaVal nArgsVal = block.get(1); + LuaVal scriptKeyVal = block.get(2); + LuaVal handlerKeyVal = block.get(3); + if (!nArgsVal.isnumber() || scriptKeyVal.isnil() || handlerKeyVal.isnil()) + continue; - if (nArgsVal.num() > 15.0) - { - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, - "AIO: Block from '%s' has over 15 arguments (n=%.0f). Sender: %s", - scriptKeyVal.tostring().c_str(), nArgsVal.num(), sender->GetName().c_str()); - continue; - } + if (nArgsVal.num() > 15.0) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "AIO: Block from '%s' has over 15 arguments (n=%.0f). Sender: %s", + scriptKeyVal.tostring().c_str(), nArgsVal.num(), sender->GetName().c_str()); + continue; + } - if (AIOScript* aioScript = _aioHandlers->GetScript(scriptKeyVal)) - aioScript->OnHandle(sender, handlerKeyVal, block); - } + if (AIOScript* aioScript = _aioHandlers->GetScript(scriptKeyVal)) + aioScript->OnHandle(sender, handlerKeyVal, block); + } } -AIOScript::AIOScript(const LuaVal &scriptKey) -: ScriptObject(scriptKey.tostring().c_str()), _key(scriptKey) +AIOScript::AIOScript(LuaVal const& scriptKey) + : ScriptObject(scriptKey.tostring().c_str()), _key(scriptKey) { - if(AIOScript::_scriptByKeyMap.find(scriptKey) != AIOScript::_scriptByKeyMap.end()) - { - sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '%s' of type tag '%i' already exist. Use another key.", scriptKey.tostring().c_str(), scriptKey.typetag()); - ASSERT(false); - } - ScriptRegistry::Instance()->AddScript(this); - AIOScript::_scriptByKeyMap[scriptKey] = this; + if (AIOScript::_scriptByKeyMap.find(scriptKey) != AIOScript::_scriptByKeyMap.end()) + { + sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '%s' of type tag '%i' already exist. Use another key.", scriptKey.tostring().c_str(), scriptKey.typetag()); + ASSERT(false); + } + ScriptRegistry::Instance()->AddScript(this); + AIOScript::_scriptByKeyMap[scriptKey] = this; } void AIOScript::AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, ArgFunc a1, ArgFunc a2, ArgFunc a3, ArgFunc a4, ArgFunc a5, ArgFunc a6) { - AIOHandlers *handler = sScriptMgr->_aioHandlers; - if(!handler) - return; - - //Look for hook - std::list *list = 0; - for(AIOHandlers::HookListType::iterator itr = handler->_initHookList.begin(); - itr != handler->_initHookList.end(); - ++itr) - { - if(itr->scriptKey == scriptKey && itr->handlerKey == handlerKey) - { - list = &itr->argsList; - break; - } - } - - //Add hook - if(!list) - { - handler->_initHookList.push_back(AIOHandlers::InitHookInfo(scriptKey, handlerKey)); - list = &handler->_initHookList.back().argsList; - } - - //Add args - if(a1) - list->push_back(a1); - if(a2) - list->push_back(a2); - if(a3) - list->push_back(a3); - if(a4) - list->push_back(a4); - if(a5) - list->push_back(a5); - if(a6) - list->push_back(a6); + AIOHandlers *handler = sScriptMgr->_aioHandlers; + if (!handler) + return; + + // Look for hook + std::list* list = nullptr; + for (AIOHandlers::HookListType::iterator itr = handler->_initHookList.begin(); + itr != handler->_initHookList.end(); + ++itr) + { + if (itr->scriptKey == scriptKey && itr->handlerKey == handlerKey) + { + list = &itr->argsList; + break; + } + } + + // Add hook + if (!list) + { + handler->_initHookList.push_back(AIOHandlers::InitHookInfo(scriptKey, handlerKey)); + list = &handler->_initHookList.back().argsList; + } + + // Add args + if (a1) + list->push_back(a1); + if (a2) + list->push_back(a2); + if (a3) + list->push_back(a3); + if (a4) + list->push_back(a4); + if (a5) + list->push_back(a5); + if (a6) + list->push_back(a6); } template ScriptClass *AIOScript::GetScript(const LuaVal &scriptKey) { - AIOScriptByKeyMap::const_iterator itr = AIOScript::_scriptByKeyMap.find(scriptKey); - if(itr == AIOScript::_scriptByKeyMap.end()) - return 0; - - return dynamic_cast(itr->second); + AIOScriptByKeyMap::const_iterator itr = AIOScript::_scriptByKeyMap.find(scriptKey); + if (itr == AIOScript::_scriptByKeyMap.end()) + return nullptr; + + return dynamic_cast(itr->second); } bool AIOScript::AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission) @@ -2687,95 +2687,91 @@ bool AIOScript::AddAddon(std::string const& addonName, std::string const& addonF return sWorld->AddAddon(World::AIOAddon(addonName, addonFile, permission)); } -void AIOScript::OnHandle(Player *sender, const LuaVal &handlerKey, const LuaVal &args) +void AIOScript::OnHandle(Player* sender, LuaVal const& handlerKey, LuaVal const& args) { - HandlerMapType::const_iterator itr = _handlerMap.find(handlerKey); - if(itr != _handlerMap.end()) - { - itr->second(sender, args); //Call the handler function - return; - } + HandlerMapType::const_iterator itr = _handlerMap.find(handlerKey); + if (itr != _handlerMap.end()) + { + itr->second(sender, args); + return; + } - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIO: No handler '%s' on script '%s'. Sender: %s", - handlerKey.tostring().c_str(), _key.tostring().c_str(), sender->GetName().c_str()); + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIO: No handler '%s' on script '%s'. Sender: %s", + handlerKey.tostring().c_str(), _key.tostring().c_str(), sender->GetName().c_str()); } AIOHandlers::AIOHandlers() - : AIOScript("AIO") + : AIOScript("AIO") { - AddHandler("Init", std::bind(&AIOHandlers::HandleInit, this, std::placeholders::_1, std::placeholders::_2)); - AddHandler("Error", std::bind(&AIOHandlers::HandleError, this, std::placeholders::_1, std::placeholders::_2)); + AddHandler("Init", std::bind(&AIOHandlers::HandleInit, this, std::placeholders::_1, std::placeholders::_2)); + AddHandler("Error", std::bind(&AIOHandlers::HandleError, this, std::placeholders::_1, std::placeholders::_2)); } -void AIOHandlers::HandleInit(Player *sender, const LuaVal &args) +void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) { - //Init hasn't cooled down - if(sender->isAIOInitOnCooldown()) - return; + // Init hasn't cooled down + if (sender->isAIOInitOnCooldown()) + return; - LuaVal versionVal = args.get(4); - LuaVal clientDataVal = args.get(5); - if(!versionVal.isnumber() || !clientDataVal.istable()) - { - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIOHandlers::HandleInit: Invalid version value or clientData value. Sender: %s, Args: %s", sender->GetName().c_str(), args.dumps().c_str()); - return; - } + LuaVal versionVal = args.get(4); + LuaVal clientDataVal = args.get(5); + if (!versionVal.isnumber() || !clientDataVal.istable()) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIOHandlers::HandleInit: Invalid version value or clientData value. Sender: %s, Args: %s", sender->GetName().c_str(), args.dumps().c_str()); + return; + } - if (std::abs(versionVal.num() - AIO_VERSION) > 0.01) - { - sender->AIOHandle("AIO", "Init", AIO_VERSION); - return; - } + if (std::abs(versionVal.num() - AIO_VERSION) > 0.01) + { + sender->AIOHandle("AIO", "Init", AIO_VERSION); + return; + } - sender->setAIOIntOnCooldown(true); + sender->setAIOIntOnCooldown(true); - LuaVal addonTable(TTABLE); - LuaVal cacheTable(TTABLE); - uint32 nAddons = sWorld->PrepareClientAddons(clientDataVal, addonTable, cacheTable, sender); + LuaVal addonTable(TTABLE); + LuaVal cacheTable(TTABLE); + uint32 nAddons = sWorld->PrepareClientAddons(clientDataVal, addonTable, cacheTable, sender); - LuaVal argsToSend(TTABLE); + LuaVal argsToSend(TTABLE); - uint32 blockIndex = 1; - for(HookListType::const_iterator itr = _initHookList.begin(); - itr != _initHookList.end(); - ++itr) - { - uint32 index = 3; - LuaVal HookBlock(TTABLE); + uint32 blockIndex = 1; + for (HookListType::const_iterator itr = _initHookList.begin(); + itr != _initHookList.end(); + ++itr) + { + uint32 index = 3; + LuaVal hookBlock(TTABLE); - HookBlock[1] = (uint32)itr->argsList.size() + 1; - HookBlock[2] = itr->scriptKey; - HookBlock[3] = itr->handlerKey; - for(std::list::const_iterator it = itr->argsList.begin(); - it != itr->argsList.end(); - ++it) - { - HookBlock[++index] = (*it)(sender); - } + hookBlock[1] = uint32(itr->argsList.size() + 1); + hookBlock[2] = itr->scriptKey; + hookBlock[3] = itr->handlerKey; + for (std::list::const_iterator it = itr->argsList.begin(); it != itr->argsList.end(); ++it) + hookBlock[++index] = (*it)(sender); - argsToSend[++blockIndex] = HookBlock; - } + argsToSend[++blockIndex] = hookBlock; + } - LuaVal AIOInitBlock(TTABLE); - AIOInitBlock[1] = 5; - AIOInitBlock[2] = "AIO"; - AIOInitBlock[3] = "Init"; - AIOInitBlock[4] = AIO_VERSION; - AIOInitBlock[5] = nAddons; - AIOInitBlock[6] = addonTable; - AIOInitBlock[7] = cacheTable; + LuaVal AIOInitBlock(TTABLE); + AIOInitBlock[1] = 5; + AIOInitBlock[2] = "AIO"; + AIOInitBlock[3] = "Init"; + AIOInitBlock[4] = AIO_VERSION; + AIOInitBlock[5] = nAddons; + AIOInitBlock[6] = addonTable; + AIOInitBlock[7] = cacheTable; - argsToSend[1] = AIOInitBlock; - sender->SendSimpleAIOMessage(argsToSend.dumps()); + argsToSend[1] = AIOInitBlock; + sender->SendSimpleAIOMessage(argsToSend.dumps()); } -void AIOHandlers::HandleError(Player *sender, const LuaVal &args) +void AIOHandlers::HandleError(Player* sender, LuaVal const& args) { - LuaVal msgVal = args.get(4); - if(!msgVal.isstring()) - return; + LuaVal msgVal = args.get(4); + if (!msgVal.isstring()) + return; - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "%s Received client addon error: %s", sender->GetSession()->GetPlayerInfo().c_str(), msgVal.str().c_str()); + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "%s Received client addon error: %s", sender->GetSession()->GetPlayerInfo().c_str(), msgVal.str().c_str()); } void PlayerScript::OnPVPKill(Player* /*killer*/, Player* /*killed*/) diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 1841628f469ec..6be703b719ab1 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -830,160 +830,160 @@ class TC_GAME_API GroupScript : public ScriptObject // class ExampleAIOScript : public AIOScript // { // public: -// ExampleAIOScript() -// : AIOScript("ExampleScriptName") -// { -// using namespace std::placeholders; -// -// // Loads addon files to addons list and sends them on AIO client initialization -// // Looks for the file in path config AIO.ClientScriptPath -// AddAddon(World::AIOAddon("ExampleAddon", "example_addon.lua")); -// -// // You can also add addons to be sent to players with specific permission -// AddAddon(World::AIOAddon("AnotherAddon", "example_addon.lua", 192)); //192 refers to admin RBAC permission +// ExampleAIOScript() +// : AIOScript("ExampleScriptName") +// { +// using namespace std::placeholders; +// +// // Loads addon files to addons list and sends them on AIO client initialization +// // Looks for the file in path config AIO.ClientScriptPath +// AddAddon(World::AIOAddon("ExampleAddon", "example_addon.lua")); +// +// // You can also add addons to be sent to players with specific permission +// AddAddon(World::AIOAddon("AnotherAddon", "example_addon.lua", 192)); //192 refers to admin RBAC permission // -// // Handler function signature: void HandlerFunction(Player *sender, const LuaVal &args) -// AddHandler("Print", std::bind(&ExampleAIOScript::HandlePrint, this, _1, _2)); -// AddHandler("Save", std::bind(&ExampleAIOScript::HandleSave, this, _1, _2)); +// // Handler function signature: void HandlerFunction(Player *sender, const LuaVal &args) +// AddHandler("Print", std::bind(&ExampleAIOScript::HandlePrint, this, _1, _2)); +// AddHandler("Save", std::bind(&ExampleAIOScript::HandleSave, this, _1, _2)); // -// // Initialization handler and arguments -// AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg, this, _1), std::bind(&ExampleAIOScript::InitArg, this, _1)); -// //Adds additional argument to send to handler -// AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg2, this, _1)); -// AddInitArgs("AnotherScript", "InitB"); //Arguments are not necessary -// } +// // Initialization handler and arguments +// AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg, this, _1), std::bind(&ExampleAIOScript::InitArg, this, _1)); +// //Adds additional argument to send to handler +// AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg2, this, _1)); +// AddInitArgs("AnotherScript", "InitB"); //Arguments are not necessary +// } // -// void HandlePrint(Player *sender, const LuaVal &args) -// { -// //LuaVal args in a handler function is always a table -// //Handler arguments index starts from 4 -// LuaVal &InputVal = args[4]; -// LuaVal &SliderVal = args[5]; +// void HandlePrint(Player *sender, const LuaVal &args) +// { +// //LuaVal args in a handler function is always a table +// //Handler arguments index starts from 4 +// LuaVal &InputVal = args[4]; +// LuaVal &SliderVal = args[5]; // -// //MUST check if the value type is valid or else smallfolk_cpp will -// //throw on obtaining that type -// if(!InputVal.isstring() || !SliderVal.isnumber()) -// { -// return; -// } +// //MUST check if the value type is valid or else smallfolk_cpp will +// //throw on obtaining that type +// if (!InputVal.isstring() || !SliderVal.isnumber()) +// { +// return; +// } // -// sender->GetSession()->SendNotification("HandlePrint -> Stored String: %s, Input: %s, Slider Value: %f", -// storedString.c_str(), InputVal.str().c_str(), SliderVal.num()); -// } +// sender->GetSession()->SendNotification("HandlePrint -> Stored String: %s, Input: %s, Slider Value: %f", +// storedString.c_str(), InputVal.str().c_str(), SliderVal.num()); +// } // -// void HandleSave(Player *sender, const LuaVal &args) -// { -// //LuaVal args in a handler function is always a table -// //Handler arguments index starts from 4 -// LuaVal &SaveVal = args.get[4]; +// void HandleSave(Player *sender, const LuaVal &args) +// { +// //LuaVal args in a handler function is always a table +// //Handler arguments index starts from 4 +// LuaVal &SaveVal = args.get[4]; // -// //MUST check if the value type is valid -// if(!SaveVal.isstring()) -// { -// return; -// } +// //MUST check if the value type is valid +// if (!SaveVal.isstring()) +// { +// return; +// } // -// storedString = SaveVal.str(); -// sender->GetSession()->SendNotification("Saved"); -// } +// storedString = SaveVal.str(); +// sender->GetSession()->SendNotification("Saved"); +// } // -// LuaVal InitArg(Player *sender) -// { -// LuaVal arg = LuaVal(TTABLE); -// arg.set("key", 12.3); -// arg["key2"] = false; +// LuaVal InitArg(Player *sender) +// { +// LuaVal arg = LuaVal(TTABLE); +// arg.set("key", 12.3); +// arg["key2"] = false; // -// return arg; -// } +// return arg; +// } // -// LuaVal InitArg2(Player *sender) -// { -// return "LuaVal will implicitly create a string LuaVal for this arg"; -// } +// LuaVal InitArg2(Player *sender) +// { +// return "LuaVal will implicitly create a string LuaVal for this arg"; +// } // // private: -// std::string storedString; +// std::string storedString; // }; class AIOScript : public ScriptObject { public: - virtual ~AIOScript() { AIOScript::_scriptByKeyMap.erase(GetKey()); } + virtual ~AIOScript() { AIOScript::_scriptByKeyMap.erase(GetKey()); } - // Returns the key of this CAIO script - LuaVal GetKey() const { return _key; } + // Returns the key of this CAIO script + LuaVal GetKey() const { return _key; } bool IsDatabaseBound() const { return false; } - typedef std::function HandlerFunc; - typedef std::function ArgFunc; + typedef std::function HandlerFunc; + typedef std::function ArgFunc; - protected: - // Registers an AIO Handler script of scriptName - AIOScript(const LuaVal &scriptKey); + protected: + // Registers an AIO Handler script of scriptName + AIOScript(LuaVal const& scriptKey); + + // Registers a handler function to call when handling + // handleKey of this script. + void AddHandler(const LuaVal &handlerKey, HandlerFunc function) { _handlerMap[handlerKey] = function; } + + // Adds a client side handler to call and adds arguments + // to sends with it for AIO client initialization. + // + // You can add additional arguments to the handler by + // calling this function again + void AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, + ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), + ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); + + // Adds a WoW addon file to the list of addons with a unique + // addon key to send on AIO client initialization. + // Returns true if addon was added, false if addon key is taken. + // + // It is required to call World::ForceReloadPlayerAddons() + // if addons are added after server is fully initialized + // for online players to load the added addons. + bool AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); + + // Returns pointer to an AIO script by its key and typename. + // Returns null if scriptName doesn't exist or typename was incorrect. + template + ScriptClass *GetScript(const LuaVal &key); - // Registers a handler function to call when handling - // handleKey of this script. - void AddHandler(const LuaVal &handlerKey, HandlerFunc function) { _handlerMap[handlerKey] = function; } + private: + void OnHandle(Player* sender, LuaVal const& handlerKey, LuaVal const& args); - // Adds a client side handler to call and adds arguments - // to sends with it for AIO client initialization. - // - // You can add additional arguments to the handler by - // calling this function again - void AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, - ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), - ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); + LuaVal _key; - // Adds a WoW addon file to the list of addons with a unique - // addon key to send on AIO client initialization. - // Returns true if addon was added, false if addon key is taken. - // - // It is required to call World::ForceReloadPlayerAddons() - // if addons are added after server is fully initialized - // for online players to load the added addons. - bool AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); + typedef std::unordered_map HandlerMapType; + HandlerMapType _handlerMap; - // Returns pointer to an AIO script by its key and typename. - // Returns null if scriptName doesn't exist or typename was incorrect. - template - ScriptClass *GetScript(const LuaVal &key); + typedef std::unordered_map AIOScriptByKeyMap; + static AIOScriptByKeyMap _scriptByKeyMap; - private: - void OnHandle(Player *sender, const LuaVal &handlerKey, const LuaVal &args); + friend class ScriptMgr; +}; - LuaVal _key; +class AIOHandlers : public AIOScript +{ + private: + AIOHandlers(); + void HandleInit(Player* sender, LuaVal const& args); + void HandleError(Player* sender, LuaVal const& args); - typedef std::unordered_map HandlerMapType; - HandlerMapType _handlerMap; + struct InitHookInfo + { + LuaVal scriptKey; + LuaVal handlerKey; + std::list argsList; - typedef std::unordered_map AIOScriptByKeyMap; - static AIOScriptByKeyMap _scriptByKeyMap; + InitHookInfo(const LuaVal &scriptKey, const LuaVal &handlerKey) + : scriptKey(scriptKey), handlerKey(handlerKey) + { } + }; - friend class ScriptMgr; -}; + typedef std::list HookListType; + HookListType _initHookList; -class AIOHandlers : public AIOScript -{ - private: - AIOHandlers(); - void HandleInit(Player *sender, const LuaVal &args); - void HandleError(Player *sender, const LuaVal &args); - - struct InitHookInfo - { - LuaVal scriptKey; - LuaVal handlerKey; - std::list argsList; - - InitHookInfo(const LuaVal &scriptKey, const LuaVal &handlerKey) - : scriptKey(scriptKey), handlerKey(handlerKey) - { } - }; - - typedef std::list HookListType; - HookListType _initHookList; - - friend class ScriptMgr; - friend class AIOScript; + friend class ScriptMgr; + friend class AIOScript; }; // Placed here due to ScriptRegistry::AddScript dependency. @@ -1002,7 +1002,7 @@ class AIOHandlers : public AIOScript class TC_GAME_API ScriptMgr { friend class ScriptObject; - friend class AIOScript; + friend class AIOScript; private: ScriptMgr(); diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index cb467583da945..846f2891be412 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -318,7 +318,7 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) } else if (_player->IsInWorld()) { - if(AntiDOS.EvaluateOpcode(*packet, currentTime)) + if (AntiDOS.EvaluateOpcode(*packet, currentTime)) { sScriptMgr->OnPacketReceive(this, *packet); opHandle->Call(this, *packet); @@ -476,25 +476,25 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) return false; //Will remove this session from the world session map } - // AIO: drop incomplete long messages (AIO.lua AIO_MSG_CACHE_TIME / AIO_MSG_CACHE_DELAY) - if (m_Socket && m_Socket->IsOpen() && !_addonMessageBuffer.empty()) - { - _aioMsgCacheSweepTimer += diff; - uint32 const sweepDelay = sWorld->getIntConfig(CONFIG_AIO_MSG_CACHE_DELAY); - if (_aioMsgCacheSweepTimer >= sweepDelay) - { - _aioMsgCacheSweepTimer = 0; - uint32 const cacheTime = sWorld->getIntConfig(CONFIG_AIO_MSG_CACHE_TIME); - for (AddonMessageBufferMap::iterator itr = _addonMessageBuffer.begin(); itr != _addonMessageBuffer.end();) - { - itr->second.Timer += sweepDelay; - if (itr->second.Timer >= cacheTime) - _addonMessageBuffer.erase(itr++); - else - ++itr; - } - } - } + // AIO: drop incomplete long messages (AIO.lua AIO_MSG_CACHE_TIME / AIO_MSG_CACHE_DELAY) + if (m_Socket && m_Socket->IsOpen() && !_addonMessageBuffer.empty()) + { + _aioMsgCacheSweepTimer += diff; + uint32 const sweepDelay = sWorld->getIntConfig(CONFIG_AIO_MSG_CACHE_DELAY); + if (_aioMsgCacheSweepTimer >= sweepDelay) + { + _aioMsgCacheSweepTimer = 0; + uint32 const cacheTime = sWorld->getIntConfig(CONFIG_AIO_MSG_CACHE_TIME); + for (AddonMessageBufferMap::iterator itr = _addonMessageBuffer.begin(); itr != _addonMessageBuffer.end();) + { + itr->second.Timer += sweepDelay; + if (itr->second.Timer >= cacheTime) + _addonMessageBuffer.erase(itr++); + else + ++itr; + } + } + } return true; } @@ -640,8 +640,8 @@ void WorldSession::LogoutPlayer(bool save) CharacterDatabase.Execute(stmt); } - //Clear aio long message buffer - _addonMessageBuffer.clear(); + //Clear aio long message buffer + _addonMessageBuffer.clear(); m_playerLogout = false; m_playerSave = false; diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 9835dfb214514..841c6195824e3 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -1349,18 +1349,18 @@ class TC_GAME_API WorldSession WorldSession(WorldSession const& right) = delete; WorldSession& operator=(WorldSession const& right) = delete; - //AIO - typedef std::map AddonPartStringMap; - struct LongMessageBufferInfo - { - uint32 Parts = 0; - uint32 Timer = 0; - uint32 BufferedBytes = 0; - AddonPartStringMap Map; - }; - typedef std::map AddonMessageBufferMap; - AddonMessageBufferMap _addonMessageBuffer; - uint32 _aioMsgCacheSweepTimer = 0; + // AIO + typedef std::map AddonPartStringMap; + struct LongMessageBufferInfo + { + uint32 Parts = 0; + uint32 Timer = 0; + uint32 BufferedBytes = 0; + AddonPartStringMap Map; + }; + typedef std::map AddonMessageBufferMap; + AddonMessageBufferMap _addonMessageBuffer; + uint32 _aioMsgCacheSweepTimer = 0; }; #endif /// @} diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index ec2aa8a3057c0..fb1b249a2eaf9 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -3607,175 +3607,175 @@ void World::ReloadRBAC() bool World::AddAddon(AIOAddon const& addon) { - if(addon.file.empty()) - return false; - - //Check if addon already exist - for(AddonCodeListType::iterator itr = m_AddonList.begin(); - itr != m_AddonList.end(); - ++itr) - { - if(itr->name == addon.name) - { - return false; - } - } - - AIOAddon copy(addon); - copy.code = ""; - - //Format path - std::string path; - path = sWorld->GetAIOClientScriptPath(); - if(path.back() != '/' && path.back() != '\\') - { - path += '/'; - } - path += copy.file; - - //Get file - std::ifstream in(path, std::ios::in | std::ios::binary); - if(in) - { - in.seekg(0, std::ios::end); - copy.code.resize(in.tellg()); - in.seekg(0, std::ios::beg); - in.read(©.code[0], copy.code.size()); - in.close(); - if(copy.code.empty()) - { - return false; - } - } - else - { - sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO AddAddon: Couldn't open file %s of addon %s", path.c_str(), copy.name.c_str()); - return false; - } - - //Set crc on original file content - boost::crc_32_type crc_result; - crc_result.process_bytes(copy.code.data(), copy.code.length()); - copy.crc = crc_result.checksum(); - - // Uncompressed addon payload (compression/obfuscation not implemented) - copy.code = std::string(1, 'U') + copy.code; - m_AddonList.push_back(copy); - - sLog->outAIOMessage(0, LOG_LEVEL_INFO, "AIO: Loaded addon %s from file %s", copy.name.c_str(), copy.file.c_str()); - return true; + if (addon.file.empty()) + return false; + + // Check if addon already exist + for (AddonCodeListType::iterator itr = m_AddonList.begin(); + itr != m_AddonList.end(); + ++itr) + { + if (itr->name == addon.name) + { + return false; + } + } + + AIOAddon copy(addon); + copy.code = ""; + + // Format path + std::string path; + path = sWorld->GetAIOClientScriptPath(); + if (path.back() != '/' && path.back() != '\\') + { + path += '/'; + } + path += copy.file; + + // Get file + std::ifstream in(path, std::ios::in | std::ios::binary); + if (in) + { + in.seekg(0, std::ios::end); + copy.code.resize(in.tellg()); + in.seekg(0, std::ios::beg); + in.read(©.code[0], copy.code.size()); + in.close(); + if (copy.code.empty()) + { + return false; + } + } + else + { + sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO AddAddon: Couldn't open file %s of addon %s", path.c_str(), copy.name.c_str()); + return false; + } + + // Set crc on original file content + boost::crc_32_type crc_result; + crc_result.process_bytes(copy.code.data(), copy.code.length()); + copy.crc = crc_result.checksum(); + + // Uncompressed addon payload (compression/obfuscation not implemented) + copy.code = std::string(1, 'U') + copy.code; + m_AddonList.push_back(copy); + + sLog->outAIOMessage(0, LOG_LEVEL_INFO, "AIO: Loaded addon %s from file %s", copy.name.c_str(), copy.file.c_str()); + return true; } bool World::RemoveAddon(std::string const& addonName, uint32* permission) { - for (AddonCodeListType::iterator itr = m_AddonList.begin(); itr != m_AddonList.end(); ++itr) - { - if (itr->name == addonName) - { - if (permission) - *permission = itr->permission; - m_AddonList.erase(itr); - return true; - } - } - return false; + for (AddonCodeListType::iterator itr = m_AddonList.begin(); itr != m_AddonList.end(); ++itr) + { + if (itr->name == addonName) + { + if (permission) + *permission = itr->permission; + m_AddonList.erase(itr); + return true; + } + } + return false; } bool World::ReloadAddons() { - sLog->outAIOMessage(0, LOG_LEVEL_INFO, "World::ReloadAddons()"); - - AddonCodeListType prevAddonList; - prevAddonList.swap(m_AddonList); - try - { - for(AddonCodeListType::const_iterator itr = prevAddonList.begin(); - itr != prevAddonList.end(); - ++itr) - { - AddAddon(*itr); - } - } - catch(std::exception &e) - { - sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO: Error reloading addons. Exception: %s", e.what()); - m_AddonList.swap(prevAddonList); - return false; - } - catch(...) - { - sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO: Error reloading addons"); - m_AddonList.swap(prevAddonList); - return false; - } - return true; -} - -size_t World::PrepareClientAddons(const LuaVal &clientData, LuaVal &addonsTable, LuaVal &cacheTable, Player *forPlayer) const -{ - if (!clientData.istable()) - return 0; - - uint32 i = 0; - for(AddonCodeListType::const_iterator itr = m_AddonList.begin(); - itr != m_AddonList.end(); - ++itr) - { - if(!forPlayer->GetSession()->HasPermission(itr->permission)) - continue; - - LuaVal CRCVal = clientData.get(itr->name); - if(CRCVal == itr->crc) - { - cacheTable[++i] = itr->name; - } - else - { - LuaVal addonData(TTABLE); - addonData["name"] = itr->name; - addonData["crc"] = itr->crc; - addonData["code"] = itr->code; - addonsTable[++i] = addonData; - } - } - return i; + sLog->outAIOMessage(0, LOG_LEVEL_INFO, "World::ReloadAddons()"); + + AddonCodeListType prevAddonList; + prevAddonList.swap(m_AddonList); + try + { + for (AddonCodeListType::const_iterator itr = prevAddonList.begin(); + itr != prevAddonList.end(); + ++itr) + { + AddAddon(*itr); + } + } + catch (std::exception &e) + { + sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO: Error reloading addons. Exception: %s", e.what()); + m_AddonList.swap(prevAddonList); + return false; + } + catch (...) + { + sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO: Error reloading addons"); + m_AddonList.swap(prevAddonList); + return false; + } + return true; +} + +size_t World::PrepareClientAddons(LuaVal const& clientData, LuaVal& addonsTable, LuaVal& cacheTable, Player* forPlayer) const +{ + if (!clientData.istable()) + return 0; + + uint32 i = 0; + for (AddonCodeListType::const_iterator itr = m_AddonList.begin(); + itr != m_AddonList.end(); + ++itr) + { + if (!forPlayer->GetSession()->HasPermission(itr->permission)) + continue; + + LuaVal CRCVal = clientData.get(itr->name); + if (CRCVal == itr->crc) + { + cacheTable[++i] = itr->name; + } + else + { + LuaVal addonData(TTABLE); + addonData["name"] = itr->name; + addonData["crc"] = itr->crc; + addonData["code"] = itr->code; + addonsTable[++i] = addonData; + } + } + return i; } void World::ForceReloadPlayerAddons(uint32 permission) { - for(SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) - { - if(itr->second->GetPlayer() && itr->second->HasPermission(permission)) - itr->second->GetPlayer()->ForceReloadAddons(); - } + for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) + { + if (itr->second->GetPlayer() && itr->second->HasPermission(permission)) + itr->second->GetPlayer()->ForceReloadAddons(); + } } void World::ForceResetPlayerAddons(uint32 permission) { - for(SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) - { - if(itr->second->GetPlayer() && itr->second->HasPermission(permission)) - itr->second->GetPlayer()->ForceResetAddons(); - } + for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) + { + if (itr->second->GetPlayer() && itr->second->HasPermission(permission)) + itr->second->GetPlayer()->ForceResetAddons(); + } } void World::AIOMessageAll(AIOMsg &msg, uint32 permission) { - std::string messageStr = msg.dumps(); - for(SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) - { - if(itr->second->GetPlayer() && itr->second->HasPermission(permission)) - itr->second->GetPlayer()->SendSimpleAIOMessage(messageStr); - } + std::string messageStr = msg.dumps(); + for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) + { + if (itr->second->GetPlayer() && itr->second->HasPermission(permission)) + itr->second->GetPlayer()->SendSimpleAIOMessage(messageStr); + } } void World::SendAllSimpleAIOMessage(const std::string &message, uint32 permission) { - for(SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) - { - if(itr->second->GetPlayer() && itr->second->HasPermission(permission)) - itr->second->GetPlayer()->SendSimpleAIOMessage(message); - } + for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) + { + if (itr->second->GetPlayer() && itr->second->HasPermission(permission)) + itr->second->GetPlayer()->SendSimpleAIOMessage(message); + } } void World::RemoveOldCorpses() diff --git a/src/server/scripts/AIO/ExampleWindow.cpp b/src/server/scripts/AIO/ExampleWindow.cpp index e4a8959a0b9a4..11835143388a3 100644 --- a/src/server/scripts/AIO/ExampleWindow.cpp +++ b/src/server/scripts/AIO/ExampleWindow.cpp @@ -7,70 +7,70 @@ // Place ExampleWindow.lua (and related client files) under lua_client_scripts/ExampleWindow/ class ExampleWindowScript : public AIOScript { -public: - ExampleWindowScript() - : AIOScript("AIOExample"), counter(0) - { - AddAddon("ExampleWindow", "ExampleWindow.lua"); - AddHandler("Print", std::bind(&ExampleWindowScript::HandlePrint, this, std::placeholders::_1, std::placeholders::_2)); - AddHandler("bullshit", std::bind(&ExampleWindowScript::HandleBullshit, this, std::placeholders::_1, std::placeholders::_2)); - AddInitArgs("AIOExample", "Init", std::bind(&ExampleWindowScript::InitArg, this, std::placeholders::_1), std::bind(&ExampleWindowScript::InitArg, this, std::placeholders::_1)); - AddInitArgs("AIOExample", "Init", std::bind(&ExampleWindowScript::InitArg2, this, std::placeholders::_1)); - AddInitArgs("AIOExample", "InitB"); - } + public: + ExampleWindowScript() + : AIOScript("AIOExample"), counter(0) + { + AddAddon("ExampleWindow", "ExampleWindow.lua"); + AddHandler("Print", std::bind(&ExampleWindowScript::HandlePrint, this, std::placeholders::_1, std::placeholders::_2)); + AddHandler("bullshit", std::bind(&ExampleWindowScript::HandleBullshit, this, std::placeholders::_1, std::placeholders::_2)); + AddInitArgs("AIOExample", "Init", std::bind(&ExampleWindowScript::InitArg, this, std::placeholders::_1), std::bind(&ExampleWindowScript::InitArg, this, std::placeholders::_1)); + AddInitArgs("AIOExample", "Init", std::bind(&ExampleWindowScript::InitArg2, this, std::placeholders::_1)); + AddInitArgs("AIOExample", "InitB"); + } -private: - void HandlePrint(Player* sender, const LuaVal& args) - { - LuaVal btn = args.get(4); - LuaVal inp = args.get(5); - LuaVal val = args.get(6); - if (!btn.isstring() || !inp.isstring() || !val.isnumber()) - return; + private: + void HandlePrint(Player* sender, LuaVal const& args) + { + LuaVal btn = args.get(4); + LuaVal inp = args.get(5); + LuaVal val = args.get(6); + if (!btn.isstring() || !inp.isstring() || !val.isnumber()) + return; - ChatHandler(sender->GetSession()).PSendSysMessage("HandlePrint -> Button Name: %s, Input: %s, Slider Value: %f", - btn.str().c_str(), inp.str().c_str(), val.num()); + ChatHandler(sender->GetSession()).PSendSysMessage("HandlePrint -> Button Name: %s, Input: %s, Slider Value: %f", + btn.str().c_str(), inp.str().c_str(), val.num()); - try - { - long size = std::stol(inp.str()); - if (size < 0 || size > 8192) - { - ChatHandler(sender->GetSession()).SendSysMessage("ExampleWindow: payload size must be between 0 and 8192."); - return; - } - std::string payload(size_t(size), 'b'); - sender->AIOHandle("AIOExample", "bullshit", payload); - } - catch (...) - { - ChatHandler(sender->GetSession()).SendSysMessage("ExampleWindow: invalid payload size."); - } - } + try + { + long size = std::stol(inp.str()); + if (size < 0 || size > 8192) + { + ChatHandler(sender->GetSession()).SendSysMessage("ExampleWindow: payload size must be between 0 and 8192."); + return; + } + std::string payload(size_t(size), 'b'); + sender->AIOHandle("AIOExample", "bullshit", payload); + } + catch (...) + { + ChatHandler(sender->GetSession()).SendSysMessage("ExampleWindow: invalid payload size."); + } + } - void HandleBullshit(Player* sender, const LuaVal& args) - { - LuaVal payload = args.get(4); - if (!payload.isstring()) - return; + void HandleBullshit(Player* sender, LuaVal const& args) + { + LuaVal payload = args.get(4); + if (!payload.isstring()) + return; - ChatHandler(sender->GetSession()).PSendSysMessage("Received bullshit block (%u bytes).", uint32(payload.str().size())); - } + ChatHandler(sender->GetSession()).PSendSysMessage("Received bullshit block (%u bytes).", uint32(payload.str().size())); + } - LuaVal InitArg(Player* /*sender*/) - { - return (counter++ % 2 == 0) ? LuaVal("Inited 1") : LuaVal("Inited 2"); - } + LuaVal InitArg(Player* /*sender*/) + { + return (counter++ % 2 == 0) ? LuaVal("Inited 1") : LuaVal("Inited 2"); + } - LuaVal InitArg2(Player* /*sender*/) - { - return (counter % 2 != 0) ? LuaVal("Inited 1") : LuaVal("Inited 2"); - } + LuaVal InitArg2(Player* /*sender*/) + { + return (counter % 2 != 0) ? LuaVal("Inited 1") : LuaVal("Inited 2"); + } - size_t counter; + size_t counter; }; void AddSC_ExampleWindow() { - new ExampleWindowScript(); + new ExampleWindowScript(); } From 824aa9141f77f88c3d8ce07f36afb685184a643f Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Mon, 1 Jun 2026 03:38:53 +0300 Subject: [PATCH 14/33] Style: strip trailing whitespace and tabs from CAIO tree Clean all files in the CAIO diff against TrinityCore 3.3.5, including docs and .gitmodules. --- .gitmodules | 4 +- CAIO_README.md | 312 +++++++++++++------------- src/server/game/Scripting/ScriptMgr.h | 28 +-- 3 files changed, 172 insertions(+), 172 deletions(-) diff --git a/.gitmodules b/.gitmodules index 5d71b4f21514c..b284b822b0405 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "dep/smallfolk_cpp/smallfolk_cpp"] - path = dep/smallfolk_cpp/smallfolk_cpp - url = https://github.com/Rochet2/smallfolk_cpp.git \ No newline at end of file + path = dep/smallfolk_cpp/smallfolk_cpp + url = https://github.com/Rochet2/smallfolk_cpp.git \ No newline at end of file diff --git a/CAIO_README.md b/CAIO_README.md index fbae57004e3d1..ec95a13d3be31 100644 --- a/CAIO_README.md +++ b/CAIO_README.md @@ -60,79 +60,79 @@ cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DSCRIPTS=static -DTOOLS=0 class ExampleCAIOScript : public AIOScript { public: - ExampleCAIOScript() - : AIOScript("ExampleScriptName") - { - using namespace std::placeholders; - - // Loads addon files to addons list and sends them on AIO client initialization - // Looks for the file in path config AIO.ClientScriptPath - AddAddon(World::AIOAddon("ExampleAddon", "example_addon.lua")); - - // You can also add addons to be sent to players with specific permission - AddAddon(World::AIOAddon("AnotherAddon", "example_addon.lua", 192)); //192 refers to gm level 3 RBAC permission - - // Handler function signature: void HandlerFunction(Player *sender, const LuaVal &args) - AddHandler("Print", std::bind(&ExampleCAIOScript::HandlePrint, this, _1, _2)); - AddHandler("Save", std::bind(&ExampleCAIOScript::HandleSave, this, _1, _2)); - - // Initialization handler and arguments - AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleCAIOScript::InitArg, this, _1), std::bind(&ExampleCAIOScript::InitArg, this, _1)); - //Adds additional argument to send to handler - AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleCAIOScript::InitArg2, this, _1)); - AddInitArgs("AnotherScript", "InitB"); //Arguments are not necessary - } - - void HandlePrint(Player *sender, const LuaVal &args) - { - //LuaVal args in a handler function is always a table - //Handler arguments index starts from 4 - LuaVal &InputVal = args[4]; - LuaVal &SliderVal = args[5]; - - //MUST check if the value type is valid or else smallfolk_cpp will - //throw on obtaining that type - if(!InputVal.isstring() || !SliderVal.isnumber()) - { - return; - } - - sender->GetSession()->SendNotification("HandlePrint -> Stored String: %s, Input: %s, Slider Value: %f", - storedString.c_str(), InputVal.str().c_str(), SliderVal.num()); - } - - void HandleSave(Player *sender, const LuaVal &args) - { - //LuaVal args in a handler function is always a table - //Handler arguments index starts from 4 - LuaVal &SaveVal = args[4]; - - //MUST check if the value type is valid - if(!SaveVal.isstring()) - { - return; - } - - storedString = SaveVal.str(); - sender->GetSession()->SendNotification("Saved"); - } - - LuaVal InitArg(Player *sender) - { - LuaVal arg = LuaVal(TTABLE); - arg.set("key", 12.3); - arg["key2"] = false; - - return arg; - } - - LuaVal InitArg2(Player *sender) - { - return "LuaVal will implicitly create a string LuaVal for this arg"; - } + ExampleCAIOScript() + : AIOScript("ExampleScriptName") + { + using namespace std::placeholders; + + // Loads addon files to addons list and sends them on AIO client initialization + // Looks for the file in path config AIO.ClientScriptPath + AddAddon(World::AIOAddon("ExampleAddon", "example_addon.lua")); + + // You can also add addons to be sent to players with specific permission + AddAddon(World::AIOAddon("AnotherAddon", "example_addon.lua", 192)); //192 refers to gm level 3 RBAC permission + + // Handler function signature: void HandlerFunction(Player *sender, const LuaVal &args) + AddHandler("Print", std::bind(&ExampleCAIOScript::HandlePrint, this, _1, _2)); + AddHandler("Save", std::bind(&ExampleCAIOScript::HandleSave, this, _1, _2)); + + // Initialization handler and arguments + AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleCAIOScript::InitArg, this, _1), std::bind(&ExampleCAIOScript::InitArg, this, _1)); + //Adds additional argument to send to handler + AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleCAIOScript::InitArg2, this, _1)); + AddInitArgs("AnotherScript", "InitB"); //Arguments are not necessary + } + + void HandlePrint(Player *sender, const LuaVal &args) + { + //LuaVal args in a handler function is always a table + //Handler arguments index starts from 4 + LuaVal &InputVal = args[4]; + LuaVal &SliderVal = args[5]; + + //MUST check if the value type is valid or else smallfolk_cpp will + //throw on obtaining that type + if(!InputVal.isstring() || !SliderVal.isnumber()) + { + return; + } + + sender->GetSession()->SendNotification("HandlePrint -> Stored String: %s, Input: %s, Slider Value: %f", + storedString.c_str(), InputVal.str().c_str(), SliderVal.num()); + } + + void HandleSave(Player *sender, const LuaVal &args) + { + //LuaVal args in a handler function is always a table + //Handler arguments index starts from 4 + LuaVal &SaveVal = args[4]; + + //MUST check if the value type is valid + if(!SaveVal.isstring()) + { + return; + } + + storedString = SaveVal.str(); + sender->GetSession()->SendNotification("Saved"); + } + + LuaVal InitArg(Player *sender) + { + LuaVal arg = LuaVal(TTABLE); + arg.set("key", 12.3); + arg["key2"] = false; + + return arg; + } + + LuaVal InitArg2(Player *sender) + { + return "LuaVal will implicitly create a string LuaVal for this arg"; + } private: - std::string storedString; + std::string storedString; }; ``` @@ -150,41 +150,41 @@ Use `LuaVal::nil` (not `LuaVal::nil()`) for default optional arguments. Type tag class AIOScript : public ScriptObject { public: - virtual ~AIOScript(); - - // Returns the key of this CAIO script - LuaVal GetKey() const; + virtual ~AIOScript(); + + // Returns the key of this CAIO script + LuaVal GetKey() const; protected: - // Registers an AIO Handler script of scriptName - AIOScript(const LuaVal &scriptKey); - - // Registers a handler function to call when handling - // handleKey of this script. - void AddHandler(const LuaVal &handlerKey, HandlerFunc function); - - // Adds a client side handler to call and adds arguments - // to sends with it for AIO client initialization. - // - // You can add additional arguments to the handler by - // calling this function again - void AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, - ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), - ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); - - // Adds a WoW addon file to the list of addons with a unique - // addon key to send on AIO client initialization. - // Returns true if addon was added, false if addon key is taken. - // - // It is required to call World::ForceReloadPlayerAddons() - // if addons are added after server is fully initialized - // for online players to load the added addons. - bool AddAddon(const World::AIOAddon &addon); - - // Returns pointer to an AIO script by its key and typename. - // Returns null if scriptName doesn't exist or typename was incorrect. - template - ScriptClass *GetScript(const LuaVal &key); + // Registers an AIO Handler script of scriptName + AIOScript(const LuaVal &scriptKey); + + // Registers a handler function to call when handling + // handleKey of this script. + void AddHandler(const LuaVal &handlerKey, HandlerFunc function); + + // Adds a client side handler to call and adds arguments + // to sends with it for AIO client initialization. + // + // You can add additional arguments to the handler by + // calling this function again + void AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, + ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), + ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); + + // Adds a WoW addon file to the list of addons with a unique + // addon key to send on AIO client initialization. + // Returns true if addon was added, false if addon key is taken. + // + // It is required to call World::ForceReloadPlayerAddons() + // if addons are added after server is fully initialized + // for online players to load the added addons. + bool AddAddon(const World::AIOAddon &addon); + + // Returns pointer to an AIO script by its key and typename. + // Returns null if scriptName doesn't exist or typename was incorrect. + template + ScriptClass *GetScript(const LuaVal &key); } ``` @@ -194,27 +194,27 @@ protected: class AIOMsg { public: - //Creates an empty AIOMsg - AIOMsg(); - - //Creates a AIO message and adds one block - AIOMsg(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); - - //Adds another block - //Another block will call another handler in one message - AIOMsg &Add(cconst LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); - - //Appends the last block - //You can add additional arguments to the last block - AIOMsg &AppendLast(const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); - - //Returns smallfolk dump of the AIO message - std::string dumps() const; + //Creates an empty AIOMsg + AIOMsg(); + + //Creates a AIO message and adds one block + AIOMsg(const LuaVal &scriptKey, const LuaVal &handlerKey, + const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + + //Adds another block + //Another block will call another handler in one message + AIOMsg &Add(cconst LuaVal &scriptKey, const LuaVal &handlerKey, + const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + + //Appends the last block + //You can add additional arguments to the last block + AIOMsg &AppendLast(const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + + //Returns smallfolk dump of the AIO message + std::string dumps() const; ``` **Player.h** @@ -223,36 +223,36 @@ public: class Player { public: - // Sends an AIO message to the player - // See: class AIOMsg - void AIOMessage(AIOMsg &msg); - - // Triggers an AIO handler on the client - // To trigger multiple handlers in one message or to send more - // arguments use Player::AIOMessage - void AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); - - // AIO can only understand smallfolk LuaVal::dumps() format - // Handler functions are called by creating a table as below - // { - // {n, ScriptName, HandlerName(optional), Arg1..N(optional) }, - // {n, AnotherScriptName, AnotherHandlerName(optional), Arg1..N(optional) } - // } - // Where n is number of arguments including handler name as an argument - void SendSimpleAIOMessage(const std::string &message); - - // Forces reload on the player AIO addons - // Syncs player AIO addons with server - void ForceReloadAddons(); - - // Force reset on the player AIO addons - // Player AIO addons and addon data is deleted and downloaded again - void ForceResetAddons(); - - bool isAIOInitOnCooldown() const; - void setAIOIntOnCooldown(bool cd); + // Sends an AIO message to the player + // See: class AIOMsg + void AIOMessage(AIOMsg &msg); + + // Triggers an AIO handler on the client + // To trigger multiple handlers in one message or to send more + // arguments use Player::AIOMessage + void AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, + const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), + const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); + + // AIO can only understand smallfolk LuaVal::dumps() format + // Handler functions are called by creating a table as below + // { + // {n, ScriptName, HandlerName(optional), Arg1..N(optional) }, + // {n, AnotherScriptName, AnotherHandlerName(optional), Arg1..N(optional) } + // } + // Where n is number of arguments including handler name as an argument + void SendSimpleAIOMessage(const std::string &message); + + // Forces reload on the player AIO addons + // Syncs player AIO addons with server + void ForceReloadAddons(); + + // Force reset on the player AIO addons + // Player AIO addons and addon data is deleted and downloaded again + void ForceResetAddons(); + + bool isAIOInitOnCooldown() const; + void setAIOIntOnCooldown(bool cd); } ``` @@ -339,5 +339,5 @@ Issues can be reported via the [Github issue tracker](https://github.com/SaiFi01 + Saif + CAIO + Rochet2 - + [AIO](https://github.com/Rochet2/AIO) + + [AIO](https://github.com/Rochet2/AIO) + [smallfolk_cpp](https://github.com/Rochet2/smallfolk_cpp) to handle and transmit Lua data in C++ diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 6be703b719ab1..a1bd328be6bb3 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -834,73 +834,73 @@ class TC_GAME_API GroupScript : public ScriptObject // : AIOScript("ExampleScriptName") // { // using namespace std::placeholders; -// +// // // Loads addon files to addons list and sends them on AIO client initialization // // Looks for the file in path config AIO.ClientScriptPath // AddAddon(World::AIOAddon("ExampleAddon", "example_addon.lua")); -// +// // // You can also add addons to be sent to players with specific permission // AddAddon(World::AIOAddon("AnotherAddon", "example_addon.lua", 192)); //192 refers to admin RBAC permission -// +// // // Handler function signature: void HandlerFunction(Player *sender, const LuaVal &args) // AddHandler("Print", std::bind(&ExampleAIOScript::HandlePrint, this, _1, _2)); // AddHandler("Save", std::bind(&ExampleAIOScript::HandleSave, this, _1, _2)); -// +// // // Initialization handler and arguments // AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg, this, _1), std::bind(&ExampleAIOScript::InitArg, this, _1)); // //Adds additional argument to send to handler // AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg2, this, _1)); // AddInitArgs("AnotherScript", "InitB"); //Arguments are not necessary // } -// +// // void HandlePrint(Player *sender, const LuaVal &args) // { // //LuaVal args in a handler function is always a table // //Handler arguments index starts from 4 // LuaVal &InputVal = args[4]; // LuaVal &SliderVal = args[5]; -// +// // //MUST check if the value type is valid or else smallfolk_cpp will // //throw on obtaining that type // if (!InputVal.isstring() || !SliderVal.isnumber()) // { // return; // } -// +// // sender->GetSession()->SendNotification("HandlePrint -> Stored String: %s, Input: %s, Slider Value: %f", // storedString.c_str(), InputVal.str().c_str(), SliderVal.num()); // } -// +// // void HandleSave(Player *sender, const LuaVal &args) // { // //LuaVal args in a handler function is always a table // //Handler arguments index starts from 4 // LuaVal &SaveVal = args.get[4]; -// +// // //MUST check if the value type is valid // if (!SaveVal.isstring()) // { // return; // } -// +// // storedString = SaveVal.str(); // sender->GetSession()->SendNotification("Saved"); // } -// +// // LuaVal InitArg(Player *sender) // { // LuaVal arg = LuaVal(TTABLE); // arg.set("key", 12.3); // arg["key2"] = false; -// +// // return arg; // } -// +// // LuaVal InitArg2(Player *sender) // { // return "LuaVal will implicitly create a string LuaVal for this arg"; // } -// +// // private: // std::string storedString; // }; From a2973250b96432bd550452dd056ce89e9d26802c Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Mon, 1 Jun 2026 03:44:12 +0300 Subject: [PATCH 15/33] Style: align CAIO indentation with TrinityCore conventions Use indented access specifiers in docs and examples, single-line for-loops where split lines misaligned, and consistent AIOMsg/AIOScript formatting. --- CAIO_README.md | 275 +++++---------------- src/server/game/Chat/AIOMsg.cpp | 4 +- src/server/game/Chat/AIOMsg.h | 40 ++- src/server/game/Entities/Player/Player.cpp | 2 +- src/server/game/Entities/Player/Player.h | 2 +- src/server/game/Scripting/ScriptMgr.cpp | 14 +- src/server/game/Scripting/ScriptMgr.h | 112 ++++----- src/server/game/World/World.cpp | 14 +- src/server/scripts/Commands/cs_caio.cpp | 20 +- 9 files changed, 157 insertions(+), 326 deletions(-) diff --git a/CAIO_README.md b/CAIO_README.md index ec95a13d3be31..e439d63579593 100644 --- a/CAIO_README.md +++ b/CAIO_README.md @@ -59,80 +59,29 @@ cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DSCRIPTS=static -DTOOLS=0 ```cpp class ExampleCAIOScript : public AIOScript { -public: - ExampleCAIOScript() - : AIOScript("ExampleScriptName") - { - using namespace std::placeholders; - - // Loads addon files to addons list and sends them on AIO client initialization - // Looks for the file in path config AIO.ClientScriptPath - AddAddon(World::AIOAddon("ExampleAddon", "example_addon.lua")); - - // You can also add addons to be sent to players with specific permission - AddAddon(World::AIOAddon("AnotherAddon", "example_addon.lua", 192)); //192 refers to gm level 3 RBAC permission - - // Handler function signature: void HandlerFunction(Player *sender, const LuaVal &args) - AddHandler("Print", std::bind(&ExampleCAIOScript::HandlePrint, this, _1, _2)); - AddHandler("Save", std::bind(&ExampleCAIOScript::HandleSave, this, _1, _2)); - - // Initialization handler and arguments - AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleCAIOScript::InitArg, this, _1), std::bind(&ExampleCAIOScript::InitArg, this, _1)); - //Adds additional argument to send to handler - AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleCAIOScript::InitArg2, this, _1)); - AddInitArgs("AnotherScript", "InitB"); //Arguments are not necessary - } - - void HandlePrint(Player *sender, const LuaVal &args) - { - //LuaVal args in a handler function is always a table - //Handler arguments index starts from 4 - LuaVal &InputVal = args[4]; - LuaVal &SliderVal = args[5]; - - //MUST check if the value type is valid or else smallfolk_cpp will - //throw on obtaining that type - if(!InputVal.isstring() || !SliderVal.isnumber()) + public: + ExampleCAIOScript() + : AIOScript("ExampleScriptName") { - return; - } + using namespace std::placeholders; - sender->GetSession()->SendNotification("HandlePrint -> Stored String: %s, Input: %s, Slider Value: %f", - storedString.c_str(), InputVal.str().c_str(), SliderVal.num()); - } + // Loads addon files; path from AIO.ClientScriptPath in worldserver.conf + AddAddon("ExampleAddon", "example_addon.lua"); + AddAddon("AnotherAddon", "example_addon.lua", 192); - void HandleSave(Player *sender, const LuaVal &args) - { - //LuaVal args in a handler function is always a table - //Handler arguments index starts from 4 - LuaVal &SaveVal = args[4]; + AddHandler("Print", std::bind(&ExampleCAIOScript::HandlePrint, this, _1, _2)); + AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleCAIOScript::InitArg, this, _1)); + } - //MUST check if the value type is valid - if(!SaveVal.isstring()) + void HandlePrint(Player* sender, LuaVal const& args) { - return; + LuaVal inputVal = args.get(4); + if (!inputVal.isstring()) + return; } - storedString = SaveVal.str(); - sender->GetSession()->SendNotification("Saved"); - } - - LuaVal InitArg(Player *sender) - { - LuaVal arg = LuaVal(TTABLE); - arg.set("key", 12.3); - arg["key2"] = false; - - return arg; - } - - LuaVal InitArg2(Player *sender) - { - return "LuaVal will implicitly create a string LuaVal for this arg"; - } - -private: - std::string storedString; + private: + std::string storedString; }; ``` @@ -149,43 +98,21 @@ Use `LuaVal::nil` (not `LuaVal::nil()`) for default optional arguments. Type tag ```cpp class AIOScript : public ScriptObject { -public: - virtual ~AIOScript(); - - // Returns the key of this CAIO script - LuaVal GetKey() const; - -protected: - // Registers an AIO Handler script of scriptName - AIOScript(const LuaVal &scriptKey); - - // Registers a handler function to call when handling - // handleKey of this script. - void AddHandler(const LuaVal &handlerKey, HandlerFunc function); - - // Adds a client side handler to call and adds arguments - // to sends with it for AIO client initialization. - // - // You can add additional arguments to the handler by - // calling this function again - void AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, - ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), - ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); - - // Adds a WoW addon file to the list of addons with a unique - // addon key to send on AIO client initialization. - // Returns true if addon was added, false if addon key is taken. - // - // It is required to call World::ForceReloadPlayerAddons() - // if addons are added after server is fully initialized - // for online players to load the added addons. - bool AddAddon(const World::AIOAddon &addon); - - // Returns pointer to an AIO script by its key and typename. - // Returns null if scriptName doesn't exist or typename was incorrect. - template - ScriptClass *GetScript(const LuaVal &key); -} + public: + virtual ~AIOScript(); + LuaVal GetKey() const; + + protected: + AIOScript(LuaVal const& scriptKey); + void AddHandler(LuaVal const& handlerKey, HandlerFunc function); + void AddInitArgs(LuaVal const& scriptKey, LuaVal const& handlerKey, + ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), + ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); + bool AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); + + template + ScriptClass* GetScript(LuaVal const& key); +}; ``` **AIOMsg.h** @@ -193,28 +120,18 @@ protected: ```cpp class AIOMsg { -public: - //Creates an empty AIOMsg - AIOMsg(); - - //Creates a AIO message and adds one block - AIOMsg(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); - - //Adds another block - //Another block will call another handler in one message - AIOMsg &Add(cconst LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); - - //Appends the last block - //You can add additional arguments to the last block - AIOMsg &AppendLast(const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); - - //Returns smallfolk dump of the AIO message - std::string dumps() const; + public: + AIOMsg(); + AIOMsg(LuaVal const& scriptKey, LuaVal const& handlerKey, + LuaVal const& a1 = LuaVal::nil, LuaVal const& a2 = LuaVal::nil, LuaVal const& a3 = LuaVal::nil, + LuaVal const& a4 = LuaVal::nil, LuaVal const& a5 = LuaVal::nil, LuaVal const& a6 = LuaVal::nil); + AIOMsg& Add(LuaVal const& scriptKey, LuaVal const& handlerKey, + LuaVal const& a1 = LuaVal::nil, LuaVal const& a2 = LuaVal::nil, LuaVal const& a3 = LuaVal::nil, + LuaVal const& a4 = LuaVal::nil, LuaVal const& a5 = LuaVal::nil, LuaVal const& a6 = LuaVal::nil); + AIOMsg& AppendLast(LuaVal const& a1 = LuaVal::nil, LuaVal const& a2 = LuaVal::nil, LuaVal const& a3 = LuaVal::nil, + LuaVal const& a4 = LuaVal::nil, LuaVal const& a5 = LuaVal::nil, LuaVal const& a6 = LuaVal::nil); + std::string dumps() const; +}; ``` **Player.h** @@ -222,97 +139,31 @@ public: ```cpp class Player { -public: - // Sends an AIO message to the player - // See: class AIOMsg - void AIOMessage(AIOMsg &msg); - - // Triggers an AIO handler on the client - // To trigger multiple handlers in one message or to send more - // arguments use Player::AIOMessage - void AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil(), const LuaVal &a2 = LuaVal::nil(), const LuaVal &a3 = LuaVal::nil(), - const LuaVal &a4 = LuaVal::nil(), const LuaVal &a5 = LuaVal::nil(), const LuaVal &a6 = LuaVal::nil()); - - // AIO can only understand smallfolk LuaVal::dumps() format - // Handler functions are called by creating a table as below - // { - // {n, ScriptName, HandlerName(optional), Arg1..N(optional) }, - // {n, AnotherScriptName, AnotherHandlerName(optional), Arg1..N(optional) } - // } - // Where n is number of arguments including handler name as an argument - void SendSimpleAIOMessage(const std::string &message); - - // Forces reload on the player AIO addons - // Syncs player AIO addons with server - void ForceReloadAddons(); - - // Force reset on the player AIO addons - // Player AIO addons and addon data is deleted and downloaded again - void ForceResetAddons(); - - bool isAIOInitOnCooldown() const; - void setAIOIntOnCooldown(bool cd); -} + public: + void AIOMessage(AIOMsg& msg); + void AIOHandle(LuaVal const& scriptKey, LuaVal const& handlerKey, + LuaVal const& a1 = LuaVal::nil, LuaVal const& a2 = LuaVal::nil, LuaVal const& a3 = LuaVal::nil, + LuaVal const& a4 = LuaVal::nil, LuaVal const& a5 = LuaVal::nil, LuaVal const& a6 = LuaVal::nil); + void SendSimpleAIOMessage(std::string const& message); + void ForceReloadAddons(); + void ForceResetAddons(); +}; ``` **World.h** ```cpp -// AIOAddon container constructor -// Permission 195 will load the addon on every player -World::AIOAddon::AIOAddon(const std::string &addonName, const std::string &addonFile, uint32 permission = 195); - -// AIO prefix configured in worldserver.conf -std::string World::GetAIOPrefix() cons; - -// AIO client LUA files path configured in worldserver.conf -std::string World::GetAIOClientScriptPath() const; - -// Forces reload on all player AIO addons -// Syncs player AIO addons with server -void World::ForceReloadPlayerAddons(uint32 permission = 195); - -// Forces reset on all player AIO addons -// Player AIO addons and addon data is deleted and downloaded again -void World::ForceResetPlayerAddons(uint32 permission = 195); - -// Sends an AIO message to all players -// See: class AIOMsg -void World::AIOMessageAll(AIOMsg &msg, uint32 permission = 195); - -// Sends a simple string message to all players - -// AIO can only understand smallfolk LuaVal::dumps() format -// Handler functions are called by creating a table as below -// { -// {n, ScriptName, HandlerName(optional), Arg1..N(optional) }, -// {n, AnotherScriptName, AnotherHandlerName(optional), Arg1..N(optional) } -// } -// Where n is number of arguments including handler name as a argument -void World::SendAllSimpleAIOMessage(const std::string &message, uint32 permission = 195); - -// Reloads client side AIO addon files and force reloads -// all player AIO addons -// Returns true if successful, false if an error occurred -bool World::ReloadAddons(); - -// Adds a WoW AIO addon file to the list of addons with a unique -// addon name to send on AIO client initialization. -// Returns true if addon was added, false if addon name is already taken -// -// It is required to call World::ForceReloadPlayerAddons() -// if addons are added after server is fully initialized -// for online players to load the added addons. -bool World::AddAddon(const AIOAddon &addon); - -// Removes an addon from addon list and force reloads affected players -// Returns permission id if an addon was removed, 0 if addon not found -// -// It is required to call World::ForceReloadPlayerAddons() -// if addons are added after server is fully initialized -// for online players to load the added addons. -bool World::RemoveAddon(std::string const& addonName, uint32* permission = nullptr); +struct AIOAddon { /* name, file, permission */ }; + +std::string GetAIOPrefix() const; +std::string GetAIOClientScriptPath() const; +void ForceReloadPlayerAddons(uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); +void ForceResetPlayerAddons(uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); +void AIOMessageAll(AIOMsg& msg, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); +void SendAllSimpleAIOMessage(std::string const& message, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); +bool ReloadAddons(); +bool AddAddon(AIOAddon const& addon); +bool RemoveAddon(std::string const& addonName, uint32* permission = nullptr); ``` ## CAIO game commands diff --git a/src/server/game/Chat/AIOMsg.cpp b/src/server/game/Chat/AIOMsg.cpp index ac781485db278..d4c8a12870ddf 100644 --- a/src/server/game/Chat/AIOMsg.cpp +++ b/src/server/game/Chat/AIOMsg.cpp @@ -5,7 +5,7 @@ AIOMsg::AIOMsg() : _val(TTABLE) { } -AIOMsg &AIOMsg::Add(const LuaVal &scriptKey, const LuaVal &handlerKey, const LuaVal &a1, const LuaVal &a2, const LuaVal &a3, const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) +AIOMsg& AIOMsg::Add(LuaVal const& scriptKey, LuaVal const& handlerKey, LuaVal const& a1, LuaVal const& a2, LuaVal const& a3, LuaVal const& a4, LuaVal const& a5, LuaVal const& a6) { LuaVal block(TTABLE); uint32 nArgs = 1; @@ -50,7 +50,7 @@ AIOMsg &AIOMsg::Add(const LuaVal &scriptKey, const LuaVal &handlerKey, const Lua return *this; } -AIOMsg &AIOMsg::AppendLast(const LuaVal &a1, const LuaVal &a2, const LuaVal &a3, const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) +AIOMsg& AIOMsg::AppendLast(LuaVal const& a1, LuaVal const& a2, LuaVal const& a3, LuaVal const& a4, LuaVal const& a5, LuaVal const& a6) { unsigned int lastBlock = _val.len(); if (!lastBlock) diff --git a/src/server/game/Chat/AIOMsg.h b/src/server/game/Chat/AIOMsg.h index 32d4f43e2ad09..69bdb05969b4f 100644 --- a/src/server/game/Chat/AIOMsg.h +++ b/src/server/game/Chat/AIOMsg.h @@ -8,45 +8,43 @@ class Player; class AIOMsg { public: - //Creates an empty AIOMsg + // Creates an empty AIOMsg AIOMsg(); - //Creates a AIO message and adds one block - AIOMsg(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, - const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil) + // Creates an AIO message and adds one block + AIOMsg(LuaVal const& scriptKey, LuaVal const& handlerKey, + LuaVal const& a1 = LuaVal::nil, LuaVal const& a2 = LuaVal::nil, LuaVal const& a3 = LuaVal::nil, + LuaVal const& a4 = LuaVal::nil, LuaVal const& a5 = LuaVal::nil, LuaVal const& a6 = LuaVal::nil) : _val(TTABLE) { Add(scriptKey, handlerKey, a1, a2, a3, a4, a5, a6); } - AIOMsg(const char* scriptKey, const char* handlerKey, - const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, - const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil) + AIOMsg(char const* scriptKey, char const* handlerKey, + LuaVal const& a1 = LuaVal::nil, LuaVal const& a2 = LuaVal::nil, LuaVal const& a3 = LuaVal::nil, + LuaVal const& a4 = LuaVal::nil, LuaVal const& a5 = LuaVal::nil, LuaVal const& a6 = LuaVal::nil) : _val(TTABLE) { Add(LuaVal(scriptKey), LuaVal(handlerKey), a1, a2, a3, a4, a5, a6); } - //Adds another block - //Another block will call another handler in one message - AIOMsg &Add(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, - const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil); + // Adds another block (calls another handler in one message) + AIOMsg& Add(LuaVal const& scriptKey, LuaVal const& handlerKey, + LuaVal const& a1 = LuaVal::nil, LuaVal const& a2 = LuaVal::nil, LuaVal const& a3 = LuaVal::nil, + LuaVal const& a4 = LuaVal::nil, LuaVal const& a5 = LuaVal::nil, LuaVal const& a6 = LuaVal::nil); - AIOMsg &Add(const char* scriptKey, const char* handlerKey, - const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, - const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil) + AIOMsg& Add(char const* scriptKey, char const* handlerKey, + LuaVal const& a1 = LuaVal::nil, LuaVal const& a2 = LuaVal::nil, LuaVal const& a3 = LuaVal::nil, + LuaVal const& a4 = LuaVal::nil, LuaVal const& a5 = LuaVal::nil, LuaVal const& a6 = LuaVal::nil) { return Add(LuaVal(scriptKey), LuaVal(handlerKey), a1, a2, a3, a4, a5, a6); } - //Appends the last block - //You can add additional arguments to the last block - AIOMsg &AppendLast(const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, - const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil); + // Appends arguments to the last block + AIOMsg& AppendLast(LuaVal const& a1 = LuaVal::nil, LuaVal const& a2 = LuaVal::nil, LuaVal const& a3 = LuaVal::nil, + LuaVal const& a4 = LuaVal::nil, LuaVal const& a5 = LuaVal::nil, LuaVal const& a6 = LuaVal::nil); - //Returns smallfolk dump of the AIO message + // Returns smallfolk dump of the AIO message std::string dumps() const { return _val.dumps(); } private: diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 5bf430e405667..735fc85569b65 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -20573,7 +20573,7 @@ void Player::Whisper(uint32 textId, Player* target, bool /*isBossWhisper*/) target->SendDirectMessage(packet.Write()); } -void Player::AIOMessage(AIOMsg &msg) +void Player::AIOMessage(AIOMsg& msg) { SendSimpleAIOMessage(msg.dumps()); } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 628275d7bf8f6..d4e501c6ff5a0 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1073,7 +1073,7 @@ class TC_GAME_API Player : public Unit, public GridObject // Sends an AIO message to the player // See: class AIOMsg - void AIOMessage(AIOMsg &msg); + void AIOMessage(AIOMsg& msg); // Triggers an AIO handler on the client // To trigger multiple handlers in one message or to send more diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 01eeb4be67a7a..bfa5e947ac162 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -2609,8 +2609,8 @@ void ScriptMgr::OnAddonMessage(Player* sender, std::string const& message) if (nArgsVal.num() > 15.0) { sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, - "AIO: Block from '%s' has over 15 arguments (n=%.0f). Sender: %s", - scriptKeyVal.tostring().c_str(), nArgsVal.num(), sender->GetName().c_str()); + "AIO: Block from '%s' has over 15 arguments (n=%.0f). Sender: %s", scriptKeyVal.tostring().c_str(), + nArgsVal.num(), sender->GetName().c_str()); continue; } @@ -2633,15 +2633,13 @@ AIOScript::AIOScript(LuaVal const& scriptKey) void AIOScript::AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, ArgFunc a1, ArgFunc a2, ArgFunc a3, ArgFunc a4, ArgFunc a5, ArgFunc a6) { - AIOHandlers *handler = sScriptMgr->_aioHandlers; + AIOHandlers* handler = sScriptMgr->_aioHandlers; if (!handler) return; // Look for hook std::list* list = nullptr; - for (AIOHandlers::HookListType::iterator itr = handler->_initHookList.begin(); - itr != handler->_initHookList.end(); - ++itr) + for (AIOHandlers::HookListType::iterator itr = handler->_initHookList.begin(); itr != handler->_initHookList.end(); ++itr) { if (itr->scriptKey == scriptKey && itr->handlerKey == handlerKey) { @@ -2736,9 +2734,7 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) LuaVal argsToSend(TTABLE); uint32 blockIndex = 1; - for (HookListType::const_iterator itr = _initHookList.begin(); - itr != _initHookList.end(); - ++itr) + for (HookListType::const_iterator itr = _initHookList.begin(); itr != _initHookList.end(); ++itr) { uint32 index = 3; LuaVal hookBlock(TTABLE); diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index a1bd328be6bb3..ee737860fc4fb 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -829,80 +829,72 @@ class TC_GAME_API GroupScript : public ScriptObject // // class ExampleAIOScript : public AIOScript // { -// public: -// ExampleAIOScript() -// : AIOScript("ExampleScriptName") -// { -// using namespace std::placeholders; -// -// // Loads addon files to addons list and sends them on AIO client initialization -// // Looks for the file in path config AIO.ClientScriptPath -// AddAddon(World::AIOAddon("ExampleAddon", "example_addon.lua")); +// public: +// ExampleAIOScript() +// : AIOScript("ExampleScriptName") +// { +// using namespace std::placeholders; // -// // You can also add addons to be sent to players with specific permission -// AddAddon(World::AIOAddon("AnotherAddon", "example_addon.lua", 192)); //192 refers to admin RBAC permission +// // Loads addon files to addons list and sends them on AIO client initialization +// // Looks for the file in path config AIO.ClientScriptPath +// AddAddon("ExampleAddon", "example_addon.lua"); // -// // Handler function signature: void HandlerFunction(Player *sender, const LuaVal &args) -// AddHandler("Print", std::bind(&ExampleAIOScript::HandlePrint, this, _1, _2)); -// AddHandler("Save", std::bind(&ExampleAIOScript::HandleSave, this, _1, _2)); +// // You can also add addons to be sent to players with specific permission +// AddAddon("AnotherAddon", "example_addon.lua", 192); // 192 refers to admin RBAC permission // -// // Initialization handler and arguments -// AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg, this, _1), std::bind(&ExampleAIOScript::InitArg, this, _1)); -// //Adds additional argument to send to handler -// AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg2, this, _1)); -// AddInitArgs("AnotherScript", "InitB"); //Arguments are not necessary -// } +// // Handler function signature: void HandlerFunction(Player* sender, LuaVal const& args) +// AddHandler("Print", std::bind(&ExampleAIOScript::HandlePrint, this, _1, _2)); +// AddHandler("Save", std::bind(&ExampleAIOScript::HandleSave, this, _1, _2)); // -// void HandlePrint(Player *sender, const LuaVal &args) -// { -// //LuaVal args in a handler function is always a table -// //Handler arguments index starts from 4 -// LuaVal &InputVal = args[4]; -// LuaVal &SliderVal = args[5]; +// // Initialization handler and arguments +// AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg, this, _1), std::bind(&ExampleAIOScript::InitArg, this, _1)); +// // Adds additional argument to send to handler +// AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg2, this, _1)); +// AddInitArgs("AnotherScript", "InitB"); // Arguments are not necessary +// } // -// //MUST check if the value type is valid or else smallfolk_cpp will -// //throw on obtaining that type -// if (!InputVal.isstring() || !SliderVal.isnumber()) +// void HandlePrint(Player* sender, LuaVal const& args) // { -// return; -// } +// // LuaVal args in a handler function is always a table +// // Handler arguments index starts from 4 +// LuaVal inputVal = args.get(4); +// LuaVal sliderVal = args.get(5); // -// sender->GetSession()->SendNotification("HandlePrint -> Stored String: %s, Input: %s, Slider Value: %f", -// storedString.c_str(), InputVal.str().c_str(), SliderVal.num()); -// } +// // MUST check if the value type is valid or else smallfolk_cpp will +// // throw on obtaining that type +// if (!inputVal.isstring() || !sliderVal.isnumber()) +// return; // -// void HandleSave(Player *sender, const LuaVal &args) -// { -// //LuaVal args in a handler function is always a table -// //Handler arguments index starts from 4 -// LuaVal &SaveVal = args.get[4]; +// sender->GetSession()->SendNotification("HandlePrint -> Stored String: %s, Input: %s, Slider Value: %f", +// storedString.c_str(), inputVal.str().c_str(), sliderVal.num()); +// } // -// //MUST check if the value type is valid -// if (!SaveVal.isstring()) +// void HandleSave(Player* sender, LuaVal const& args) // { -// return; -// } +// LuaVal saveVal = args.get(4); // -// storedString = SaveVal.str(); -// sender->GetSession()->SendNotification("Saved"); -// } +// if (!saveVal.isstring()) +// return; // -// LuaVal InitArg(Player *sender) -// { -// LuaVal arg = LuaVal(TTABLE); -// arg.set("key", 12.3); -// arg["key2"] = false; +// storedString = saveVal.str(); +// sender->GetSession()->SendNotification("Saved"); +// } // -// return arg; -// } +// LuaVal InitArg(Player* /*sender*/) +// { +// LuaVal arg(TTABLE); +// arg.set("key", 12.3); +// arg["key2"] = false; +// return arg; +// } // -// LuaVal InitArg2(Player *sender) -// { -// return "LuaVal will implicitly create a string LuaVal for this arg"; -// } +// LuaVal InitArg2(Player* /*sender*/) +// { +// return LuaVal("LuaVal will implicitly create a string LuaVal for this arg"); +// } // -// private: -// std::string storedString; +// private: +// std::string storedString; // }; class AIOScript : public ScriptObject { @@ -974,7 +966,7 @@ class AIOHandlers : public AIOScript LuaVal handlerKey; std::list argsList; - InitHookInfo(const LuaVal &scriptKey, const LuaVal &handlerKey) + InitHookInfo(LuaVal const& scriptKey, LuaVal const& handlerKey) : scriptKey(scriptKey), handlerKey(handlerKey) { } }; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index fb1b249a2eaf9..f51ae91d89f75 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -3611,9 +3611,7 @@ bool World::AddAddon(AIOAddon const& addon) return false; // Check if addon already exist - for (AddonCodeListType::iterator itr = m_AddonList.begin(); - itr != m_AddonList.end(); - ++itr) + for (AddonCodeListType::iterator itr = m_AddonList.begin(); itr != m_AddonList.end(); ++itr) { if (itr->name == addon.name) { @@ -3689,9 +3687,7 @@ bool World::ReloadAddons() prevAddonList.swap(m_AddonList); try { - for (AddonCodeListType::const_iterator itr = prevAddonList.begin(); - itr != prevAddonList.end(); - ++itr) + for (AddonCodeListType::const_iterator itr = prevAddonList.begin(); itr != prevAddonList.end(); ++itr) { AddAddon(*itr); } @@ -3717,9 +3713,7 @@ size_t World::PrepareClientAddons(LuaVal const& clientData, LuaVal& addonsTable, return 0; uint32 i = 0; - for (AddonCodeListType::const_iterator itr = m_AddonList.begin(); - itr != m_AddonList.end(); - ++itr) + for (AddonCodeListType::const_iterator itr = m_AddonList.begin(); itr != m_AddonList.end(); ++itr) { if (!forPlayer->GetSession()->HasPermission(itr->permission)) continue; @@ -3759,7 +3753,7 @@ void World::ForceResetPlayerAddons(uint32 permission) } } -void World::AIOMessageAll(AIOMsg &msg, uint32 permission) +void World::AIOMessageAll(AIOMsg& msg, uint32 permission) { std::string messageStr = msg.dumps(); for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) diff --git a/src/server/scripts/Commands/cs_caio.cpp b/src/server/scripts/Commands/cs_caio.cpp index b6b0f9a8ca24d..76aa4947178b8 100644 --- a/src/server/scripts/Commands/cs_caio.cpp +++ b/src/server/scripts/Commands/cs_caio.cpp @@ -22,16 +22,16 @@ class caio_commandscript : public CommandScript { static std::vector caioCommandTable = { - { "version", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleVersionCommand, "" }, - { "send", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleSendCommand, "" }, - { "forcereload", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadCommand, "" }, - { "forcereset", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleResetCommand, "" }, - { "sendall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleSendAllCommand, "" }, - { "forcereloadall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadAllCommand, "" }, - { "forceresetall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleResetAllCommand, "" }, - { "reloadaddons", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadAddonsCommand, "" }, - { "addaddon", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleAddAddonCommand, "" }, - { "removeaddon", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleRemoveAddonCommand, "" }, + { "version", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleVersionCommand, "" }, + { "send", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleSendCommand, "" }, + { "forcereload", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadCommand, "" }, + { "forcereset", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleResetCommand, "" }, + { "sendall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleSendAllCommand, "" }, + { "forcereloadall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadAllCommand, "" }, + { "forceresetall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleResetAllCommand, "" }, + { "reloadaddons", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadAddonsCommand, "" }, + { "addaddon", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleAddAddonCommand, "" }, + { "removeaddon", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleRemoveAddonCommand, "" }, }; static std::vector commandTable = From 4230c2a0cb8fc71bc5ec352d2355868ed54a2645 Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Mon, 1 Jun 2026 03:44:24 +0300 Subject: [PATCH 16/33] Style: restore multi-line AIO log message formatting --- src/server/game/Scripting/ScriptMgr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index bfa5e947ac162..0f83fce0156a1 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -2609,8 +2609,8 @@ void ScriptMgr::OnAddonMessage(Player* sender, std::string const& message) if (nArgsVal.num() > 15.0) { sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, - "AIO: Block from '%s' has over 15 arguments (n=%.0f). Sender: %s", scriptKeyVal.tostring().c_str(), - nArgsVal.num(), sender->GetName().c_str()); + "AIO: Block from '%s' has over 15 arguments (n=%.0f). Sender: %s", + scriptKeyVal.tostring().c_str(), nArgsVal.num(), sender->GetName().c_str()); continue; } From c2ca5ae7b27c92ab817c3525c2e9ca300a79c33d Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Mon, 1 Jun 2026 04:08:00 +0300 Subject: [PATCH 17/33] Fix GCC CI: LuaVal init block and sign-compare warnings. PrepareClientAddons returns size_t; cast addon count for LuaVal. Add shared AIO.h and silence -Wsign-compare in AIO paths. --- src/server/game/AIO/AIO.h | 31 ++++++++++++++++++++++ src/server/game/Chat/AIOMsg.cpp | 2 +- src/server/game/Entities/Player/Player.cpp | 2 +- src/server/game/Handlers/ChatHandler.cpp | 2 +- src/server/game/Scripting/ScriptMgr.cpp | 8 +++--- src/server/game/Scripting/ScriptMgr.h | 3 +-- src/server/game/World/World.h | 8 +----- 7 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 src/server/game/AIO/AIO.h diff --git a/src/server/game/AIO/AIO.h b/src/server/game/AIO/AIO.h new file mode 100644 index 0000000000000..96c67f3f9989f --- /dev/null +++ b/src/server/game/AIO/AIO.h @@ -0,0 +1,31 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#ifndef TRINITY_AIO_H +#define TRINITY_AIO_H + +#include "Define.h" + +#define AIO_VERSION 1.75 +#define AIO_VERSION_STRING "1.75" + +// RBAC permission id used when distributing addons/messages to players (see sql/CAIO/Auth.sql) +constexpr uint32 AIO_DEFAULT_ADDON_PERMISSION = 195; +// WoW addon whisper payload limit (matches client AIO.lua when AIO_SERVER is false) +constexpr uint32 AIO_MAX_WHISPER_LENGTH = 255; + +#endif diff --git a/src/server/game/Chat/AIOMsg.cpp b/src/server/game/Chat/AIOMsg.cpp index d4c8a12870ddf..81900b1201137 100644 --- a/src/server/game/Chat/AIOMsg.cpp +++ b/src/server/game/Chat/AIOMsg.cpp @@ -61,7 +61,7 @@ AIOMsg& AIOMsg::AppendLast(LuaVal const& a1, LuaVal const& a2, LuaVal const& a3, if (!nArgsVal.isnumber()) return *this; - unsigned int nArgs = nArgsVal.num(); + unsigned int nArgs = static_cast(nArgsVal.num()); if (!a1.isnil()) { block.insert(a1); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 735fc85569b65..cfa7f50a1883c 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -20594,7 +20594,7 @@ void Player::SendSimpleAIOMessage(std::string const& message) uint32 const shortHeaderLen = 1 + uint32(aioPrefix.size()) + 1 + 2; // S + prefix + tab + short id uint32 const longHeaderLen = 1 + uint32(aioPrefix.size()) + 1 + 6; // S + prefix + tab + long meta - if (shortHeaderLen + message.size() <= maxPacketLen) + if (shortHeaderLen + message.size() <= size_t(maxPacketLen)) { std::string fullmsg = "S" + aioPrefix + "\t\x1\x1" + message; WorldPackets::Chat::Chat packet; diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index 946492f70dad1..4e7f19ab12565 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -361,7 +361,7 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms messagePartsItr->second.Map[partId] = std::move(partPayload); - bool haveAllParts = messagePartsItr->second.Map.size() >= messagePartsItr->second.Parts; + bool haveAllParts = messagePartsItr->second.Map.size() >= static_cast(messagePartsItr->second.Parts); if (haveAllParts) { for (uint32 expectedPart = 1; expectedPart <= parts; ++expectedPart) diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 0f83fce0156a1..867ecfb415d6f 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -2594,7 +2594,7 @@ void ScriptMgr::OnAddonMessage(Player* sender, std::string const& message) return; // Call handlers from all blocks in order - for (size_t i = 1; i <= mainTable.len(); ++i) + for (unsigned int i = 1; i <= mainTable.len(); ++i) { LuaVal block = mainTable.get(static_cast(i)); if (!block.istable()) @@ -2729,7 +2729,7 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) LuaVal addonTable(TTABLE); LuaVal cacheTable(TTABLE); - uint32 nAddons = sWorld->PrepareClientAddons(clientDataVal, addonTable, cacheTable, sender); + size_t const nAddons = sWorld->PrepareClientAddons(clientDataVal, addonTable, cacheTable, sender); LuaVal argsToSend(TTABLE); @@ -2739,7 +2739,7 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) uint32 index = 3; LuaVal hookBlock(TTABLE); - hookBlock[1] = uint32(itr->argsList.size() + 1); + hookBlock[1] = static_cast(itr->argsList.size() + 1); hookBlock[2] = itr->scriptKey; hookBlock[3] = itr->handlerKey; for (std::list::const_iterator it = itr->argsList.begin(); it != itr->argsList.end(); ++it) @@ -2753,7 +2753,7 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) AIOInitBlock[2] = "AIO"; AIOInitBlock[3] = "Init"; AIOInitBlock[4] = AIO_VERSION; - AIOInitBlock[5] = nAddons; + AIOInitBlock[5] = static_cast(nAddons); AIOInitBlock[6] = addonTable; AIOInitBlock[7] = cacheTable; diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index ee737860fc4fb..af95cf98059ef 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -25,10 +25,9 @@ #include #include +#include "AIO.h" #include "smallfolk.h" -#include "World.h" - class AccountMgr; class AuctionHouseObject; class Aura; diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index bb1cce9d7d5e8..89741eb4bc4a6 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -35,13 +35,7 @@ #include #include -#define AIO_VERSION 1.75 -#define AIO_VERSION_STRING "1.75" - -// RBAC permission id used when distributing addons/messages to players (see sql/CAIO/Auth.sql) -constexpr uint32 AIO_DEFAULT_ADDON_PERMISSION = 195; -// WoW addon whisper payload limit (matches client AIO.lua when AIO_SERVER is false) -constexpr uint32 AIO_MAX_WHISPER_LENGTH = 255; +#include "AIO.h" class Object; class Player; From b9f17b2043ddf9d7dad77918c1f9acc2865f3487 Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Mon, 1 Jun 2026 10:34:42 +0300 Subject: [PATCH 18/33] Fix GCC CI: more sign-compare and LuaVal index fixes. --- src/server/game/Chat/AIOMsg.cpp | 2 +- src/server/game/Handlers/ChatHandler.cpp | 6 +++--- src/server/game/Scripting/ScriptMgr.cpp | 8 ++++---- src/server/game/World/World.cpp | 4 ++-- src/server/game/World/World.h | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/server/game/Chat/AIOMsg.cpp b/src/server/game/Chat/AIOMsg.cpp index 81900b1201137..af437c6b2cf17 100644 --- a/src/server/game/Chat/AIOMsg.cpp +++ b/src/server/game/Chat/AIOMsg.cpp @@ -56,7 +56,7 @@ AIOMsg& AIOMsg::AppendLast(LuaVal const& a1, LuaVal const& a2, LuaVal const& a3, if (!lastBlock) return *this; - LuaVal &block = _val[lastBlock]; + LuaVal& block = _val.at(static_cast(lastBlock)); LuaVal nArgsVal = block.get(1); if (!nArgsVal.isnumber()) return *this; diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index 4e7f19ab12565..6a84204317844 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -179,7 +179,7 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms return; } - if (msg.size() > 255) + if (msg.size() > 255u) return; // Our Warden module also uses SendAddonMessage as a way to communicate Lua check results to the server, see if this is that @@ -306,7 +306,7 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms return; uint16 messageId = 0; - if ((msg.size() - delimPos - 1) >= 2) + if ((msg.size() - delimPos - 1) >= size_t(2)) { messageId = (msg[delimPos + 1] - 1) * 254 + msg[delimPos + 2] - 1; @@ -317,7 +317,7 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms } } - if ((msg.size() - delimPos - 1) >= 6) + if ((msg.size() - delimPos - 1) >= size_t(6)) { uint32 parts = (msg[delimPos + 3] - 1) * 254 + msg[delimPos + 4] - 1; if (parts < 2) diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 867ecfb415d6f..7d1349b9bc0f6 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -2624,7 +2624,7 @@ AIOScript::AIOScript(LuaVal const& scriptKey) { if (AIOScript::_scriptByKeyMap.find(scriptKey) != AIOScript::_scriptByKeyMap.end()) { - sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '%s' of type tag '%i' already exist. Use another key.", scriptKey.tostring().c_str(), scriptKey.typetag()); + sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '%s' of type tag '%i' already exist. Use another key.", scriptKey.tostring().c_str(), static_cast(scriptKey.typetag())); ASSERT(false); } ScriptRegistry::Instance()->AddScript(this); @@ -2729,7 +2729,7 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) LuaVal addonTable(TTABLE); LuaVal cacheTable(TTABLE); - size_t const nAddons = sWorld->PrepareClientAddons(clientDataVal, addonTable, cacheTable, sender); + uint32 const nAddons = sWorld->PrepareClientAddons(clientDataVal, addonTable, cacheTable, sender); LuaVal argsToSend(TTABLE); @@ -2739,7 +2739,7 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) uint32 index = 3; LuaVal hookBlock(TTABLE); - hookBlock[1] = static_cast(itr->argsList.size() + 1); + hookBlock[1] = static_cast(itr->argsList.size() + 1); hookBlock[2] = itr->scriptKey; hookBlock[3] = itr->handlerKey; for (std::list::const_iterator it = itr->argsList.begin(); it != itr->argsList.end(); ++it) @@ -2753,7 +2753,7 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) AIOInitBlock[2] = "AIO"; AIOInitBlock[3] = "Init"; AIOInitBlock[4] = AIO_VERSION; - AIOInitBlock[5] = static_cast(nAddons); + AIOInitBlock[5] = nAddons; AIOInitBlock[6] = addonTable; AIOInitBlock[7] = cacheTable; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index f51ae91d89f75..6a708bb6506ac 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1578,7 +1578,7 @@ void World::LoadConfigSettings(bool reload) m_int_configs[CONFIG_AIO_MAX_BUFFER_SIZE] = sConfigMgr->GetIntDefault("AIO.MaxBufferSize", 1048576); m_aioclientpath = sConfigMgr->GetStringDefault("AIO.ClientScriptPath", "lua_client_scripts"); m_aioprefix = sConfigMgr->GetStringDefault("AIO.Prefix", "AIO"); - if (m_aioprefix.size() > 15) + if (m_aioprefix.size() > 15u) m_aioprefix = m_aioprefix.substr(0, 15); // HotSwap @@ -3707,7 +3707,7 @@ bool World::ReloadAddons() return true; } -size_t World::PrepareClientAddons(LuaVal const& clientData, LuaVal& addonsTable, LuaVal& cacheTable, Player* forPlayer) const +uint32 World::PrepareClientAddons(LuaVal const& clientData, LuaVal& addonsTable, LuaVal& cacheTable, Player* forPlayer) const { if (!clientData.istable()) return 0; diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 89741eb4bc4a6..bc5d4d6203e56 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -804,7 +804,7 @@ class TC_GAME_API World bool ReloadAddons(); bool AddAddon(AIOAddon const& addon); bool RemoveAddon(std::string const& addonName, uint32* permission = nullptr); - size_t PrepareClientAddons(LuaVal const& clientData, LuaVal& addonsTable, LuaVal& cacheTable, Player* forPlayer) const; + uint32 PrepareClientAddons(LuaVal const& clientData, LuaVal& addonsTable, LuaVal& cacheTable, Player* forPlayer) const; void RemoveOldCorpses(); void TriggerGuidWarning(); From 99b759df27355368e1733089c04c3583a654af63 Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Tue, 2 Jun 2026 02:01:11 +0300 Subject: [PATCH 19/33] CAIO: hygiene, modern commands, refactor AIO whisper handling - Revert unrelated upstream diffs; restore gossip world SQL update - Migrate .caio to ChatCommandTable; fmt outAIOMessage logs; CI builds examples - Extract HandleIncomingAIOClientWhisper; trim ScriptMgr.h; add script example doc --- .github/workflows/gcc-build.yml | 2 +- CAIO_README.md | 2 +- doc/CAIO_MESSAGE_FORMAT.md | 2 +- doc/CAIO_SCRIPT_EXAMPLE.md | 71 +++++++ .../world/3.3.5/2026_05_31_00_world.sql | 3 + .../game/Achievements/AchievementMgr.cpp | 2 +- src/server/game/Entities/Player/Player.cpp | 3 +- src/server/game/Entities/Player/Player.h | 5 - src/server/game/Groups/Group.cpp | 2 +- src/server/game/Handlers/ChatHandler.cpp | 102 +--------- src/server/game/Scripting/ScriptMgr.cpp | 37 ++-- src/server/game/Scripting/ScriptMgr.h | 81 +------- src/server/game/Server/WorldSession.cpp | 115 +++++++++++ src/server/game/Server/WorldSession.h | 9 + src/server/game/Server/WorldSocket.cpp | 2 +- src/server/game/World/World.cpp | 18 +- src/server/scripts/Commands/cs_caio.cpp | 188 +++++------------- 17 files changed, 306 insertions(+), 338 deletions(-) create mode 100644 doc/CAIO_SCRIPT_EXAMPLE.md create mode 100644 sql/updates/world/3.3.5/2026_05_31_00_world.sql diff --git a/.github/workflows/gcc-build.yml b/.github/workflows/gcc-build.yml index b36ca72961621..84ece5be934e1 100644 --- a/.github/workflows/gcc-build.yml +++ b/.github/workflows/gcc-build.yml @@ -19,7 +19,7 @@ jobs: run: | mkdir bin cd bin - cmake ../ -DWITH_WARNINGS=1 -DWITH_COREDEBUG=0 -DUSE_COREPCH=1 -DUSE_SCRIPTPCH=1 -DTOOLS=1 -DSCRIPTS=dynamic -DSERVERS=1 -DNOJEM=0 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-Werror" -DCMAKE_CXX_FLAGS="-Werror" -DCMAKE_C_FLAGS_DEBUG="-DNDEBUG" -DCMAKE_CXX_FLAGS_DEBUG="-DNDEBUG" -DCMAKE_INSTALL_PREFIX=check_install -DBUILD_TESTING=1 + cmake ../ -DWITH_WARNINGS=1 -DWITH_COREDEBUG=0 -DUSE_COREPCH=1 -DUSE_SCRIPTPCH=1 -DTOOLS=1 -DSCRIPTS=dynamic -DSERVERS=1 -DNOJEM=0 -DWITH_CAIO_EXAMPLES=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-Werror" -DCMAKE_CXX_FLAGS="-Werror" -DCMAKE_C_FLAGS_DEBUG="-DNDEBUG" -DCMAKE_CXX_FLAGS_DEBUG="-DNDEBUG" -DCMAKE_INSTALL_PREFIX=check_install -DBUILD_TESTING=1 cd .. - name: Build run: | diff --git a/CAIO_README.md b/CAIO_README.md index e439d63579593..f38464ff0cc97 100644 --- a/CAIO_README.md +++ b/CAIO_README.md @@ -14,7 +14,7 @@ AIO version **1.75** — must match `AIO_VERSION` in your server and client `AIO + Clone this repository/branch or merge with your own TrinityCore 3.3.5 branch + `git submodule update --init --recursive` (required for `dep/smallfolk_cpp/smallfolk_cpp`, tracks [smallfolk_cpp](https://github.com/Rochet2/smallfolk_cpp) **master**, currently v2.x) + Build/Install TrinityCore -+ [Install(Add) (C)AIO scripts](#api-reference) ++ [Install(Add) (C)AIO scripts](#api-reference) — see also `doc/CAIO_SCRIPT_EXAMPLE.md` + Run SQL files from `TrinityCore_Installation_Dir/sql/CAIO` (`Auth.sql` on auth DB, `World.sql` on world DB) + Copy `AIO_Client` from your AIO tree to `WoW_Installation_Dir/Interface/AddOns/AIO_Client` (use the same AIO repo/commit as the server expects) + Copy server-side client addon sources into `TrinityCore_Installation_Dir/lua_client_scripts` (one folder per addon, e.g. `lua_client_scripts/ExampleWindow/ExampleWindow.lua`) diff --git a/doc/CAIO_MESSAGE_FORMAT.md b/doc/CAIO_MESSAGE_FORMAT.md index 9935990b1490a..31b647cccff4f 100644 --- a/doc/CAIO_MESSAGE_FORMAT.md +++ b/doc/CAIO_MESSAGE_FORMAT.md @@ -30,7 +30,7 @@ On TrinityCore, AIO uses **`CHAT_MSG_WHISPER` with `LANG_ADDON`** (not a separat ## Version handshake -On init, client sends `AIO` / `Init` with protocol version **1.75**. Server defines `AIO_VERSION` / `AIO_VERSION_STRING` in `World.h`. +On init, client sends `AIO` / `Init` with protocol version **1.75**. Server defines `AIO_VERSION` / `AIO_VERSION_STRING` in `src/server/game/AIO/AIO.h`. ## Compression / obfuscation diff --git a/doc/CAIO_SCRIPT_EXAMPLE.md b/doc/CAIO_SCRIPT_EXAMPLE.md new file mode 100644 index 0000000000000..5a4a82fa3ea0a --- /dev/null +++ b/doc/CAIO_SCRIPT_EXAMPLE.md @@ -0,0 +1,71 @@ +# CAIO script example + +Inherit `AIOScript` to register server-side AIO handlers. See [smallfolk_cpp](https://github.com/Rochet2/smallfolk_cpp) for `LuaVal` usage. + +```cpp +class ExampleAIOScript : public AIOScript +{ + public: + ExampleAIOScript() + : AIOScript("ExampleScriptName") + { + using namespace std::placeholders; + + // Loads addon files; path from AIO.ClientScriptPath in worldserver.conf + AddAddon("ExampleAddon", "example_addon.lua"); + + // Optional permission-gated addon (RBAC id) + AddAddon("AnotherAddon", "example_addon.lua", 192); + + AddHandler("Print", std::bind(&ExampleAIOScript::HandlePrint, this, _1, _2)); + AddHandler("Save", std::bind(&ExampleAIOScript::HandleSave, this, _1, _2)); + + AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg, this, _1), std::bind(&ExampleAIOScript::InitArg, this, _1)); + AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg2, this, _1)); + AddInitArgs("AnotherScript", "InitB"); + } + + void HandlePrint(Player* sender, LuaVal const& args) + { + LuaVal inputVal = args.get(4); + LuaVal sliderVal = args.get(5); + + if (!inputVal.isstring() || !sliderVal.isnumber()) + return; + + sender->GetSession()->SendNotification("HandlePrint -> Stored String: %s, Input: %s, Slider Value: %f", + storedString.c_str(), inputVal.str().c_str(), sliderVal.num()); + } + + void HandleSave(Player* sender, LuaVal const& args) + { + LuaVal saveVal = args.get(4); + + if (!saveVal.isstring()) + return; + + storedString = saveVal.str(); + sender->GetSession()->SendNotification("Saved"); + } + + LuaVal InitArg(Player* /*sender*/) + { + LuaVal arg(TTABLE); + arg.set("key", 12.3); + arg["key2"] = false; + return arg; + } + + LuaVal InitArg2(Player* /*sender*/) + { + return LuaVal("LuaVal will implicitly create a string LuaVal for this arg"); + } + + private: + std::string storedString; +}; +``` + +Register the script in a loader (see `src/server/scripts/AIO/aio_script_loader.cpp` and `WITH_CAIO_EXAMPLES`). + +Handler `args` is always a table; handler parameters start at index **4**. Always check value types before use. diff --git a/sql/updates/world/3.3.5/2026_05_31_00_world.sql b/sql/updates/world/3.3.5/2026_05_31_00_world.sql new file mode 100644 index 0000000000000..0398a8c84df7d --- /dev/null +++ b/sql/updates/world/3.3.5/2026_05_31_00_world.sql @@ -0,0 +1,3 @@ +UPDATE `creature_template` SET `gossip_menu_id`=7169 WHERE `entry`=16237; +UPDATE `gossip_menu` SET `MenuID`=7169 WHERE `MenuID`=57031; +UPDATE `conditions` SET `SourceGroup`=7169 WHERE `SourceTypeOrReferenceId`=14 AND `SourceGroup`=57031; diff --git a/src/server/game/Achievements/AchievementMgr.cpp b/src/server/game/Achievements/AchievementMgr.cpp index 109ee47a5957f..d71d6e0d1dc25 100644 --- a/src/server/game/Achievements/AchievementMgr.cpp +++ b/src/server/game/Achievements/AchievementMgr.cpp @@ -379,7 +379,7 @@ bool AchievementCriteriaData::Meets(uint32 criteria_id, Player const* source, Wo Unit const* unitTarget = target->ToUnit(); if (!unitTarget) return false; - return unitTarget->GetGender() == Gender(gender.gender); + return unitTarget->GetGender() == static_cast<::Gender>(gender.gender); } case ACHIEVEMENT_CRITERIA_DATA_TYPE_SCRIPT: { diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index cfa7f50a1883c..4a7963d1a1e3f 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -112,7 +112,6 @@ #include "WorldPacket.h" #include "WorldSession.h" #include "AIOMsg.h" -#include "GameObjectAI.h" #include "WorldStatePackets.h" #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) @@ -20555,7 +20554,7 @@ void Player::Whisper(std::string_view text, Language language, Player* target, b ChatHandler(GetSession()).PSendSysMessage(LANG_PLAYER_DND, target->GetName().c_str(), target->autoReplyMsg.c_str()); } -void Player::Whisper(uint32 textId, Player* target, bool /*isBossWhisper*/) +void Player::Whisper(uint32 textId, Player* target, bool /*isBossWhisper = false*/) { if (!target) return; diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index d4e501c6ff5a0..0229012fdeb25 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -36,10 +36,6 @@ #include "smallfolk.h" -#include -#include -#include - struct AccessRequirement; struct AchievementEntry; struct AreaTableEntry; @@ -81,7 +77,6 @@ class ReputationMgr; class SpellCastTargets; class AIOMsg; class TradeData; -class UpdateMask; enum InventoryType : uint8; enum ItemClass : uint8; diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index dfbfb700fab5a..1011ccea29efa 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -63,7 +63,7 @@ Group::Group() : m_leaderGuid(), m_leaderName(""), m_groupType(GROUPTYPE_NORMAL) m_dungeonDifficulty(DUNGEON_DIFFICULTY_NORMAL), m_raidDifficulty(RAID_DIFFICULTY_10MAN_NORMAL), m_bgGroup(nullptr), m_bfGroup(nullptr), m_lootMethod(FREE_FOR_ALL), m_lootThreshold(ITEM_QUALITY_UNCOMMON), m_looterGuid(), m_masterLooterGuid(), m_subGroupsCounts(nullptr), m_guid(), m_counter(0), m_maxEnchantingLevel(0), m_dbStoreId(0), m_isLeaderOffline(false), - m_scriptRef(this, NoopGroupDeleter()) +m_scriptRef(this, NoopGroupDeleter()) { for (uint8 i = 0; i < TARGET_ICONS_COUNT; ++i) m_targetIcons[i].Clear(); diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index 6a84204317844..37a061164ae97 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -296,103 +296,11 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms if (lang == LANG_ADDON && receiver) { - size_t delimPos = msg.find('\t'); - if (delimPos != std::string::npos) - { - std::string prefix = msg.substr(0, delimPos); - if (prefix == "C" + sWorld->GetAIOPrefix()) - { - if (receiver != sender) - return; - - uint16 messageId = 0; - if ((msg.size() - delimPos - 1) >= size_t(2)) - { - messageId = (msg[delimPos + 1] - 1) * 254 + msg[delimPos + 2] - 1; - - if (messageId == 0) - { - sScriptMgr->OnAddonMessage(sender, msg.substr(delimPos + 3)); - break; - } - } - - if ((msg.size() - delimPos - 1) >= size_t(6)) - { - uint32 parts = (msg[delimPos + 3] - 1) * 254 + msg[delimPos + 4] - 1; - if (parts < 2) - { - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "HandleAddonMessagechatOpcode: Received AIO addon message with number of parts: %u (< 2). Sender: %s", parts, sender->GetName().c_str()); - return; - } - - uint32 maxparts = sWorld->getIntConfig(CONFIG_AIO_MAXPARTS); - if (parts > maxparts) - { - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "HandleAddonMessagechatOpcode: Received AIO addon message with too many parts: %u (> %u). Message Id: %u, Sender: %s", parts, maxparts, messageId, sender->GetName().c_str()); - return; - } - - uint32 partId = (msg[delimPos + 5] - 1) * 254 + msg[delimPos + 6] - 1; - if (partId < 1 || partId > parts) - { - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "HandleAddonMessagechatOpcode: Invalid AIO part id %u for %u parts. Message Id: %u, Sender: %s", partId, parts, messageId, sender->GetName().c_str()); - return; - } - - std::string const partPayload = msg.substr(delimPos + 7); - uint32 const maxBufferSize = sWorld->getIntConfig(CONFIG_AIO_MAX_BUFFER_SIZE); - - AddonMessageBufferMap::iterator messagePartsItr = _addonMessageBuffer.find(messageId); - if (messagePartsItr == _addonMessageBuffer.end()) - messagePartsItr = _addonMessageBuffer.insert(std::make_pair(messageId, LongMessageBufferInfo())).first; - else if (parts != messagePartsItr->second.Parts) - messagePartsItr->second = LongMessageBufferInfo(); - - messagePartsItr->second.Parts = parts; - messagePartsItr->second.Timer = 0; - messagePartsItr->second.BufferedBytes += uint32(partPayload.size()); - if (messagePartsItr->second.BufferedBytes > maxBufferSize) - { - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "HandleAddonMessagechatOpcode: AIO reassembly buffer exceeded %u bytes. Message Id: %u, Sender: %s", maxBufferSize, messageId, sender->GetName().c_str()); - _addonMessageBuffer.erase(messagePartsItr); - return; - } - - messagePartsItr->second.Map[partId] = std::move(partPayload); - - bool haveAllParts = messagePartsItr->second.Map.size() >= static_cast(messagePartsItr->second.Parts); - if (haveAllParts) - { - for (uint32 expectedPart = 1; expectedPart <= parts; ++expectedPart) - { - if (messagePartsItr->second.Map.find(expectedPart) == messagePartsItr->second.Map.end()) - { - haveAllParts = false; - break; - } - } - } - - if (haveAllParts) - { - std::string actualAIOMessage; - actualAIOMessage.reserve(messagePartsItr->second.BufferedBytes); - for (uint32 expectedPart = 1; expectedPart <= parts; ++expectedPart) - actualAIOMessage += messagePartsItr->second.Map.find(expectedPart)->second; - - sScriptMgr->OnAddonMessage(sender, actualAIOMessage); - _addonMessageBuffer.erase(messagePartsItr); - break; - } - - break; - } - - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "HandleAddonMessagechatOpcode: Received AIO long addon message without meta data, Sender: %s", sender->GetName().c_str()); - break; - } - } + IncomingAIOWhisperResult const aioResult = HandleIncomingAIOClientWhisper(sender, receiver, msg); + if (aioResult == IncomingAIOWhisperResult::DropPacket) + return; + if (aioResult == IncomingAIOWhisperResult::Consumed) + break; } if (!receiver || (lang != LANG_ADDON && !receiver->isAcceptWhispers() && receiver->GetSession()->HasPermission(rbac::RBAC_PERM_CAN_FILTER_WHISPERS) && !receiver->IsInWhisperWhiteList(sender->GetGUID()))) { diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 7d1349b9bc0f6..bfb052e47baa9 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -2590,8 +2590,12 @@ void ScriptMgr::OnAddonMessage(Player* sender, std::string const& message) return; LuaVal mainTable = LuaVal::loads(message); - if (!mainTable.istable()) // Unable to parse or incorrect format + if (!mainTable.istable()) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_DEBUG, + "AIO: Ignoring non-table addon payload from {} ({} bytes)", sender->GetName(), message.size()); return; + } // Call handlers from all blocks in order for (unsigned int i = 1; i <= mainTable.len(); ++i) @@ -2609,12 +2613,12 @@ void ScriptMgr::OnAddonMessage(Player* sender, std::string const& message) if (nArgsVal.num() > 15.0) { sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, - "AIO: Block from '%s' has over 15 arguments (n=%.0f). Sender: %s", - scriptKeyVal.tostring().c_str(), nArgsVal.num(), sender->GetName().c_str()); + "AIO: Block from '{}' has over 15 arguments (n={:.0f}). Sender: {}", + scriptKeyVal.tostring(), nArgsVal.num(), sender->GetName()); continue; } - if (AIOScript* aioScript = _aioHandlers->GetScript(scriptKeyVal)) + if (AIOScript* aioScript = AIOScript::FindByKey(scriptKeyVal)) aioScript->OnHandle(sender, handlerKeyVal, block); } } @@ -2624,7 +2628,7 @@ AIOScript::AIOScript(LuaVal const& scriptKey) { if (AIOScript::_scriptByKeyMap.find(scriptKey) != AIOScript::_scriptByKeyMap.end()) { - sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '%s' of type tag '%i' already exist. Use another key.", scriptKey.tostring().c_str(), static_cast(scriptKey.typetag())); + sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '{}' of type tag '{}' already exist. Use another key.", scriptKey.tostring(), static_cast(scriptKey.typetag())); ASSERT(false); } ScriptRegistry::Instance()->AddScript(this); @@ -2670,14 +2674,19 @@ void AIOScript::AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, A list->push_back(a6); } -template -ScriptClass *AIOScript::GetScript(const LuaVal &scriptKey) +AIOScript* AIOScript::FindByKey(LuaVal const& scriptKey) { - AIOScriptByKeyMap::const_iterator itr = AIOScript::_scriptByKeyMap.find(scriptKey); - if (itr == AIOScript::_scriptByKeyMap.end()) + AIOScriptByKeyMap::const_iterator itr = _scriptByKeyMap.find(scriptKey); + if (itr == _scriptByKeyMap.end()) return nullptr; - return dynamic_cast(itr->second); + return itr->second; +} + +template +ScriptClass *AIOScript::GetScript(const LuaVal &scriptKey) +{ + return dynamic_cast(FindByKey(scriptKey)); } bool AIOScript::AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission) @@ -2694,8 +2703,8 @@ void AIOScript::OnHandle(Player* sender, LuaVal const& handlerKey, LuaVal const& return; } - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIO: No handler '%s' on script '%s'. Sender: %s", - handlerKey.tostring().c_str(), _key.tostring().c_str(), sender->GetName().c_str()); + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIO: No handler '{}' on script '{}'. Sender: {}", + handlerKey.tostring(), _key.tostring(), sender->GetName()); } AIOHandlers::AIOHandlers() @@ -2715,7 +2724,7 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) LuaVal clientDataVal = args.get(5); if (!versionVal.isnumber() || !clientDataVal.istable()) { - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIOHandlers::HandleInit: Invalid version value or clientData value. Sender: %s, Args: %s", sender->GetName().c_str(), args.dumps().c_str()); + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIOHandlers::HandleInit: Invalid version value or clientData value. Sender: {}, Args: {}", sender->GetName(), args.dumps()); return; } @@ -2767,7 +2776,7 @@ void AIOHandlers::HandleError(Player* sender, LuaVal const& args) if (!msgVal.isstring()) return; - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "%s Received client addon error: %s", sender->GetSession()->GetPlayerInfo().c_str(), msgVal.str().c_str()); + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "{} Received client addon error: {}", sender->GetSession()->GetPlayerInfo(), msgVal.str()); } void PlayerScript::OnPVPKill(Player* /*killer*/, Player* /*killed*/) diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index af95cf98059ef..353c7b5682d5d 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -818,83 +818,8 @@ class TC_GAME_API GroupScript : public ScriptObject virtual void OnDisband(Group* group); }; -// ##################### Abstract AIO handler script ##################### -// Inherit AIOScript to make an AIO handler script -// -// See smallfork_cpp at https://github.com/Rochet2/smallfolk_cpp for -// reference on how to use LuaVal -// -// Example of use: -// -// class ExampleAIOScript : public AIOScript -// { -// public: -// ExampleAIOScript() -// : AIOScript("ExampleScriptName") -// { -// using namespace std::placeholders; -// -// // Loads addon files to addons list and sends them on AIO client initialization -// // Looks for the file in path config AIO.ClientScriptPath -// AddAddon("ExampleAddon", "example_addon.lua"); -// -// // You can also add addons to be sent to players with specific permission -// AddAddon("AnotherAddon", "example_addon.lua", 192); // 192 refers to admin RBAC permission -// -// // Handler function signature: void HandlerFunction(Player* sender, LuaVal const& args) -// AddHandler("Print", std::bind(&ExampleAIOScript::HandlePrint, this, _1, _2)); -// AddHandler("Save", std::bind(&ExampleAIOScript::HandleSave, this, _1, _2)); -// -// // Initialization handler and arguments -// AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg, this, _1), std::bind(&ExampleAIOScript::InitArg, this, _1)); -// // Adds additional argument to send to handler -// AddInitArgs("ExampleScriptName", "Init", std::bind(&ExampleAIOScript::InitArg2, this, _1)); -// AddInitArgs("AnotherScript", "InitB"); // Arguments are not necessary -// } -// -// void HandlePrint(Player* sender, LuaVal const& args) -// { -// // LuaVal args in a handler function is always a table -// // Handler arguments index starts from 4 -// LuaVal inputVal = args.get(4); -// LuaVal sliderVal = args.get(5); -// -// // MUST check if the value type is valid or else smallfolk_cpp will -// // throw on obtaining that type -// if (!inputVal.isstring() || !sliderVal.isnumber()) -// return; -// -// sender->GetSession()->SendNotification("HandlePrint -> Stored String: %s, Input: %s, Slider Value: %f", -// storedString.c_str(), inputVal.str().c_str(), sliderVal.num()); -// } -// -// void HandleSave(Player* sender, LuaVal const& args) -// { -// LuaVal saveVal = args.get(4); -// -// if (!saveVal.isstring()) -// return; -// -// storedString = saveVal.str(); -// sender->GetSession()->SendNotification("Saved"); -// } -// -// LuaVal InitArg(Player* /*sender*/) -// { -// LuaVal arg(TTABLE); -// arg.set("key", 12.3); -// arg["key2"] = false; -// return arg; -// } -// -// LuaVal InitArg2(Player* /*sender*/) -// { -// return LuaVal("LuaVal will implicitly create a string LuaVal for this arg"); -// } -// -// private: -// std::string storedString; -// }; +// Inherit AIOScript for server-side AIO handlers (LuaVal / smallfolk_cpp). +// Full example: doc/CAIO_SCRIPT_EXAMPLE.md class AIOScript : public ScriptObject { public: @@ -938,6 +863,8 @@ class AIOScript : public ScriptObject template ScriptClass *GetScript(const LuaVal &key); + static AIOScript* FindByKey(LuaVal const& scriptKey); + private: void OnHandle(Player* sender, LuaVal const& handlerKey, LuaVal const& args); diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 846f2891be412..2532fd8511f5f 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -499,6 +499,121 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) return true; } +WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhisper(Player* sender, Player* receiver, std::string const& msg) +{ + size_t delimPos = msg.find('\t'); + if (delimPos == std::string::npos) + return IncomingAIOWhisperResult::NotAIO; + + std::string prefix = msg.substr(0, delimPos); + if (prefix != "C" + sWorld->GetAIOPrefix()) + return IncomingAIOWhisperResult::NotAIO; + + if (receiver != sender) + return IncomingAIOWhisperResult::DropPacket; + + uint16 messageId = 0; + if ((msg.size() - delimPos - 1) >= size_t(2)) + { + messageId = (msg[delimPos + 1] - 1) * 254 + msg[delimPos + 2] - 1; + + if (messageId == 0) + { + sScriptMgr->OnAddonMessage(sender, msg.substr(delimPos + 3)); + return IncomingAIOWhisperResult::Consumed; + } + } + + if ((msg.size() - delimPos - 1) < size_t(6)) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "HandleIncomingAIOClientWhisper: Received AIO long addon message without meta data. Sender: {}", sender->GetName()); + return IncomingAIOWhisperResult::Consumed; + } + + uint32 parts = (msg[delimPos + 3] - 1) * 254 + msg[delimPos + 4] - 1; + if (parts < 2) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "HandleIncomingAIOClientWhisper: Received AIO addon message with number of parts: {} (< 2). Sender: {}", parts, sender->GetName()); + return IncomingAIOWhisperResult::DropPacket; + } + + uint32 maxparts = sWorld->getIntConfig(CONFIG_AIO_MAXPARTS); + if (parts > maxparts) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "HandleIncomingAIOClientWhisper: Received AIO addon message with too many parts: {} (> {}). Message Id: {}, Sender: {}", + parts, maxparts, messageId, sender->GetName()); + return IncomingAIOWhisperResult::DropPacket; + } + + uint32 partId = (msg[delimPos + 5] - 1) * 254 + msg[delimPos + 6] - 1; + if (partId < 1 || partId > parts) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "HandleIncomingAIOClientWhisper: Invalid AIO part id {} for {} parts. Message Id: {}, Sender: {}", + partId, parts, messageId, sender->GetName()); + return IncomingAIOWhisperResult::DropPacket; + } + + std::string const partPayload = msg.substr(delimPos + 7); + uint32 const maxBufferSize = sWorld->getIntConfig(CONFIG_AIO_MAX_BUFFER_SIZE); + + AddonMessageBufferMap::iterator messagePartsItr = _addonMessageBuffer.find(messageId); + if (messagePartsItr == _addonMessageBuffer.end()) + messagePartsItr = _addonMessageBuffer.insert(std::make_pair(messageId, LongMessageBufferInfo())).first; + else if (parts != messagePartsItr->second.Parts) + messagePartsItr->second = LongMessageBufferInfo(); + + if (messagePartsItr->second.Map.find(partId) != messagePartsItr->second.Map.end()) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "HandleIncomingAIOClientWhisper: Duplicate part {} for message id {}. Sender: {}", partId, messageId, sender->GetName()); + _addonMessageBuffer.erase(messagePartsItr); + return IncomingAIOWhisperResult::DropPacket; + } + + messagePartsItr->second.Parts = parts; + messagePartsItr->second.Timer = 0; + messagePartsItr->second.BufferedBytes += uint32(partPayload.size()); + if (messagePartsItr->second.BufferedBytes > maxBufferSize) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "HandleIncomingAIOClientWhisper: AIO reassembly buffer exceeded {} bytes. Message Id: {}, Sender: {}", + maxBufferSize, messageId, sender->GetName()); + _addonMessageBuffer.erase(messagePartsItr); + return IncomingAIOWhisperResult::DropPacket; + } + + messagePartsItr->second.Map[partId] = partPayload; + + bool haveAllParts = messagePartsItr->second.Map.size() >= static_cast(messagePartsItr->second.Parts); + if (haveAllParts) + { + for (uint32 expectedPart = 1; expectedPart <= parts; ++expectedPart) + { + if (messagePartsItr->second.Map.find(expectedPart) == messagePartsItr->second.Map.end()) + { + haveAllParts = false; + break; + } + } + } + + if (!haveAllParts) + return IncomingAIOWhisperResult::Consumed; + + std::string actualAIOMessage; + actualAIOMessage.reserve(messagePartsItr->second.BufferedBytes); + for (uint32 expectedPart = 1; expectedPart <= parts; ++expectedPart) + actualAIOMessage += messagePartsItr->second.Map.find(expectedPart)->second; + + sScriptMgr->OnAddonMessage(sender, actualAIOMessage); + _addonMessageBuffer.erase(messagePartsItr); + return IncomingAIOWhisperResult::Consumed; +} + /// %Log the player out void WorldSession::LogoutPlayer(bool save) { diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 841c6195824e3..78ac70ea16007 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -1282,6 +1282,15 @@ class TC_GAME_API WorldSession return _legitCharacters.find(lowGUID) != _legitCharacters.end(); } + enum class IncomingAIOWhisperResult : uint8 + { + NotAIO, + Consumed, + DropPacket + }; + + IncomingAIOWhisperResult HandleIncomingAIOClientWhisper(Player* sender, Player* receiver, std::string const& msg); + // Movement helpers Unit* ValidateAndGetUnitBeingMoved(ObjectGuid guid, OpcodeClient opcode, bool forStatusAck) const; diff --git a/src/server/game/Server/WorldSocket.cpp b/src/server/game/Server/WorldSocket.cpp index bb6dd6032244e..67df7cdbb4bae 100644 --- a/src/server/game/Server/WorldSocket.cpp +++ b/src/server/game/Server/WorldSocket.cpp @@ -95,7 +95,7 @@ bool WorldSocket::Update() if (!queued->empty()) buffer.Write(queued->contents(), queued->size()); } - else // single packet larger than buffer size + else // single packet larger than _sendBufferSize { MessageBuffer packetBuffer(queued->size() + header.getHeaderLength()); packetBuffer.Write(header.header, header.getHeaderLength()); diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 6a708bb6506ac..25b54685a3bc5 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -20,7 +20,6 @@ */ #include "World.h" -#include #include "AccountMgr.h" #include "AchievementMgr.h" #include "AddonMgr.h" @@ -1589,11 +1588,22 @@ void World::LoadConfigSettings(bool reload) m_bool_configs[CONFIG_HOTSWAP_INSTALL_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnableInstall", true); m_bool_configs[CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnablePrefixCorrection", true); + // prevent character rename on character customization m_bool_configs[CONFIG_PREVENT_RENAME_CUSTOMIZATION] = sConfigMgr->GetBoolDefault("PreventRenameCharacterOnCustomization", false); + + // Allow 5-man parties to use raid warnings m_bool_configs[CONFIG_CHAT_PARTY_RAID_WARNINGS] = sConfigMgr->GetBoolDefault("PartyRaidWarnings", false); + + // Allow to cache data queries m_bool_configs[CONFIG_CACHE_DATA_QUERIES] = sConfigMgr->GetBoolDefault("CacheDataQueries", true); + + // Whether to use LoS from game objects m_bool_configs[CONFIG_CHECK_GOBJECT_LOS] = sConfigMgr->GetBoolDefault("CheckGameObjectLoS", true); + + // Anti movement cheat measure. Time each client have to acknowledge a movement change until they are kicked m_int_configs[CONFIG_PENDING_MOVE_CHANGES_TIMEOUT] = sConfigMgr->GetIntDefault("AntiCheat.PendingMoveChangesTimeoutTime", 0); + + // Specifies if IP addresses can be logged to the database m_bool_configs[CONFIG_ALLOW_LOGGING_IP_ADDRESSES_IN_DATABASE] = sConfigMgr->GetBoolDefault("AllowLoggingIPAddressesInDatabase", true, true); // call ScriptMgr if we're reloading the configuration @@ -3647,7 +3657,7 @@ bool World::AddAddon(AIOAddon const& addon) } else { - sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO AddAddon: Couldn't open file %s of addon %s", path.c_str(), copy.name.c_str()); + sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO AddAddon: Couldn't open file {} of addon {}", path, copy.name); return false; } @@ -3660,7 +3670,7 @@ bool World::AddAddon(AIOAddon const& addon) copy.code = std::string(1, 'U') + copy.code; m_AddonList.push_back(copy); - sLog->outAIOMessage(0, LOG_LEVEL_INFO, "AIO: Loaded addon %s from file %s", copy.name.c_str(), copy.file.c_str()); + sLog->outAIOMessage(0, LOG_LEVEL_INFO, "AIO: Loaded addon {} from file {}", copy.name, copy.file); return true; } @@ -3694,7 +3704,7 @@ bool World::ReloadAddons() } catch (std::exception &e) { - sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO: Error reloading addons. Exception: %s", e.what()); + sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO: Error reloading addons. Exception: {}", e.what()); m_AddonList.swap(prevAddonList); return false; } diff --git a/src/server/scripts/Commands/cs_caio.cpp b/src/server/scripts/Commands/cs_caio.cpp index 76aa4947178b8..5ec060f92ccce 100644 --- a/src/server/scripts/Commands/cs_caio.cpp +++ b/src/server/scripts/Commands/cs_caio.cpp @@ -5,171 +5,121 @@ Comment : All AIO related server side commands Category : commandscripts EndScriptData */ +#include "ScriptMgr.h" +#include "AIO.h" #include "Chat.h" +#include "ChatCommand.h" #include "Language.h" #include "Player.h" #include "RBAC.h" -#include "ScriptMgr.h" #include "World.h" #include "smallfolk.h" +using namespace Trinity::ChatCommands; + class caio_commandscript : public CommandScript { public: caio_commandscript() : CommandScript("caio_commandscript") { } - std::vector GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector caioCommandTable = + static ChatCommandTable caioCommandTable = { - { "version", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleVersionCommand, "" }, - { "send", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleSendCommand, "" }, - { "forcereload", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadCommand, "" }, - { "forcereset", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleResetCommand, "" }, - { "sendall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleSendAllCommand, "" }, - { "forcereloadall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadAllCommand, "" }, - { "forceresetall", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleResetAllCommand, "" }, - { "reloadaddons", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleReloadAddonsCommand, "" }, - { "addaddon", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleAddAddonCommand, "" }, - { "removeaddon", rbac::RBAC_PERM_COMMAND_CAIO, true, &HandleRemoveAddonCommand, "" }, + { "version", HandleVersionCommand, rbac::RBAC_PERM_COMMAND_CAIO, Console::Yes }, + { "send", HandleSendCommand, rbac::RBAC_PERM_COMMAND_CAIO, Console::Yes }, + { "forcereload", HandleReloadCommand, rbac::RBAC_PERM_COMMAND_CAIO, Console::Yes }, + { "forcereset", HandleResetCommand, rbac::RBAC_PERM_COMMAND_CAIO, Console::Yes }, + { "sendall", HandleSendAllCommand, rbac::RBAC_PERM_COMMAND_CAIO, Console::Yes }, + { "forcereloadall", HandleReloadAllCommand, rbac::RBAC_PERM_COMMAND_CAIO, Console::Yes }, + { "forceresetall", HandleResetAllCommand, rbac::RBAC_PERM_COMMAND_CAIO, Console::Yes }, + { "reloadaddons", HandleReloadAddonsCommand, rbac::RBAC_PERM_COMMAND_CAIO, Console::Yes }, + { "addaddon", HandleAddAddonCommand, rbac::RBAC_PERM_COMMAND_CAIO, Console::Yes }, + { "removeaddon", HandleRemoveAddonCommand, rbac::RBAC_PERM_COMMAND_CAIO, Console::Yes }, }; - static std::vector commandTable = + static ChatCommandTable commandTable = { - { "caio", rbac::RBAC_PERM_COMMAND_CAIO, true, nullptr, "", caioCommandTable }, + { "caio", caioCommandTable }, }; return commandTable; } - static bool HandleVersionCommand(ChatHandler* handler, char const* /*args*/) + static bool HandleVersionCommand(ChatHandler* handler) { - handler->PSendSysMessage("AIO version %s (protocol %.2f).", AIO_VERSION_STRING, AIO_VERSION); + handler->PSendSysMessage("AIO version {} (protocol {:.2f}).", AIO_VERSION_STRING, AIO_VERSION); return true; } - static bool HandleSendCommand(ChatHandler* handler, char const* args) + static bool HandleSendCommand(ChatHandler* handler, PlayerIdentifier const& target, QuotedString message) { - Player* target = nullptr; - if (!handler->extractPlayerTarget((char*)args, &target)) - return false; - - char* tailStr = strtok(nullptr, ""); - if (!tailStr) - return false; - - char* msg = handler->extractQuotedArg(tailStr); - if (!msg) - return false; - - if (!LuaVal::loads(msg).istable()) + if (!LuaVal::loads(message).istable()) { handler->SendSysMessage("CAIO: message must be smallfolk-serialized table data (use AIOMsg or AIO.Msg on the server)."); return false; } - target->SendSimpleAIOMessage(msg); - handler->PSendSysMessage(LANG_SENDMESSAGE, target->GetName().c_str(), msg); + Player* player = target.GetConnectedPlayer(); + if (!player) + return false; + + player->SendSimpleAIOMessage(message); + handler->PSendSysMessage(LANG_SENDMESSAGE, target.GetName().c_str(), message.c_str()); return true; } - static bool HandleReloadCommand(ChatHandler* handler, char const* args) + static bool HandleReloadCommand(ChatHandler* handler, PlayerIdentifier const& target) { - Player* target = nullptr; - if (!handler->extractPlayerTarget((char*)args, &target, nullptr, nullptr)) + Player* player = target.GetConnectedPlayer(); + if (!player) return false; - target->ForceReloadAddons(); - handler->PSendSysMessage(LANG_CAIO_FORCERELOAD_SENT, target->GetName().c_str()); + player->ForceReloadAddons(); + handler->PSendSysMessage(LANG_CAIO_FORCERELOAD_SENT, target.GetName().c_str()); return true; } - static bool HandleResetCommand(ChatHandler* handler, char const* args) + static bool HandleResetCommand(ChatHandler* handler, PlayerIdentifier const& target) { - Player* target = nullptr; - if (!handler->extractPlayerTarget((char*)args, &target, nullptr, nullptr)) + Player* player = target.GetConnectedPlayer(); + if (!player) return false; - target->ForceResetAddons(); - handler->PSendSysMessage(LANG_CAIO_FORCERESET_SENT, target->GetName().c_str()); + player->ForceResetAddons(); + handler->PSendSysMessage(LANG_CAIO_FORCERESET_SENT, target.GetName().c_str()); return true; } - static bool HandleSendAllCommand(ChatHandler* handler, char const* args) + static bool HandleSendAllCommand(ChatHandler* handler, QuotedString message, Optional permission) { - if (!*args) - return false; - - char* msg = handler->extractQuotedArg((char*)args); - if (!msg) - return false; - - if (!LuaVal::loads(msg).istable()) + if (!LuaVal::loads(message).istable()) { handler->SendSysMessage("CAIO: message must be smallfolk-serialized table data (use AIOMsg or AIO.Msg on the server)."); return false; } - char* permission = strtok(nullptr, ""); - uint32 perm = AIO_DEFAULT_ADDON_PERMISSION; - if (permission) - { - try - { - perm = std::stoi(permission); - } - catch (std::exception&) - { - return false; - } - } - - sWorld->SendAllSimpleAIOMessage(msg, perm); - handler->PSendSysMessage(LANG_SENDMESSAGE, "all players", msg); + uint32 perm = permission.value_or(AIO_DEFAULT_ADDON_PERMISSION); + sWorld->SendAllSimpleAIOMessage(message, perm); + handler->PSendSysMessage(LANG_SENDMESSAGE, "all players", message.c_str()); return true; } - static bool HandleReloadAllCommand(ChatHandler* handler, char const* args) + static bool HandleReloadAllCommand(ChatHandler* handler, Optional permission) { - uint32 perm = AIO_DEFAULT_ADDON_PERMISSION; - if (args && *args) - { - try - { - perm = std::stoi(args); - } - catch (std::exception&) - { - return false; - } - } - - sWorld->ForceReloadPlayerAddons(perm); + sWorld->ForceReloadPlayerAddons(permission.value_or(AIO_DEFAULT_ADDON_PERMISSION)); handler->PSendSysMessage(LANG_CAIO_FORCERELOAD_SENT, "all players"); return true; } - static bool HandleResetAllCommand(ChatHandler* handler, char const* args) + static bool HandleResetAllCommand(ChatHandler* handler, Optional permission) { - uint32 perm = AIO_DEFAULT_ADDON_PERMISSION; - if (args && *args) - { - try - { - perm = std::stoi(args); - } - catch (std::exception&) - { - return false; - } - } - - sWorld->ForceResetPlayerAddons(perm); + sWorld->ForceResetPlayerAddons(permission.value_or(AIO_DEFAULT_ADDON_PERMISSION)); handler->PSendSysMessage(LANG_CAIO_FORCERESET_SENT, "all players"); return true; } - static bool HandleReloadAddonsCommand(ChatHandler* handler, char const* /*args*/) + static bool HandleReloadAddonsCommand(ChatHandler* handler) { if (sWorld->ReloadAddons()) sWorld->ForceReloadPlayerAddons(); @@ -179,53 +129,25 @@ class caio_commandscript : public CommandScript return true; } - static bool HandleAddAddonCommand(ChatHandler* handler, char const* args) + static bool HandleAddAddonCommand(ChatHandler* handler, std::string addonName, QuotedString addonFile, Optional permission) { - if (!*args) - return false; - - char* addonName = strtok((char*)args, " "); - if (!addonName || addonName[0] == '"') - return false; - - char* tailStr = strtok(nullptr, ""); - char* addonFile = handler->extractQuotedArg(tailStr); - if (!addonFile) - return false; - - char* permission = strtok(nullptr, ""); - uint32 perm = AIO_DEFAULT_ADDON_PERMISSION; - if (permission) - { - try - { - perm = std::stoi(permission); - } - catch (std::exception&) - { - return false; - } - } - + uint32 perm = permission.value_or(AIO_DEFAULT_ADDON_PERMISSION); World::AIOAddon newAddon(addonName, addonFile, perm); if (sWorld->AddAddon(newAddon)) sWorld->ForceReloadPlayerAddons(perm); else - handler->PSendSysMessage(LANG_CAIO_ADDADDON_ERROR, addonName); + handler->PSendSysMessage(LANG_CAIO_ADDADDON_ERROR, addonName.c_str()); return true; } - static bool HandleRemoveAddonCommand(ChatHandler* handler, char const* args) + static bool HandleRemoveAddonCommand(ChatHandler* handler, std::string addonName) { - if (!*args) - return false; - uint32 perm = AIO_DEFAULT_ADDON_PERMISSION; - if (sWorld->RemoveAddon(args, &perm)) + if (sWorld->RemoveAddon(addonName, &perm)) sWorld->ForceReloadPlayerAddons(perm); else - handler->PSendSysMessage(LANG_CAIO_REMOVEADDON_ERROR, args); + handler->PSendSysMessage(LANG_CAIO_REMOVEADDON_ERROR, addonName.c_str()); return true; } From 1cf5a48392cb8a6be450db4a99111d869c06433b Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Tue, 2 Jun 2026 02:29:39 +0300 Subject: [PATCH 20/33] Fix GCC CI: FindByKey visibility, LuaVal casts, AIO scripts. Make AIOScript::FindByKey public for ScriptMgr, fix -Werror conversions on LuaVal table indices, decode AIO whisper bytes safely, and pass WITH_CAIO_EXAMPLES to the dynamic AIO script module. --- src/server/game/Chat/AIOMsg.cpp | 4 ++-- src/server/game/Entities/Player/Player.cpp | 3 ++- src/server/game/Scripting/ScriptMgr.cpp | 6 +++--- src/server/game/Scripting/ScriptMgr.h | 4 ++-- src/server/game/Server/WorldSession.cpp | 14 +++++++++++--- src/server/game/World/World.cpp | 4 ++-- src/server/scripts/CMakeLists.txt | 7 +++++++ src/server/scripts/Commands/cs_caio.cpp | 2 +- 8 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/server/game/Chat/AIOMsg.cpp b/src/server/game/Chat/AIOMsg.cpp index af437c6b2cf17..bab2df65c948d 100644 --- a/src/server/game/Chat/AIOMsg.cpp +++ b/src/server/game/Chat/AIOMsg.cpp @@ -45,7 +45,7 @@ AIOMsg& AIOMsg::Add(LuaVal const& scriptKey, LuaVal const& handlerKey, LuaVal co ++nArgs; } - block[1] = nArgs; + block[1] = static_cast(nArgs); _val.insert(block); return *this; } @@ -93,6 +93,6 @@ AIOMsg& AIOMsg::AppendLast(LuaVal const& a1, LuaVal const& a2, LuaVal const& a3, ++nArgs; } - block[1] = nArgs; + block[1] = static_cast(nArgs); return *this; } diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 4a7963d1a1e3f..24ad7bb78cd28 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -20603,7 +20603,8 @@ void Player::SendSimpleAIOMessage(std::string const& message) } uint32 const chunkLen = maxPacketLen > longHeaderLen ? maxPacketLen - longHeaderLen : 1; - uint16 const parts = uint16(std::ceil(float(message.size()) / float(chunkLen))); + float const messageLen = float(message.size()); + uint16 const parts = uint16(std::ceil(messageLen / float(chunkLen))); uint16 high = uint16(std::floor(float(parts) / 254.0f)); std::string partsStr(1, char(high + 1)); diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index bfb052e47baa9..5584490e328b6 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -2752,9 +2752,9 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) hookBlock[2] = itr->scriptKey; hookBlock[3] = itr->handlerKey; for (std::list::const_iterator it = itr->argsList.begin(); it != itr->argsList.end(); ++it) - hookBlock[++index] = (*it)(sender); + hookBlock[static_cast(++index)] = (*it)(sender); - argsToSend[++blockIndex] = hookBlock; + argsToSend[static_cast(++blockIndex)] = hookBlock; } LuaVal AIOInitBlock(TTABLE); @@ -2762,7 +2762,7 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) AIOInitBlock[2] = "AIO"; AIOInitBlock[3] = "Init"; AIOInitBlock[4] = AIO_VERSION; - AIOInitBlock[5] = nAddons; + AIOInitBlock[5] = static_cast(nAddons); AIOInitBlock[6] = addonTable; AIOInitBlock[7] = cacheTable; diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 353c7b5682d5d..3b7117e8fcef3 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -829,6 +829,8 @@ class AIOScript : public ScriptObject LuaVal GetKey() const { return _key; } bool IsDatabaseBound() const { return false; } + static AIOScript* FindByKey(LuaVal const& scriptKey); + typedef std::function HandlerFunc; typedef std::function ArgFunc; @@ -863,8 +865,6 @@ class AIOScript : public ScriptObject template ScriptClass *GetScript(const LuaVal &key); - static AIOScript* FindByKey(LuaVal const& scriptKey); - private: void OnHandle(Player* sender, LuaVal const& handlerKey, LuaVal const& args); diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 2532fd8511f5f..88b5a1e75512d 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -499,6 +499,14 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) return true; } +namespace +{ +uint32 DecodeAIOByte(char value) +{ + return uint32(static_cast(value) - 1u); +} +} + WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhisper(Player* sender, Player* receiver, std::string const& msg) { size_t delimPos = msg.find('\t'); @@ -515,7 +523,7 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis uint16 messageId = 0; if ((msg.size() - delimPos - 1) >= size_t(2)) { - messageId = (msg[delimPos + 1] - 1) * 254 + msg[delimPos + 2] - 1; + messageId = uint16(DecodeAIOByte(msg[delimPos + 1]) * 254u + DecodeAIOByte(msg[delimPos + 2])); if (messageId == 0) { @@ -531,7 +539,7 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis return IncomingAIOWhisperResult::Consumed; } - uint32 parts = (msg[delimPos + 3] - 1) * 254 + msg[delimPos + 4] - 1; + uint32 parts = DecodeAIOByte(msg[delimPos + 3]) * 254u + DecodeAIOByte(msg[delimPos + 4]); if (parts < 2) { sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, @@ -548,7 +556,7 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis return IncomingAIOWhisperResult::DropPacket; } - uint32 partId = (msg[delimPos + 5] - 1) * 254 + msg[delimPos + 6] - 1; + uint32 partId = DecodeAIOByte(msg[delimPos + 5]) * 254u + DecodeAIOByte(msg[delimPos + 6]); if (partId < 1 || partId > parts) { sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 25b54685a3bc5..b2b00e4c0f08d 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -3731,7 +3731,7 @@ uint32 World::PrepareClientAddons(LuaVal const& clientData, LuaVal& addonsTable, LuaVal CRCVal = clientData.get(itr->name); if (CRCVal == itr->crc) { - cacheTable[++i] = itr->name; + cacheTable[static_cast(++i)] = itr->name; } else { @@ -3739,7 +3739,7 @@ uint32 World::PrepareClientAddons(LuaVal const& clientData, LuaVal& addonsTable, addonData["name"] = itr->name; addonData["crc"] = itr->crc; addonData["code"] = itr->code; - addonsTable[++i] = addonData; + addonsTable[static_cast(++i)] = addonData; } } return i; diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt index 09a16aa927097..fb6bf68bc649a 100644 --- a/src/server/scripts/CMakeLists.txt +++ b/src/server/scripts/CMakeLists.txt @@ -152,6 +152,9 @@ foreach(SCRIPT_MODULE ${SCRIPT_MODULE_LIST}) # Add the module content to the whole static module unset(SCRIPT_MODULE_PRIVATE_SOURCES) CollectSourceFiles(${SCRIPT_MODULE_PATH} SCRIPT_MODULE_PRIVATE_SOURCES) + if(SCRIPT_MODULE STREQUAL "AIO" AND NOT WITH_CAIO_EXAMPLES) + list(FILTER SCRIPT_MODULE_PRIVATE_SOURCES EXCLUDE REGEX "[/\\\\]ExampleWindow\\.cpp$") + endif() # Configure the scriptloader ConfigureScriptLoader(${SCRIPT_MODULE} SCRIPT_MODULE_PRIVATE_SCRIPTLOADER ON ${SCRIPT_MODULE}) GetProjectNameOfScriptModule(${SCRIPT_MODULE} SCRIPT_MODULE_PROJECT_NAME) @@ -171,6 +174,10 @@ foreach(SCRIPT_MODULE ${SCRIPT_MODULE_LIST}) PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + if(WITH_CAIO_EXAMPLES AND SCRIPT_MODULE STREQUAL "AIO") + target_compile_definitions(${SCRIPT_MODULE_PROJECT_NAME} PRIVATE WITH_CAIO_EXAMPLES) + endif() + set_target_properties(${SCRIPT_MODULE_PROJECT_NAME} PROPERTIES COMPILE_WARNING_AS_ERROR ${WITH_WARNINGS_AS_ERRORS} diff --git a/src/server/scripts/Commands/cs_caio.cpp b/src/server/scripts/Commands/cs_caio.cpp index 5ec060f92ccce..312fe8d3b722c 100644 --- a/src/server/scripts/Commands/cs_caio.cpp +++ b/src/server/scripts/Commands/cs_caio.cpp @@ -48,7 +48,7 @@ class caio_commandscript : public CommandScript static bool HandleVersionCommand(ChatHandler* handler) { - handler->PSendSysMessage("AIO version {} (protocol {:.2f}).", AIO_VERSION_STRING, AIO_VERSION); + handler->PSendSysMessage("AIO version %s (protocol %.2f).", AIO_VERSION_STRING, AIO_VERSION); return true; } From 221db2280548763c2a1e363958d5ec9c7734fbb7 Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Tue, 2 Jun 2026 22:17:19 +0300 Subject: [PATCH 21/33] Fix GCC CI: public AIO dispatch, uint32 message ids, casts. Route addon blocks through HandleAddonBlock, store reassembly buffers by uint32 message id, and tighten LuaVal numeric index conversions. --- src/server/game/Chat/AIOMsg.cpp | 2 +- src/server/game/Scripting/ScriptMgr.cpp | 4 ++-- src/server/game/Scripting/ScriptMgr.h | 2 ++ src/server/game/Server/WorldSession.cpp | 4 ++-- src/server/game/Server/WorldSession.h | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/server/game/Chat/AIOMsg.cpp b/src/server/game/Chat/AIOMsg.cpp index bab2df65c948d..5778e2a9500c1 100644 --- a/src/server/game/Chat/AIOMsg.cpp +++ b/src/server/game/Chat/AIOMsg.cpp @@ -61,7 +61,7 @@ AIOMsg& AIOMsg::AppendLast(LuaVal const& a1, LuaVal const& a2, LuaVal const& a3, if (!nArgsVal.isnumber()) return *this; - unsigned int nArgs = static_cast(nArgsVal.num()); + unsigned int nArgs = static_cast(static_cast(nArgsVal.num())); if (!a1.isnil()) { block.insert(a1); diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 5584490e328b6..f0ed6d48a69d9 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -2619,7 +2619,7 @@ void ScriptMgr::OnAddonMessage(Player* sender, std::string const& message) } if (AIOScript* aioScript = AIOScript::FindByKey(scriptKeyVal)) - aioScript->OnHandle(sender, handlerKeyVal, block); + aioScript->HandleAddonBlock(sender, handlerKeyVal, block); } } @@ -2748,7 +2748,7 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) uint32 index = 3; LuaVal hookBlock(TTABLE); - hookBlock[1] = static_cast(itr->argsList.size() + 1); + hookBlock[1] = static_cast(itr->argsList.size()) + 1u; hookBlock[2] = itr->scriptKey; hookBlock[3] = itr->handlerKey; for (std::list::const_iterator it = itr->argsList.begin(); it != itr->argsList.end(); ++it) diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 3b7117e8fcef3..fd8be8d53d2cc 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -831,6 +831,8 @@ class AIOScript : public ScriptObject static AIOScript* FindByKey(LuaVal const& scriptKey); + void HandleAddonBlock(Player* sender, LuaVal const& handlerKey, LuaVal const& args) { OnHandle(sender, handlerKey, args); } + typedef std::function HandlerFunc; typedef std::function ArgFunc; diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 88b5a1e75512d..2863c25b2d293 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -520,10 +520,10 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis if (receiver != sender) return IncomingAIOWhisperResult::DropPacket; - uint16 messageId = 0; + uint32 messageId = 0; if ((msg.size() - delimPos - 1) >= size_t(2)) { - messageId = uint16(DecodeAIOByte(msg[delimPos + 1]) * 254u + DecodeAIOByte(msg[delimPos + 2])); + messageId = DecodeAIOByte(msg[delimPos + 1]) * 254u + DecodeAIOByte(msg[delimPos + 2]); if (messageId == 0) { diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 78ac70ea16007..a549ef9526c44 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -1367,7 +1367,7 @@ class TC_GAME_API WorldSession uint32 BufferedBytes = 0; AddonPartStringMap Map; }; - typedef std::map AddonMessageBufferMap; + typedef std::map AddonMessageBufferMap; AddonMessageBufferMap _addonMessageBuffer; uint32 _aioMsgCacheSweepTimer = 0; }; From 761186d800d53aa4e2c4863bab2fe1795c31a983 Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Tue, 2 Jun 2026 22:23:42 +0300 Subject: [PATCH 22/33] Fix AIOScript::HandleAddonBlock out-of-line definition for GCC. --- src/server/game/Scripting/ScriptMgr.cpp | 5 +++++ src/server/game/Scripting/ScriptMgr.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index f0ed6d48a69d9..e938c5967b070 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -2683,6 +2683,11 @@ AIOScript* AIOScript::FindByKey(LuaVal const& scriptKey) return itr->second; } +void AIOScript::HandleAddonBlock(Player* sender, LuaVal const& handlerKey, LuaVal const& args) +{ + OnHandle(sender, handlerKey, args); +} + template ScriptClass *AIOScript::GetScript(const LuaVal &scriptKey) { diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index fd8be8d53d2cc..f739921285fec 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -831,7 +831,7 @@ class AIOScript : public ScriptObject static AIOScript* FindByKey(LuaVal const& scriptKey); - void HandleAddonBlock(Player* sender, LuaVal const& handlerKey, LuaVal const& args) { OnHandle(sender, handlerKey, args); } + void HandleAddonBlock(Player* sender, LuaVal const& handlerKey, LuaVal const& args); typedef std::function HandlerFunc; typedef std::function ArgFunc; From 850b1d674247a06ce15955fecd2b81d324e74c31 Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Tue, 2 Jun 2026 22:37:27 +0300 Subject: [PATCH 23/33] Fix ScriptMgr constructor member initializer order for -Wreorder --- src/server/game/Scripting/ScriptMgr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index e938c5967b070..46a4a56f76276 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -1022,7 +1022,7 @@ std::string const& ScriptObject::GetName() const } ScriptMgr::ScriptMgr() - : _scriptCount(0), _script_loader_callback(nullptr), _scheduledScripts(0), _aioHandlers(nullptr) + : _scriptCount(0), _scheduledScripts(0), _aioHandlers(nullptr), _script_loader_callback(nullptr) { } From 3593022afd16daa70d3bc43caa2076ee52158caa Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Tue, 2 Jun 2026 22:51:44 +0300 Subject: [PATCH 24/33] Apply WITH_CAIO_EXAMPLES to all dynamic script modules for PCH reuse --- src/server/scripts/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt index fb6bf68bc649a..558023fc8bfcf 100644 --- a/src/server/scripts/CMakeLists.txt +++ b/src/server/scripts/CMakeLists.txt @@ -174,7 +174,7 @@ foreach(SCRIPT_MODULE ${SCRIPT_MODULE_LIST}) PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - if(WITH_CAIO_EXAMPLES AND SCRIPT_MODULE STREQUAL "AIO") + if(WITH_CAIO_EXAMPLES) target_compile_definitions(${SCRIPT_MODULE_PROJECT_NAME} PRIVATE WITH_CAIO_EXAMPLES) endif() From 1bba5c9ff0185e9ff6116e9523f7d2c862208d9b Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Tue, 2 Jun 2026 23:11:56 +0300 Subject: [PATCH 25/33] Skip lua_client_scripts install when the directory is absent --- src/server/worldserver/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/worldserver/CMakeLists.txt b/src/server/worldserver/CMakeLists.txt index d491dc6180e55..f15efb2edb004 100644 --- a/src/server/worldserver/CMakeLists.txt +++ b/src/server/worldserver/CMakeLists.txt @@ -88,7 +88,7 @@ elseif(WIN32) endif() endif() -if(COPY_CONF) +if(COPY_CONF AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lua_client_scripts") if(UNIX) install(DIRECTORY lua_client_scripts DESTINATION bin) elseif(WIN32) From 208acbd0c05e41476249321ba67243f12cbd912d Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Fri, 5 Jun 2026 01:48:16 +0300 Subject: [PATCH 26/33] Implement CAIO review follow-ups: split headers, AddOnInit, hardening. Extract AIOScript/PlayerAIO/AIOCodec, thin Player AIO API, AddOnInit hooks, incoming limits and rate limiting, config validation, narrow PCH/examples defines, ExampleWindow StressTest rename, and AIO codec unit tests. --- CAIO_README.md | 41 +++---- doc/CAIO_SCRIPT_EXAMPLE.md | 4 + src/server/game/AIO/AIOCodec.h | 48 ++++++++ src/server/game/AIO/PlayerAIO.cpp | 46 ++++++++ src/server/game/AIO/PlayerAIO.h | 38 ++++++ src/server/game/Entities/Player/Player.cpp | 10 +- src/server/game/Entities/Player/Player.h | 24 +--- src/server/game/Scripting/AIOScript.h | 118 +++++++++++++++++++ src/server/game/Scripting/ScriptMgr.cpp | 37 ++++-- src/server/game/Scripting/ScriptMgr.h | 103 +--------------- src/server/game/Server/WorldSession.cpp | 41 ++++--- src/server/game/Server/WorldSession.h | 3 + src/server/game/World/World.cpp | 25 ++++ src/server/game/World/World.h | 4 + src/server/scripts/AIO/ExampleWindow.cpp | 11 +- src/server/scripts/CMakeLists.txt | 16 ++- src/server/scripts/Commands/cs_caio.cpp | 24 +++- src/server/worldserver/worldserver.conf.dist | 10 ++ tests/game/AIOCodec.cpp | 52 ++++++++ 19 files changed, 464 insertions(+), 191 deletions(-) create mode 100644 src/server/game/AIO/AIOCodec.h create mode 100644 src/server/game/AIO/PlayerAIO.cpp create mode 100644 src/server/game/AIO/PlayerAIO.h create mode 100644 src/server/game/Scripting/AIOScript.h create mode 100644 tests/game/AIOCodec.cpp diff --git a/CAIO_README.md b/CAIO_README.md index f38464ff0cc97..31784859f60bb 100644 --- a/CAIO_README.md +++ b/CAIO_README.md @@ -40,14 +40,13 @@ cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DSCRIPTS=static -DTOOLS=0 ## Stock AIO server parity (C++ vs `AIO.lua` with `AIO_SERVER = true`) + **Transport:** `LANG_ADDON` whispers with `S`/`C` prefix are required on 3.3.5; the client receives them as `CHAT_MSG_ADDON` (same as stock Eluna server). Not a CAIO gap. -+ **Init hooks:** C++ `AddInitArgs` appends extra handler blocks to the init reply. For full init-message rewriting (stock `AIO.AddOnInit`), see Todo below. ++ **Init hooks:** C++ `AddInitArgs` appends extra handler blocks to the init reply. `AddOnInit` on `AIOScript` mutates the full outgoing init table before send (stock `AIO.AddOnInit` parity). + **Pre-init gating:** Stock server does **not** queue pre-init blocks (`AIO_INITED` is client-only). CAIO matches that. + **Block arg limit:** Server rejects blocks with `n > 15` (same as stock server Lua). + **Message cache:** `AIO.MsgCacheTime` / `AIO.MsgCacheDelay` match `AIO_MSG_CACHE_TIME` / `AIO_MSG_CACHE_DELAY` in `AIO.lua`. ## Todo -+ **`AIO.AddOnInit` parity (future):** Stock Lua server (`AIO_SERVER = true`) allows `AIO.AddOnInit(func)` where `func(initmsg, player)` receives the full outgoing init message and may replace or rewrite it before send. C++ only has `AddInitArgs`, which appends separate handler blocks and does not mutate the core `AIO`/`Init` block (version, addon table, cache table). A future C++ API would need an init-message hook with read/write access to the assembled init payload. + Implement obfuscation (optional, deferred) + Implement compression (optional, deferred) + Add individual RBAC permissions per `.caio` subcommand (optional; all subcommands use `RBAC_PERM_COMMAND_CAIO` today) @@ -93,25 +92,17 @@ Use `LuaVal::nil` (not `LuaVal::nil()`) for default optional arguments. Type tag ### CAIO reference and functions -**ScriptMgr.h** +**AIOScript.h** (included from `ScriptMgr.h`) ```cpp class AIOScript : public ScriptObject { - public: - virtual ~AIOScript(); - LuaVal GetKey() const; - protected: AIOScript(LuaVal const& scriptKey); void AddHandler(LuaVal const& handlerKey, HandlerFunc function); - void AddInitArgs(LuaVal const& scriptKey, LuaVal const& handlerKey, - ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), - ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); + void AddInitArgs(LuaVal const& scriptKey, LuaVal const& handlerKey, ...); + void AddOnInit(InitMessageFunc func); // mutates full init reply (AIO.AddOnInit parity) bool AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); - - template - ScriptClass* GetScript(LuaVal const& key); }; ``` @@ -134,20 +125,20 @@ class AIOMsg }; ``` -**Player.h** +**Player.h** / **PlayerAIO.h** ```cpp -class Player -{ - public: - void AIOMessage(AIOMsg& msg); - void AIOHandle(LuaVal const& scriptKey, LuaVal const& handlerKey, - LuaVal const& a1 = LuaVal::nil, LuaVal const& a2 = LuaVal::nil, LuaVal const& a3 = LuaVal::nil, - LuaVal const& a4 = LuaVal::nil, LuaVal const& a5 = LuaVal::nil, LuaVal const& a6 = LuaVal::nil); - void SendSimpleAIOMessage(std::string const& message); - void ForceReloadAddons(); - void ForceResetAddons(); -}; +// Player.h — no LuaVal in the public player API +void Player::SendSimpleAIOMessage(std::string const& message); +void Player::ForceReloadAddons(); +void Player::ForceResetAddons(); + +// PlayerAIO.h — use from CAIO scripts +namespace Trinity::AIO { + void Message(Player* player, AIOMsg& msg); + void Handle(Player* player, LuaVal const& scriptKey, LuaVal const& handlerKey, ...); + void Handle(Player* player, char const* scriptKey, char const* handlerKey, ...); +} ``` **World.h** diff --git a/doc/CAIO_SCRIPT_EXAMPLE.md b/doc/CAIO_SCRIPT_EXAMPLE.md index 5a4a82fa3ea0a..fde77911f2686 100644 --- a/doc/CAIO_SCRIPT_EXAMPLE.md +++ b/doc/CAIO_SCRIPT_EXAMPLE.md @@ -68,4 +68,8 @@ class ExampleAIOScript : public AIOScript Register the script in a loader (see `src/server/scripts/AIO/aio_script_loader.cpp` and `WITH_CAIO_EXAMPLES`). +To send messages to the client from C++, use `Trinity::AIO::Handle` / `Trinity::AIO::Message` from `PlayerAIO.h` (keeps `Player.h` free of `LuaVal`). + +For full init-message rewriting (stock `AIO.AddOnInit`), call `AddOnInit` with a callback that receives `(Player*, LuaVal& initMessage)` after the init blocks are assembled and before send. + Handler `args` is always a table; handler parameters start at index **4**. Always check value types before use. diff --git a/src/server/game/AIO/AIOCodec.h b/src/server/game/AIO/AIOCodec.h new file mode 100644 index 0000000000000..e8535902121fd --- /dev/null +++ b/src/server/game/AIO/AIOCodec.h @@ -0,0 +1,48 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#ifndef TRINITY_AIO_CODEC_H +#define TRINITY_AIO_CODEC_H + +#include "Define.h" +#include +#include +#include + +namespace Trinity::AIO::Codec +{ + inline uint32 DecodeByte(char value) + { + return uint32(static_cast(value) - 1u); + } + + inline uint32 DecodePair(char high, char low) + { + return DecodeByte(high) * 254u + DecodeByte(low); + } + + inline bool IsClientPrefix(std::string const& prefix, std::string const& msg, size_t& delimPosOut) + { + delimPosOut = msg.find('\t'); + if (delimPosOut == std::string::npos) + return false; + + return msg.compare(0, delimPosOut, "C" + prefix) == 0; + } +} + +#endif diff --git a/src/server/game/AIO/PlayerAIO.cpp b/src/server/game/AIO/PlayerAIO.cpp new file mode 100644 index 0000000000000..2cf462a3ad736 --- /dev/null +++ b/src/server/game/AIO/PlayerAIO.cpp @@ -0,0 +1,46 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#include "PlayerAIO.h" +#include "Player.h" + +namespace Trinity::AIO +{ +void Message(Player* player, AIOMsg& msg) +{ + if (player) + player->SendSimpleAIOMessage(msg.dumps()); +} + +void Handle(Player* player, LuaVal const& scriptKey, LuaVal const& handlerKey, + LuaVal const& a1, LuaVal const& a2, LuaVal const& a3, + LuaVal const& a4, LuaVal const& a5, LuaVal const& a6) +{ + if (!player) + return; + + AIOMsg msg(scriptKey, handlerKey, a1, a2, a3, a4, a5, a6); + player->SendSimpleAIOMessage(msg.dumps()); +} + +void Handle(Player* player, char const* scriptKey, char const* handlerKey, + LuaVal const& a1, LuaVal const& a2, LuaVal const& a3, + LuaVal const& a4, LuaVal const& a5, LuaVal const& a6) +{ + Handle(player, LuaVal(scriptKey), LuaVal(handlerKey), a1, a2, a3, a4, a5, a6); +} +} diff --git a/src/server/game/AIO/PlayerAIO.h b/src/server/game/AIO/PlayerAIO.h new file mode 100644 index 0000000000000..620decce65806 --- /dev/null +++ b/src/server/game/AIO/PlayerAIO.h @@ -0,0 +1,38 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#ifndef TRINITY_PLAYER_AIO_H +#define TRINITY_PLAYER_AIO_H + +#include "AIOMsg.h" +#include "smallfolk.h" + +class Player; + +namespace Trinity::AIO +{ + TC_GAME_API void Message(Player* player, AIOMsg& msg); + TC_GAME_API void Handle(Player* player, LuaVal const& scriptKey, LuaVal const& handlerKey, + LuaVal const& a1 = LuaVal::nil, LuaVal const& a2 = LuaVal::nil, LuaVal const& a3 = LuaVal::nil, + LuaVal const& a4 = LuaVal::nil, LuaVal const& a5 = LuaVal::nil, LuaVal const& a6 = LuaVal::nil); + + TC_GAME_API void Handle(Player* player, char const* scriptKey, char const* handlerKey, + LuaVal const& a1 = LuaVal::nil, LuaVal const& a2 = LuaVal::nil, LuaVal const& a3 = LuaVal::nil, + LuaVal const& a4 = LuaVal::nil, LuaVal const& a5 = LuaVal::nil, LuaVal const& a6 = LuaVal::nil); +} + +#endif diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 24ad7bb78cd28..fa336e8416f4e 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -112,6 +112,7 @@ #include "WorldPacket.h" #include "WorldSession.h" #include "AIOMsg.h" +#include "PlayerAIO.h" #include "WorldStatePackets.h" #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) @@ -20572,15 +20573,14 @@ void Player::Whisper(uint32 textId, Player* target, bool /*isBossWhisper = false target->SendDirectMessage(packet.Write()); } -void Player::AIOMessage(AIOMsg& msg) +void Player::ForceReloadAddons() { - SendSimpleAIOMessage(msg.dumps()); + Trinity::AIO::Handle(this, "AIO", "ForceReload"); } -void Player::AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, const LuaVal &a1, const LuaVal &a2, const LuaVal &a3, const LuaVal &a4, const LuaVal &a5, const LuaVal &a6) +void Player::ForceResetAddons() { - AIOMsg msg(scriptKey, handlerKey, a1, a2, a3, a4, a5, a6); - SendSimpleAIOMessage(msg.dumps()); + Trinity::AIO::Handle(this, "AIO", "ForceReset"); } void Player::SendSimpleAIOMessage(std::string const& message) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 0229012fdeb25..bd32749972ed1 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -34,8 +34,6 @@ #include #include -#include "smallfolk.h" - struct AccessRequirement; struct AchievementEntry; struct AreaTableEntry; @@ -1066,17 +1064,6 @@ class TC_GAME_API Player : public Unit, public GridObject void Whisper(uint32 textId, Player* target, bool isBossWhisper = false) override; void WhisperAddon(std::string const& text, Player* receiver); - // Sends an AIO message to the player - // See: class AIOMsg - void AIOMessage(AIOMsg& msg); - - // Triggers an AIO handler on the client - // To trigger multiple handlers in one message or to send more - // arguments use Player::AIOMessage - void AIOHandle(const LuaVal &scriptKey, const LuaVal &handlerKey, - const LuaVal &a1 = LuaVal::nil, const LuaVal &a2 = LuaVal::nil, const LuaVal &a3 = LuaVal::nil, - const LuaVal &a4 = LuaVal::nil, const LuaVal &a5 = LuaVal::nil, const LuaVal &a6 = LuaVal::nil); - // AIO can only understand smallfolk LuaVal::dumps() format // Handler functions are called by creating a table as below // { @@ -1086,13 +1073,11 @@ class TC_GAME_API Player : public Unit, public GridObject // Where n is number of arguments including handler name as an argument void SendSimpleAIOMessage(const std::string &message); - // Forces reload on the player AIO addons - // Syncs player AIO addons with server - void ForceReloadAddons() { AIOHandle("AIO", "ForceReload"); } + // Forces reload on the player AIO addons (syncs with server) + void ForceReloadAddons(); - // Force reset on the player AIO addons - // Player AIO addons and addon data is deleted and downloaded again - void ForceResetAddons() { AIOHandle("AIO", "ForceReset"); } + // Force reset: client addon data is deleted and downloaded again + void ForceResetAddons(); bool isAIOInitOnCooldown() const { return m_aioInitCd; } void setAIOIntOnCooldown(bool cd) { m_aioInitCd = cd; m_aioInitTimer = 0; } @@ -2593,7 +2578,6 @@ class TC_GAME_API Player : public Unit, public GridObject WorldLocation _corpseLocation; - friend class AIOHandlers; }; TC_GAME_API void AddItemsSetItem(Player* player, Item* item); diff --git a/src/server/game/Scripting/AIOScript.h b/src/server/game/Scripting/AIOScript.h new file mode 100644 index 0000000000000..d5f391b7e5cad --- /dev/null +++ b/src/server/game/Scripting/AIOScript.h @@ -0,0 +1,118 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#ifndef TRINITY_AIOSCRIPT_H +#define TRINITY_AIOSCRIPT_H + +// Included from ScriptMgr.h after ScriptObject is defined. +#include "AIO.h" +#include "Define.h" +#include "smallfolk.h" +#include +#include +#include + +class Player; +class ScriptMgr; + +// Inherit AIOScript for server-side AIO handlers (LuaVal / smallfolk_cpp). +// Full example: doc/CAIO_SCRIPT_EXAMPLE.md +class TC_GAME_API AIOScript : public ScriptObject +{ + public: + ~AIOScript() override; + + LuaVal GetKey() const { return _key; } + bool IsDatabaseBound() const { return false; } + + static AIOScript* FindByKey(LuaVal const& scriptKey); + + void HandleAddonBlock(Player* sender, LuaVal const& handlerKey, LuaVal const& args); + + using HandlerFunc = std::function; + using ArgFunc = std::function; + using InitMessageFunc = std::function; + + protected: + AIOScript(LuaVal const& scriptKey); + + void AddHandler(LuaVal const& handlerKey, HandlerFunc function) { _handlerMap[handlerKey] = std::move(function); } + + void AddInitArgs(LuaVal const& scriptKey, LuaVal const& handlerKey, + ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), + ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); + + // Stock AIO.AddOnInit parity: mutate or replace the full outgoing init message table before send. + void AddOnInit(InitMessageFunc func); + + bool AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); + + template + ScriptClass* GetScript(LuaVal const& scriptKey); + + private: + void OnHandle(Player* sender, LuaVal const& handlerKey, LuaVal const& args); + + LuaVal _key; + + using HandlerMapType = std::unordered_map; + HandlerMapType _handlerMap; + + using AIOScriptByKeyMap = std::unordered_map; + static AIOScriptByKeyMap _scriptByKeyMap; + + friend class ScriptMgr; + friend class AIOHandlers; +}; + +class TC_GAME_API AIOHandlers : public AIOScript +{ + public: + struct InitHookInfo + { + LuaVal scriptKey; + LuaVal handlerKey; + std::list argsList; + + InitHookInfo(LuaVal const& scriptKey, LuaVal const& handlerKey) + : scriptKey(scriptKey), handlerKey(handlerKey) + { } + }; + + using HookListType = std::list; + + void RegisterInitMessageHook(AIOScript::InitMessageFunc func); + + private: + AIOHandlers(); + void HandleInit(Player* sender, LuaVal const& args); + void HandleError(Player* sender, LuaVal const& args); + + HookListType _initHookList; + std::list _initMessageHooks; + + friend class ScriptMgr; + friend class AIOScript; +}; + +template +ScriptClass* AIOScript::GetScript(LuaVal const& scriptKey) +{ + return dynamic_cast(FindByKey(scriptKey)); +} + +#endif diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 46a4a56f76276..75761a5934122 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -31,6 +31,7 @@ #include "ObjectMgr.h" #include "OutdoorPvPMgr.h" #include "Player.h" +#include "PlayerAIO.h" #include "ScriptReloadMgr.h" #include "ScriptSystem.h" #include "SmartAI.h" @@ -2584,11 +2585,35 @@ PlayerScript::PlayerScript(char const* name) AIOScript::AIOScriptByKeyMap AIOScript::_scriptByKeyMap = AIOScript::AIOScriptByKeyMap(); +AIOScript::~AIOScript() +{ + _scriptByKeyMap.erase(_key); +} + +void AIOScript::AddOnInit(InitMessageFunc func) +{ + if (AIOHandlers* handler = sScriptMgr->_aioHandlers) + handler->RegisterInitMessageHook(std::move(func)); +} + +void AIOHandlers::RegisterInitMessageHook(AIOScript::InitMessageFunc func) +{ + _initMessageHooks.push_back(std::move(func)); +} + void ScriptMgr::OnAddonMessage(Player* sender, std::string const& message) { if (!sender) return; + uint32 const maxIncoming = sWorld->getIntConfig(CONFIG_AIO_MAX_INCOMING); + if (message.size() > maxIncoming) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "AIO: Incoming message size {} exceeds limit {}. Sender: {}", message.size(), maxIncoming, sender->GetName()); + return; + } + LuaVal mainTable = LuaVal::loads(message); if (!mainTable.istable()) { @@ -2688,12 +2713,6 @@ void AIOScript::HandleAddonBlock(Player* sender, LuaVal const& handlerKey, LuaVa OnHandle(sender, handlerKey, args); } -template -ScriptClass *AIOScript::GetScript(const LuaVal &scriptKey) -{ - return dynamic_cast(FindByKey(scriptKey)); -} - bool AIOScript::AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission) { return sWorld->AddAddon(World::AIOAddon(addonName, addonFile, permission)); @@ -2735,7 +2754,7 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) if (std::abs(versionVal.num() - AIO_VERSION) > 0.01) { - sender->AIOHandle("AIO", "Init", AIO_VERSION); + Trinity::AIO::Handle(sender, "AIO", "Init", AIO_VERSION); return; } @@ -2772,6 +2791,10 @@ void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) AIOInitBlock[7] = cacheTable; argsToSend[1] = AIOInitBlock; + + for (AIOScript::InitMessageFunc const& hook : _initMessageHooks) + hook(sender, argsToSend); + sender->SendSimpleAIOMessage(argsToSend.dumps()); } diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index f739921285fec..a564538761c83 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -25,9 +25,6 @@ #include #include -#include "AIO.h" -#include "smallfolk.h" - class AccountMgr; class AuctionHouseObject; class Aura; @@ -818,105 +815,7 @@ class TC_GAME_API GroupScript : public ScriptObject virtual void OnDisband(Group* group); }; -// Inherit AIOScript for server-side AIO handlers (LuaVal / smallfolk_cpp). -// Full example: doc/CAIO_SCRIPT_EXAMPLE.md -class AIOScript : public ScriptObject -{ - public: - virtual ~AIOScript() { AIOScript::_scriptByKeyMap.erase(GetKey()); } - - // Returns the key of this CAIO script - LuaVal GetKey() const { return _key; } - bool IsDatabaseBound() const { return false; } - - static AIOScript* FindByKey(LuaVal const& scriptKey); - - void HandleAddonBlock(Player* sender, LuaVal const& handlerKey, LuaVal const& args); - - typedef std::function HandlerFunc; - typedef std::function ArgFunc; - - protected: - // Registers an AIO Handler script of scriptName - AIOScript(LuaVal const& scriptKey); - - // Registers a handler function to call when handling - // handleKey of this script. - void AddHandler(const LuaVal &handlerKey, HandlerFunc function) { _handlerMap[handlerKey] = function; } - - // Adds a client side handler to call and adds arguments - // to sends with it for AIO client initialization. - // - // You can add additional arguments to the handler by - // calling this function again - void AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, - ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), - ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); - - // Adds a WoW addon file to the list of addons with a unique - // addon key to send on AIO client initialization. - // Returns true if addon was added, false if addon key is taken. - // - // It is required to call World::ForceReloadPlayerAddons() - // if addons are added after server is fully initialized - // for online players to load the added addons. - bool AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); - - // Returns pointer to an AIO script by its key and typename. - // Returns null if scriptName doesn't exist or typename was incorrect. - template - ScriptClass *GetScript(const LuaVal &key); - - private: - void OnHandle(Player* sender, LuaVal const& handlerKey, LuaVal const& args); - - LuaVal _key; - - typedef std::unordered_map HandlerMapType; - HandlerMapType _handlerMap; - - typedef std::unordered_map AIOScriptByKeyMap; - static AIOScriptByKeyMap _scriptByKeyMap; - - friend class ScriptMgr; -}; - -class AIOHandlers : public AIOScript -{ - private: - AIOHandlers(); - void HandleInit(Player* sender, LuaVal const& args); - void HandleError(Player* sender, LuaVal const& args); - - struct InitHookInfo - { - LuaVal scriptKey; - LuaVal handlerKey; - std::list argsList; - - InitHookInfo(LuaVal const& scriptKey, LuaVal const& handlerKey) - : scriptKey(scriptKey), handlerKey(handlerKey) - { } - }; - - typedef std::list HookListType; - HookListType _initHookList; - - friend class ScriptMgr; - friend class AIOScript; -}; - -// Placed here due to ScriptRegistry::AddScript dependency. -#define sScriptMgr ScriptMgr::instance() - -// namespace -// { - typedef std::vector UnusedScriptContainer; - typedef std::list UnusedScriptNamesContainer; - - extern UnusedScriptContainer UnusedScripts; - extern UnusedScriptNamesContainer UnusedScriptNames; -// } +#include "AIOScript.h" // Manages registration, loading, and execution of scripts. class TC_GAME_API ScriptMgr diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 2863c25b2d293..d19c9628100aa 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -49,6 +49,7 @@ #include "PacketUtilities.h" #include "Player.h" #include "Realm.h" +#include "AIOCodec.h" #include "ScriptMgr.h" #include "SocialMgr.h" #include "QueryHolder.h" @@ -499,22 +500,28 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) return true; } -namespace +bool WorldSession::AllowNextAIOIncomingMessage(Player* sender) { -uint32 DecodeAIOByte(char value) -{ - return uint32(static_cast(value) - 1u); -} + uint32 const minInterval = sWorld->getIntConfig(CONFIG_AIO_MSG_RATE_MS); + if (!minInterval) + return true; + + uint32 const now = getMSTime(); + if (_aioLastCompleteMessageMs && (now - _aioLastCompleteMessageMs) < minInterval) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_DEBUG, + "AIO: Rate limited incoming message from {} ({} ms since last)", sender->GetName(), now - _aioLastCompleteMessageMs); + return false; + } + + _aioLastCompleteMessageMs = now; + return true; } WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhisper(Player* sender, Player* receiver, std::string const& msg) { - size_t delimPos = msg.find('\t'); - if (delimPos == std::string::npos) - return IncomingAIOWhisperResult::NotAIO; - - std::string prefix = msg.substr(0, delimPos); - if (prefix != "C" + sWorld->GetAIOPrefix()) + size_t delimPos = 0; + if (!Trinity::AIO::Codec::IsClientPrefix(sWorld->GetAIOPrefix(), msg, delimPos)) return IncomingAIOWhisperResult::NotAIO; if (receiver != sender) @@ -523,11 +530,12 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis uint32 messageId = 0; if ((msg.size() - delimPos - 1) >= size_t(2)) { - messageId = DecodeAIOByte(msg[delimPos + 1]) * 254u + DecodeAIOByte(msg[delimPos + 2]); + messageId = Trinity::AIO::Codec::DecodePair(msg[delimPos + 1], msg[delimPos + 2]); if (messageId == 0) { - sScriptMgr->OnAddonMessage(sender, msg.substr(delimPos + 3)); + if (AllowNextAIOIncomingMessage(sender)) + sScriptMgr->OnAddonMessage(sender, msg.substr(delimPos + 3)); return IncomingAIOWhisperResult::Consumed; } } @@ -539,7 +547,7 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis return IncomingAIOWhisperResult::Consumed; } - uint32 parts = DecodeAIOByte(msg[delimPos + 3]) * 254u + DecodeAIOByte(msg[delimPos + 4]); + uint32 parts = Trinity::AIO::Codec::DecodePair(msg[delimPos + 3], msg[delimPos + 4]); if (parts < 2) { sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, @@ -556,7 +564,7 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis return IncomingAIOWhisperResult::DropPacket; } - uint32 partId = DecodeAIOByte(msg[delimPos + 5]) * 254u + DecodeAIOByte(msg[delimPos + 6]); + uint32 partId = Trinity::AIO::Codec::DecodePair(msg[delimPos + 5], msg[delimPos + 6]); if (partId < 1 || partId > parts) { sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, @@ -617,7 +625,8 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis for (uint32 expectedPart = 1; expectedPart <= parts; ++expectedPart) actualAIOMessage += messagePartsItr->second.Map.find(expectedPart)->second; - sScriptMgr->OnAddonMessage(sender, actualAIOMessage); + if (AllowNextAIOIncomingMessage(sender)) + sScriptMgr->OnAddonMessage(sender, actualAIOMessage); _addonMessageBuffer.erase(messagePartsItr); return IncomingAIOWhisperResult::Consumed; } diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index a549ef9526c44..a08a0979a1823 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -1370,6 +1370,9 @@ class TC_GAME_API WorldSession typedef std::map AddonMessageBufferMap; AddonMessageBufferMap _addonMessageBuffer; uint32 _aioMsgCacheSweepTimer = 0; + uint32 _aioLastCompleteMessageMs = 0; + + bool AllowNextAIOIncomingMessage(Player* sender); }; #endif /// @} diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index b2b00e4c0f08d..9c1be0ace2e28 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -91,6 +91,7 @@ #include #include #include +#include #include "AIOMsg.h" #include "smallfolk.h" @@ -1575,11 +1576,16 @@ void World::LoadConfigSettings(bool reload) m_int_configs[CONFIG_AIO_MSG_CACHE_TIME] = sConfigMgr->GetIntDefault("AIO.MsgCacheTime", 15000); m_int_configs[CONFIG_AIO_MSG_CACHE_DELAY] = sConfigMgr->GetIntDefault("AIO.MsgCacheDelay", 5000); m_int_configs[CONFIG_AIO_MAX_BUFFER_SIZE] = sConfigMgr->GetIntDefault("AIO.MaxBufferSize", 1048576); + m_int_configs[CONFIG_AIO_MAX_INCOMING] = sConfigMgr->GetIntDefault("AIO.MaxIncomingMessageSize", 262144); + m_int_configs[CONFIG_AIO_MSG_RATE_MS] = sConfigMgr->GetIntDefault("AIO.MsgRateLimitMs", 50); m_aioclientpath = sConfigMgr->GetStringDefault("AIO.ClientScriptPath", "lua_client_scripts"); m_aioprefix = sConfigMgr->GetStringDefault("AIO.Prefix", "AIO"); if (m_aioprefix.size() > 15u) m_aioprefix = m_aioprefix.substr(0, 15); + if (m_int_configs[CONFIG_AIO_MAX_INCOMING] > m_int_configs[CONFIG_AIO_MAX_BUFFER_SIZE]) + m_int_configs[CONFIG_AIO_MAX_INCOMING] = m_int_configs[CONFIG_AIO_MAX_BUFFER_SIZE]; + // HotSwap m_bool_configs[CONFIG_HOTSWAP_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.Enabled", true); m_bool_configs[CONFIG_HOTSWAP_RECOMPILER_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnableReCompiler", true); @@ -1633,6 +1639,7 @@ void World::SetInitialWorldSettings() ///- Initialize config settings LoadConfigSettings(); + ValidateAIOSettings(); ///- Initialize Allowed Security Level LoadDBAllowedSecurityLevel(); @@ -3689,6 +3696,24 @@ bool World::RemoveAddon(std::string const& addonName, uint32* permission) return false; } +void World::ValidateAIOSettings() +{ + if (m_aioprefix.empty()) + TC_LOG_ERROR("server.loading", "AIO.Prefix must not be empty."); + + if (m_int_configs[CONFIG_AIO_MSG_MAX_LEN] != 255) + TC_LOG_WARN("server.loading", "AIO.MsgMaxLen is {} but 255 is required for 3.3.5 addon whispers.", m_int_configs[CONFIG_AIO_MSG_MAX_LEN]); + + boost::filesystem::path clientPath(m_aioclientpath); + if (!boost::filesystem::exists(clientPath)) + TC_LOG_WARN("server.loading", "AIO.ClientScriptPath '{}' does not exist (create it or fix the config).", m_aioclientpath); + else if (!boost::filesystem::is_directory(clientPath)) + TC_LOG_ERROR("server.loading", "AIO.ClientScriptPath '{}' is not a directory.", m_aioclientpath); + + TC_LOG_INFO("server.loading", "AIO: prefix '{}', client scripts '{}', max incoming {} bytes, rate limit {} ms.", + m_aioprefix, m_aioclientpath, m_int_configs[CONFIG_AIO_MAX_INCOMING], m_int_configs[CONFIG_AIO_MSG_RATE_MS]); +} + bool World::ReloadAddons() { sLog->outAIOMessage(0, LOG_LEVEL_INFO, "World::ReloadAddons()"); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index bc5d4d6203e56..ad8d7f13bb777 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -400,6 +400,8 @@ enum WorldIntConfigs : uint32 CONFIG_AIO_MSG_CACHE_TIME, CONFIG_AIO_MSG_CACHE_DELAY, CONFIG_AIO_MAX_BUFFER_SIZE, + CONFIG_AIO_MAX_INCOMING, + CONFIG_AIO_MSG_RATE_MS, CONFIG_AUCTION_GETALL_DELAY, CONFIG_AUCTION_SEARCH_DELAY, CONFIG_TALENTS_INSPECTING, @@ -806,6 +808,8 @@ class TC_GAME_API World bool RemoveAddon(std::string const& addonName, uint32* permission = nullptr); uint32 PrepareClientAddons(LuaVal const& clientData, LuaVal& addonsTable, LuaVal& cacheTable, Player* forPlayer) const; + void ValidateAIOSettings(); + void RemoveOldCorpses(); void TriggerGuidWarning(); void TriggerGuidAlert(); diff --git a/src/server/scripts/AIO/ExampleWindow.cpp b/src/server/scripts/AIO/ExampleWindow.cpp index 11835143388a3..2917bd05b18ed 100644 --- a/src/server/scripts/AIO/ExampleWindow.cpp +++ b/src/server/scripts/AIO/ExampleWindow.cpp @@ -1,4 +1,5 @@ #include "ScriptMgr.h" +#include "PlayerAIO.h" #include "Player.h" #include "Chat.h" #include "smallfolk.h" @@ -11,9 +12,9 @@ class ExampleWindowScript : public AIOScript ExampleWindowScript() : AIOScript("AIOExample"), counter(0) { - AddAddon("ExampleWindow", "ExampleWindow.lua"); + AddAddon("ExampleWindow", "ExampleWindow/ExampleWindow.lua"); AddHandler("Print", std::bind(&ExampleWindowScript::HandlePrint, this, std::placeholders::_1, std::placeholders::_2)); - AddHandler("bullshit", std::bind(&ExampleWindowScript::HandleBullshit, this, std::placeholders::_1, std::placeholders::_2)); + AddHandler("StressTest", std::bind(&ExampleWindowScript::HandleStressTest, this, std::placeholders::_1, std::placeholders::_2)); AddInitArgs("AIOExample", "Init", std::bind(&ExampleWindowScript::InitArg, this, std::placeholders::_1), std::bind(&ExampleWindowScript::InitArg, this, std::placeholders::_1)); AddInitArgs("AIOExample", "Init", std::bind(&ExampleWindowScript::InitArg2, this, std::placeholders::_1)); AddInitArgs("AIOExample", "InitB"); @@ -40,7 +41,7 @@ class ExampleWindowScript : public AIOScript return; } std::string payload(size_t(size), 'b'); - sender->AIOHandle("AIOExample", "bullshit", payload); + Trinity::AIO::Handle(sender, "AIOExample", "StressTest", payload); } catch (...) { @@ -48,13 +49,13 @@ class ExampleWindowScript : public AIOScript } } - void HandleBullshit(Player* sender, LuaVal const& args) + void HandleStressTest(Player* sender, LuaVal const& args) { LuaVal payload = args.get(4); if (!payload.isstring()) return; - ChatHandler(sender->GetSession()).PSendSysMessage("Received bullshit block (%u bytes).", uint32(payload.str().size())); + ChatHandler(sender->GetSession()).PSendSysMessage("Received StressTest block (%u bytes).", uint32(payload.str().size())); } LuaVal InitArg(Player* /*sender*/) diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt index 558023fc8bfcf..e029640a700ac 100644 --- a/src/server/scripts/CMakeLists.txt +++ b/src/server/scripts/CMakeLists.txt @@ -174,10 +174,6 @@ foreach(SCRIPT_MODULE ${SCRIPT_MODULE_LIST}) PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - if(WITH_CAIO_EXAMPLES) - target_compile_definitions(${SCRIPT_MODULE_PROJECT_NAME} PRIVATE WITH_CAIO_EXAMPLES) - endif() - set_target_properties(${SCRIPT_MODULE_PROJECT_NAME} PROPERTIES COMPILE_WARNING_AS_ERROR ${WITH_WARNINGS_AS_ERRORS} @@ -229,10 +225,6 @@ target_include_directories(scripts PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -if(WITH_CAIO_EXAMPLES) - target_compile_definitions(scripts PRIVATE WITH_CAIO_EXAMPLES) -endif() - set_target_properties(scripts PROPERTIES COMPILE_WARNING_AS_ERROR ${WITH_WARNINGS_AS_ERRORS} @@ -240,8 +232,14 @@ set_target_properties(scripts # Generate precompiled header if(USE_SCRIPTPCH) + set(SCRIPT_PCH_REUSE_PROJECTS ${DYNAMIC_SCRIPT_MODULE_PROJECTS}) + if(WITH_CAIO_EXAMPLES) + GetProjectNameOfScriptModule("AIO" SCRIPT_AIO_PROJECT_NAME) + target_compile_definitions(${SCRIPT_AIO_PROJECT_NAME} PRIVATE WITH_CAIO_EXAMPLES) + list(REMOVE_ITEM SCRIPT_PCH_REUSE_PROJECTS ${SCRIPT_AIO_PROJECT_NAME}) + endif() add_cxx_pch("scripts" ${PRIVATE_PCH_HEADER} ${DYNAMIC_SCRIPT_MODULE_PROJECTS}) - reuse_cxx_pch("${DYNAMIC_SCRIPT_MODULE_PROJECTS}" scripts) + reuse_cxx_pch("${SCRIPT_PCH_REUSE_PROJECTS}" scripts) endif() # Remove all shared libraries in the installl directory which diff --git a/src/server/scripts/Commands/cs_caio.cpp b/src/server/scripts/Commands/cs_caio.cpp index 312fe8d3b722c..e459e801416c2 100644 --- a/src/server/scripts/Commands/cs_caio.cpp +++ b/src/server/scripts/Commands/cs_caio.cpp @@ -17,6 +17,20 @@ EndScriptData */ using namespace Trinity::ChatCommands; +namespace +{ +bool IsSafeAddonRelativePath(std::string const& path) +{ + if (path.empty() || path.front() == '/' || path.front() == '\\') + return false; + + if (path.find("..") != std::string::npos) + return false; + + return path.find_first_of(":*?\"<>|") == std::string::npos; +} +} + class caio_commandscript : public CommandScript { public: @@ -65,7 +79,7 @@ class caio_commandscript : public CommandScript return false; player->SendSimpleAIOMessage(message); - handler->PSendSysMessage(LANG_SENDMESSAGE, target.GetName().c_str(), message.c_str()); + handler->PSendSysMessage("CAIO: sent %zu bytes to %s.", message.size(), target.GetName().c_str()); return true; } @@ -101,7 +115,7 @@ class caio_commandscript : public CommandScript uint32 perm = permission.value_or(AIO_DEFAULT_ADDON_PERMISSION); sWorld->SendAllSimpleAIOMessage(message, perm); - handler->PSendSysMessage(LANG_SENDMESSAGE, "all players", message.c_str()); + handler->PSendSysMessage("CAIO: sent %zu bytes to all players.", message.size()); return true; } @@ -131,6 +145,12 @@ class caio_commandscript : public CommandScript static bool HandleAddAddonCommand(ChatHandler* handler, std::string addonName, QuotedString addonFile, Optional permission) { + if (!IsSafeAddonRelativePath(addonFile)) + { + handler->SendSysMessage("CAIO: addon file path must be relative to AIO.ClientScriptPath and must not contain '..'."); + return false; + } + uint32 perm = permission.value_or(AIO_DEFAULT_ADDON_PERMISSION); World::AIOAddon newAddon(addonName, addonFile, perm); if (sWorld->AddAddon(newAddon)) diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 6b2f05e6b5669..cd46ff119008c 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -4133,6 +4133,14 @@ PacketSpoof.BanDuration = 86400 # Maximum total bytes buffered per long AIO message while reassembling parts. # Default: 1048576 # +# AIO.MaxIncomingMessageSize +# Maximum size (bytes) of a fully reassembled client-to-server AIO payload. +# Default: 262144 (cannot exceed AIO.MaxBufferSize) +# +# AIO.MsgRateLimitMs +# Minimum milliseconds between completed incoming AIO messages per session (0 = disabled). +# Default: 50 +# # AIO.ClientScriptPath # Path to client lua scripts to send to the client # Default: "lua_client_scripts" @@ -4145,6 +4153,8 @@ AIO.InitCooldown = 5000 AIO.MsgCacheTime = 15000 AIO.MsgCacheDelay = 5000 AIO.MaxBufferSize = 1048576 +AIO.MaxIncomingMessageSize = 262144 +AIO.MsgRateLimitMs = 50 AIO.ClientScriptPath = "lua_client_scripts" # diff --git a/tests/game/AIOCodec.cpp b/tests/game/AIOCodec.cpp new file mode 100644 index 0000000000000..1132add888830 --- /dev/null +++ b/tests/game/AIOCodec.cpp @@ -0,0 +1,52 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#include "tc_catch2.h" + +#include "AIOCodec.h" + +using namespace Trinity::AIO::Codec; + +TEST_CASE("AIO codec byte decoding", "[AIO]") +{ + REQUIRE(DecodeByte('\x01') == 0); + REQUIRE(DecodeByte('\xFF') == 254); + REQUIRE(DecodePair('\x01', '\x01') == 0); + REQUIRE(DecodePair('\x02', '\x02') == 255); +} + +TEST_CASE("AIO client prefix detection", "[AIO]") +{ + SECTION("matches configured prefix") + { + size_t delimPos = 0; + REQUIRE(IsClientPrefix("AIO", "CAIO\tpayload", delimPos)); + REQUIRE(delimPos == 3); + } + + SECTION("rejects server prefix") + { + size_t delimPos = 0; + REQUIRE_FALSE(IsClientPrefix("AIO", "SAIO\tpayload", delimPos)); + } + + SECTION("rejects missing tab") + { + size_t delimPos = 0; + REQUIRE_FALSE(IsClientPrefix("AIO", "CAIOpayload", delimPos)); + } +} From 65e091e2f82f1d0604981ffc5baea5345320f7d8 Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Fri, 5 Jun 2026 02:32:38 +0300 Subject: [PATCH 27/33] Harden CAIO: safe parsing, path checks, AIOScript split, tests. Add AIOUtil/WorldAIO, move AIOHandlers to AIOScript.cpp, wire prefix cache, incoming rate limits and reassembly guards, max blocks/parse failures config, ExampleWindow client lua and stub loader, and AIO unit tests. --- CAIO_README.md | 4 +- src/server/game/AIO/AIOCodec.h | 4 +- src/server/game/AIO/AIOUtil.cpp | 75 +++++ src/server/game/AIO/AIOUtil.h | 49 ++++ src/server/game/AIO/WorldAIO.cpp | 50 ++++ src/server/game/AIO/WorldAIO.h | 33 +++ src/server/game/Chat/AIOMsg.h | 1 - src/server/game/Scripting/AIOScript.cpp | 277 ++++++++++++++++++ src/server/game/Scripting/AIOScript.h | 46 +-- src/server/game/Scripting/ScriptMgr.cpp | 239 ++------------- src/server/game/Scripting/ScriptMgr.h | 5 +- src/server/game/Server/WorldSession.cpp | 104 ++++++- src/server/game/Server/WorldSession.h | 12 +- src/server/game/World/World.cpp | 39 ++- src/server/game/World/World.h | 4 + src/server/scripts/AIO/ExampleWindowStub.cpp | 24 ++ src/server/scripts/AIO/aio_script_loader.cpp | 7 +- src/server/scripts/CMakeLists.txt | 4 + src/server/scripts/Commands/cs_caio.cpp | 23 +- .../ExampleWindow/ExampleWindow.lua | 26 ++ src/server/worldserver/worldserver.conf.dist | 10 + tests/game/AIOCodec.cpp | 8 +- tests/game/AIOUtil.cpp | 70 +++++ 23 files changed, 808 insertions(+), 306 deletions(-) create mode 100644 src/server/game/AIO/AIOUtil.cpp create mode 100644 src/server/game/AIO/AIOUtil.h create mode 100644 src/server/game/AIO/WorldAIO.cpp create mode 100644 src/server/game/AIO/WorldAIO.h create mode 100644 src/server/game/Scripting/AIOScript.cpp create mode 100644 src/server/scripts/AIO/ExampleWindowStub.cpp create mode 100644 src/server/worldserver/lua_client_scripts/ExampleWindow/ExampleWindow.lua create mode 100644 tests/game/AIOUtil.cpp diff --git a/CAIO_README.md b/CAIO_README.md index 31784859f60bb..6a45fed45c329 100644 --- a/CAIO_README.md +++ b/CAIO_README.md @@ -12,14 +12,14 @@ AIO version **1.75** — must match `AIO_VERSION` in your server and client `AIO ## Install + Clone this repository/branch or merge with your own TrinityCore 3.3.5 branch -+ `git submodule update --init --recursive` (required for `dep/smallfolk_cpp/smallfolk_cpp`, tracks [smallfolk_cpp](https://github.com/Rochet2/smallfolk_cpp) **master**, currently v2.x) ++ `git submodule update --init --recursive` (required for `dep/smallfolk_cpp/smallfolk_cpp` — [smallfolk_cpp](https://github.com/Rochet2/smallfolk_cpp); pin the submodule commit in production, e.g. `git -C dep/smallfolk_cpp/smallfolk_cpp checkout ` after init) + Build/Install TrinityCore + [Install(Add) (C)AIO scripts](#api-reference) — see also `doc/CAIO_SCRIPT_EXAMPLE.md` + Run SQL files from `TrinityCore_Installation_Dir/sql/CAIO` (`Auth.sql` on auth DB, `World.sql` on world DB) + Copy `AIO_Client` from your AIO tree to `WoW_Installation_Dir/Interface/AddOns/AIO_Client` (use the same AIO repo/commit as the server expects) + Copy server-side client addon sources into `TrinityCore_Installation_Dir/lua_client_scripts` (one folder per addon, e.g. `lua_client_scripts/ExampleWindow/ExampleWindow.lua`) + Set `AIO.MsgMaxLen` to **255** in `worldserver.conf` (WoW addon whisper limit; matches client `AIO.lua` when `AIO_SERVER` is false) -+ Optional: build with `-DWITH_CAIO_EXAMPLES=ON` to include the `ExampleWindow` test script ++ Optional: build with `-DWITH_CAIO_EXAMPLES=ON` to include the `ExampleWindow` test script (ships `worldserver/lua_client_scripts/ExampleWindow/ExampleWindow.lua` for install) ## Build notes (TrinityCore 3.3.5 + CAIO) diff --git a/src/server/game/AIO/AIOCodec.h b/src/server/game/AIO/AIOCodec.h index e8535902121fd..06815448b9d9e 100644 --- a/src/server/game/AIO/AIOCodec.h +++ b/src/server/game/AIO/AIOCodec.h @@ -35,13 +35,13 @@ namespace Trinity::AIO::Codec return DecodeByte(high) * 254u + DecodeByte(low); } - inline bool IsClientPrefix(std::string const& prefix, std::string const& msg, size_t& delimPosOut) + inline bool IsClientPrefix(std::string const& clientWirePrefix, std::string const& msg, size_t& delimPosOut) { delimPosOut = msg.find('\t'); if (delimPosOut == std::string::npos) return false; - return msg.compare(0, delimPosOut, "C" + prefix) == 0; + return delimPosOut == clientWirePrefix.size() && msg.compare(0, delimPosOut, clientWirePrefix) == 0; } } diff --git a/src/server/game/AIO/AIOUtil.cpp b/src/server/game/AIO/AIOUtil.cpp new file mode 100644 index 0000000000000..49053103798f0 --- /dev/null +++ b/src/server/game/AIO/AIOUtil.cpp @@ -0,0 +1,75 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#include "AIOUtil.h" + +#include + +namespace Trinity::AIO +{ +bool IsSafeAddonRelativePath(std::string const& path) +{ + if (path.empty() || path.front() == '/' || path.front() == '\\') + return false; + + if (path.find("..") != std::string::npos) + return false; + + return path.find_first_of(":*?\"<>|") == std::string::npos; +} + +LoadMessageOutcome TryLoadIncomingMessage(std::string const& message, uint32 maxBytes, uint32 maxBlocks) +{ + LoadMessageOutcome outcome; + + if (message.size() > maxBytes) + { + outcome.result = LoadMessageResult::Oversize; + return outcome; + } + + try + { + outcome.table = LuaVal::loads(message); + } + catch (std::exception const&) + { + outcome.result = LoadMessageResult::ParseError; + return outcome; + } + catch (...) + { + outcome.result = LoadMessageResult::ParseError; + return outcome; + } + + if (!outcome.table.istable()) + { + outcome.result = LoadMessageResult::NotTable; + return outcome; + } + + if (outcome.table.len() > maxBlocks) + { + outcome.result = LoadMessageResult::TooManyBlocks; + return outcome; + } + + outcome.result = LoadMessageResult::Ok; + return outcome; +} +} diff --git a/src/server/game/AIO/AIOUtil.h b/src/server/game/AIO/AIOUtil.h new file mode 100644 index 0000000000000..d5fb87076748c --- /dev/null +++ b/src/server/game/AIO/AIOUtil.h @@ -0,0 +1,49 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#ifndef TRINITY_AIO_UTIL_H +#define TRINITY_AIO_UTIL_H + +#include "Define.h" +#include "smallfolk.h" +#include + +namespace Trinity::AIO +{ + constexpr uint32 MAX_BLOCK_ARGS = 15; + + TC_GAME_API bool IsSafeAddonRelativePath(std::string const& path); + + enum class LoadMessageResult : uint8 + { + Ok, + Oversize, + ParseError, + NotTable, + TooManyBlocks + }; + + struct LoadMessageOutcome + { + LoadMessageResult result = LoadMessageResult::ParseError; + LuaVal table; + }; + + TC_GAME_API LoadMessageOutcome TryLoadIncomingMessage(std::string const& message, uint32 maxBytes, uint32 maxBlocks); +} + +#endif diff --git a/src/server/game/AIO/WorldAIO.cpp b/src/server/game/AIO/WorldAIO.cpp new file mode 100644 index 0000000000000..3e3be6acbb724 --- /dev/null +++ b/src/server/game/AIO/WorldAIO.cpp @@ -0,0 +1,50 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#include "WorldAIO.h" +#include "AIOMsg.h" +#include "Player.h" +#include "World.h" +#include "WorldSession.h" + +namespace Trinity::AIO +{ +void MessageAll(World* world, AIOMsg& msg, uint32 permission) +{ + if (!world) + return; + + std::string const messageStr = msg.dumps(); + for (SessionMap::const_iterator itr = world->GetAllSessions().begin(); itr != world->GetAllSessions().end(); ++itr) + { + if (itr->second->GetPlayer() && itr->second->HasPermission(permission)) + itr->second->GetPlayer()->SendSimpleAIOMessage(messageStr); + } +} + +void SendAllSimple(World* world, std::string const& message, uint32 permission) +{ + if (!world) + return; + + for (SessionMap::const_iterator itr = world->GetAllSessions().begin(); itr != world->GetAllSessions().end(); ++itr) + { + if (itr->second->GetPlayer() && itr->second->HasPermission(permission)) + itr->second->GetPlayer()->SendSimpleAIOMessage(message); + } +} +} diff --git a/src/server/game/AIO/WorldAIO.h b/src/server/game/AIO/WorldAIO.h new file mode 100644 index 0000000000000..8095c25b961a2 --- /dev/null +++ b/src/server/game/AIO/WorldAIO.h @@ -0,0 +1,33 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#ifndef TRINITY_WORLD_AIO_H +#define TRINITY_WORLD_AIO_H + +#include "AIO.h" +#include + +class AIOMsg; +class World; + +namespace Trinity::AIO +{ + TC_GAME_API void MessageAll(World* world, AIOMsg& msg, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); + TC_GAME_API void SendAllSimple(World* world, std::string const& message, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); +} + +#endif diff --git a/src/server/game/Chat/AIOMsg.h b/src/server/game/Chat/AIOMsg.h index 69bdb05969b4f..582a910d13c47 100644 --- a/src/server/game/Chat/AIOMsg.h +++ b/src/server/game/Chat/AIOMsg.h @@ -49,7 +49,6 @@ class AIOMsg private: LuaVal _val; - friend class Player; }; #endif diff --git a/src/server/game/Scripting/AIOScript.cpp b/src/server/game/Scripting/AIOScript.cpp new file mode 100644 index 0000000000000..11b3eba39c93d --- /dev/null +++ b/src/server/game/Scripting/AIOScript.cpp @@ -0,0 +1,277 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#include "ScriptMgr.h" +#include "AIOUtil.h" +#include "Log.h" +#include "Player.h" +#include "PlayerAIO.h" +#include "ScriptMgr.h" +#include "World.h" +#include +#include + +class AIOHandlers : public AIOScript +{ + public: + struct InitHookInfo + { + LuaVal scriptKey; + LuaVal handlerKey; + std::list argsList; + + InitHookInfo(LuaVal const& scriptKey, LuaVal const& handlerKey) + : scriptKey(scriptKey), handlerKey(handlerKey) + { } + }; + + using HookListType = std::list; + + void RegisterInitMessageHook(AIOScript::InitMessageFunc func) + { + _initMessageHooks.push_back(std::move(func)); + } + + void RegisterInitArgs(LuaVal const& scriptKey, LuaVal const& handlerKey, + AIOScript::ArgFunc a1, AIOScript::ArgFunc a2, AIOScript::ArgFunc a3, + AIOScript::ArgFunc a4, AIOScript::ArgFunc a5, AIOScript::ArgFunc a6) + { + std::list* list = nullptr; + for (HookListType::iterator itr = _initHookList.begin(); itr != _initHookList.end(); ++itr) + { + if (itr->scriptKey == scriptKey && itr->handlerKey == handlerKey) + { + list = &itr->argsList; + break; + } + } + + if (!list) + { + _initHookList.push_back(InitHookInfo(scriptKey, handlerKey)); + list = &_initHookList.back().argsList; + } + + if (a1) + list->push_back(a1); + if (a2) + list->push_back(a2); + if (a3) + list->push_back(a3); + if (a4) + list->push_back(a4); + if (a5) + list->push_back(a5); + if (a6) + list->push_back(a6); + } + + private: + AIOHandlers(); + void HandleInit(Player* sender, LuaVal const& args); + void HandleError(Player* sender, LuaVal const& args); + + HookListType _initHookList; + std::list _initMessageHooks; +}; + +AIOScript::AIOScriptByKeyMap AIOScript::_scriptByKeyMap = AIOScript::AIOScriptByKeyMap(); + +AIOScript::~AIOScript() +{ + _scriptByKeyMap.erase(_key); +} + +void AIOScript::AddOnInit(InitMessageFunc func) +{ + sScriptMgr->RegisterAIOInitHook(std::move(func)); +} + +void AIOScript::AddInitArgs(LuaVal const& scriptKey, LuaVal const& handlerKey, ArgFunc a1, ArgFunc a2, ArgFunc a3, ArgFunc a4, ArgFunc a5, ArgFunc a6) +{ + sScriptMgr->RegisterAIOInitArgs(scriptKey, handlerKey, std::move(a1), std::move(a2), std::move(a3), std::move(a4), std::move(a5), std::move(a6)); +} + +AIOScript::AIOScript(LuaVal const& scriptKey) + : ScriptObject(scriptKey.tostring().c_str()), _key(scriptKey) +{ + if (AIOScript::_scriptByKeyMap.find(scriptKey) != AIOScript::_scriptByKeyMap.end()) + { + sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '{}' of type tag '{}' already exist. Use another key.", scriptKey.tostring(), static_cast(scriptKey.typetag())); + ASSERT(false); + } + ScriptRegistry::Instance()->AddScript(this); + AIOScript::_scriptByKeyMap[scriptKey] = this; +} + +AIOScript* AIOScript::FindByKey(LuaVal const& scriptKey) +{ + AIOScriptByKeyMap::const_iterator itr = _scriptByKeyMap.find(scriptKey); + if (itr == _scriptByKeyMap.end()) + return nullptr; + + return itr->second; +} + +void AIOScript::HandleAddonBlock(Player* sender, LuaVal const& handlerKey, LuaVal const& args) +{ + OnHandle(sender, handlerKey, args); +} + +void AIOScript::DispatchIncomingBlocks(Player* sender, LuaVal const& mainTable) +{ + for (unsigned int i = 1; i <= mainTable.len(); ++i) + { + LuaVal block = mainTable.get(static_cast(i)); + if (!block.istable()) + continue; + + LuaVal nArgsVal = block.get(1); + LuaVal scriptKeyVal = block.get(2); + LuaVal handlerKeyVal = block.get(3); + if (!nArgsVal.isnumber() || scriptKeyVal.isnil() || handlerKeyVal.isnil()) + continue; + + if (nArgsVal.num() > double(MAX_BLOCK_ARGS)) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "AIO: Block from '{}' has over {} arguments (n={:.0f}). Sender: {}", + scriptKeyVal.tostring(), MAX_BLOCK_ARGS, nArgsVal.num(), sender->GetName()); + continue; + } + + if (AIOScript* aioScript = AIOScript::FindByKey(scriptKeyVal)) + aioScript->HandleAddonBlock(sender, handlerKeyVal, block); + } +} + +bool AIOScript::AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission) +{ + return sWorld->AddAddon(World::AIOAddon(addonName, addonFile, permission)); +} + +void AIOScript::OnHandle(Player* sender, LuaVal const& handlerKey, LuaVal const& args) +{ + HandlerMapType::const_iterator itr = _handlerMap.find(handlerKey); + if (itr != _handlerMap.end()) + { + itr->second(sender, args); + return; + } + + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIO: No handler '{}' on script '{}'. Sender: {}", + handlerKey.tostring(), _key.tostring(), sender->GetName()); +} + +AIOHandlers::AIOHandlers() + : AIOScript("AIO") +{ + AddHandler("Init", std::bind(&AIOHandlers::HandleInit, this, std::placeholders::_1, std::placeholders::_2)); + AddHandler("Error", std::bind(&AIOHandlers::HandleError, this, std::placeholders::_1, std::placeholders::_2)); +} + +void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) +{ + if (sender->isAIOInitOnCooldown()) + return; + + LuaVal versionVal = args.get(4); + LuaVal clientDataVal = args.get(5); + if (!versionVal.isnumber() || !clientDataVal.istable()) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIOHandlers::HandleInit: Invalid version value or clientData value. Sender: {}, Args: {}", sender->GetName(), args.dumps()); + return; + } + + if (std::abs(versionVal.num() - AIO_VERSION) > 0.01) + { + Trinity::AIO::Handle(sender, "AIO", "Init", AIO_VERSION); + return; + } + + sender->setAIOIntOnCooldown(true); + + LuaVal addonTable(TTABLE); + LuaVal cacheTable(TTABLE); + uint32 const nAddons = sWorld->PrepareClientAddons(clientDataVal, addonTable, cacheTable, sender); + + LuaVal argsToSend(TTABLE); + + uint32 blockIndex = 1; + for (HookListType::const_iterator itr = _initHookList.begin(); itr != _initHookList.end(); ++itr) + { + uint32 index = 3; + LuaVal hookBlock(TTABLE); + + hookBlock[1] = static_cast(itr->argsList.size()) + 1u; + hookBlock[2] = itr->scriptKey; + hookBlock[3] = itr->handlerKey; + for (std::list::const_iterator it = itr->argsList.begin(); it != itr->argsList.end(); ++it) + hookBlock[static_cast(++index)] = (*it)(sender); + + argsToSend[static_cast(++blockIndex)] = hookBlock; + } + + LuaVal AIOInitBlock(TTABLE); + AIOInitBlock[1] = 5; + AIOInitBlock[2] = "AIO"; + AIOInitBlock[3] = "Init"; + AIOInitBlock[4] = AIO_VERSION; + AIOInitBlock[5] = static_cast(nAddons); + AIOInitBlock[6] = addonTable; + AIOInitBlock[7] = cacheTable; + + argsToSend[1] = AIOInitBlock; + + for (AIOScript::InitMessageFunc const& hook : _initMessageHooks) + hook(sender, argsToSend); + + sender->SendSimpleAIOMessage(argsToSend.dumps()); +} + +void AIOHandlers::HandleError(Player* sender, LuaVal const& args) +{ + LuaVal msgVal = args.get(4); + if (!msgVal.isstring()) + return; + + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "{} Received client addon error: {}", sender->GetSession()->GetPlayerInfo(), msgVal.str()); +} + +AIOHandlers* CreateAIOHandlers() +{ + return new AIOHandlers(); +} + +void DestroyAIOHandlers(AIOHandlers* handlers) +{ + delete handlers; +} + +void RegisterAIOInitHookOnHandlers(AIOHandlers* handlers, AIOScript::InitMessageFunc func) +{ + if (handlers) + handlers->RegisterInitMessageHook(std::move(func)); +} + +void RegisterAIOInitArgsOnHandlers(AIOHandlers* handlers, LuaVal const& scriptKey, LuaVal const& handlerKey, + AIOScript::ArgFunc a1, AIOScript::ArgFunc a2, AIOScript::ArgFunc a3, + AIOScript::ArgFunc a4, AIOScript::ArgFunc a5, AIOScript::ArgFunc a6) +{ + if (handlers) + handlers->RegisterInitArgs(scriptKey, handlerKey, std::move(a1), std::move(a2), std::move(a3), std::move(a4), std::move(a5), std::move(a6)); +} diff --git a/src/server/game/Scripting/AIOScript.h b/src/server/game/Scripting/AIOScript.h index d5f391b7e5cad..154e21d9ea3eb 100644 --- a/src/server/game/Scripting/AIOScript.h +++ b/src/server/game/Scripting/AIOScript.h @@ -23,11 +23,9 @@ #include "Define.h" #include "smallfolk.h" #include -#include #include class Player; -class ScriptMgr; // Inherit AIOScript for server-side AIO handlers (LuaVal / smallfolk_cpp). // Full example: doc/CAIO_SCRIPT_EXAMPLE.md @@ -42,6 +40,7 @@ class TC_GAME_API AIOScript : public ScriptObject static AIOScript* FindByKey(LuaVal const& scriptKey); void HandleAddonBlock(Player* sender, LuaVal const& handlerKey, LuaVal const& args); + static void DispatchIncomingBlocks(Player* sender, LuaVal const& mainTable); using HandlerFunc = std::function; using ArgFunc = std::function; @@ -56,7 +55,6 @@ class TC_GAME_API AIOScript : public ScriptObject ArgFunc a1 = ArgFunc(), ArgFunc a2 = ArgFunc(), ArgFunc a3 = ArgFunc(), ArgFunc a4 = ArgFunc(), ArgFunc a5 = ArgFunc(), ArgFunc a6 = ArgFunc()); - // Stock AIO.AddOnInit parity: mutate or replace the full outgoing init message table before send. void AddOnInit(InitMessageFunc func); bool AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); @@ -74,39 +72,6 @@ class TC_GAME_API AIOScript : public ScriptObject using AIOScriptByKeyMap = std::unordered_map; static AIOScriptByKeyMap _scriptByKeyMap; - - friend class ScriptMgr; - friend class AIOHandlers; -}; - -class TC_GAME_API AIOHandlers : public AIOScript -{ - public: - struct InitHookInfo - { - LuaVal scriptKey; - LuaVal handlerKey; - std::list argsList; - - InitHookInfo(LuaVal const& scriptKey, LuaVal const& handlerKey) - : scriptKey(scriptKey), handlerKey(handlerKey) - { } - }; - - using HookListType = std::list; - - void RegisterInitMessageHook(AIOScript::InitMessageFunc func); - - private: - AIOHandlers(); - void HandleInit(Player* sender, LuaVal const& args); - void HandleError(Player* sender, LuaVal const& args); - - HookListType _initHookList; - std::list _initMessageHooks; - - friend class ScriptMgr; - friend class AIOScript; }; template @@ -115,4 +80,13 @@ ScriptClass* AIOScript::GetScript(LuaVal const& scriptKey) return dynamic_cast(FindByKey(scriptKey)); } +class AIOHandlers; + +TC_GAME_API AIOHandlers* CreateAIOHandlers(); +TC_GAME_API void DestroyAIOHandlers(AIOHandlers* handlers); +TC_GAME_API void RegisterAIOInitHookOnHandlers(AIOHandlers* handlers, AIOScript::InitMessageFunc func); +TC_GAME_API void RegisterAIOInitArgsOnHandlers(AIOHandlers* handlers, LuaVal const& scriptKey, LuaVal const& handlerKey, + AIOScript::ArgFunc a1, AIOScript::ArgFunc a2, AIOScript::ArgFunc a3, + AIOScript::ArgFunc a4, AIOScript::ArgFunc a5, AIOScript::ArgFunc a6); + #endif diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 75761a5934122..720b4247938e0 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -30,8 +30,9 @@ #include "MapManager.h" #include "ObjectMgr.h" #include "OutdoorPvPMgr.h" +#include "AIOUtil.h" #include "Player.h" -#include "PlayerAIO.h" +#include "WorldSession.h" #include "ScriptReloadMgr.h" #include "ScriptSystem.h" #include "SmartAI.h" @@ -1047,7 +1048,7 @@ void ScriptMgr::Initialize() TC_LOG_INFO("server.loading", "Loading C++ scripts"); FillSpellSummary(); - _aioHandlers = new AIOHandlers(); + _aioHandlers = CreateAIOHandlers(); // Load core scripts SetScriptContext(GetNameOfStaticContext()); @@ -1134,8 +1135,7 @@ void ScriptMgr::Unload() { sScriptRegistryCompositum->Unload(); AIOScript::_scriptByKeyMap.clear(); - delete _aioHandlers; - _aioHandlers = nullptr; + DestroyAIOHandlers(_aioHandlers); delete[] SpellSummary; delete[] UnitAI::AISpellInfo; @@ -2583,22 +2583,17 @@ PlayerScript::PlayerScript(char const* name) ScriptRegistry::Instance()->AddScript(this); } -AIOScript::AIOScriptByKeyMap AIOScript::_scriptByKeyMap = AIOScript::AIOScriptByKeyMap(); - -AIOScript::~AIOScript() -{ - _scriptByKeyMap.erase(_key); -} - -void AIOScript::AddOnInit(InitMessageFunc func) +void ScriptMgr::RegisterAIOInitHook(AIOScript::InitMessageFunc func) { - if (AIOHandlers* handler = sScriptMgr->_aioHandlers) - handler->RegisterInitMessageHook(std::move(func)); + RegisterAIOInitHookOnHandlers(_aioHandlers, std::move(func)); } -void AIOHandlers::RegisterInitMessageHook(AIOScript::InitMessageFunc func) +void ScriptMgr::RegisterAIOInitArgs(LuaVal const& scriptKey, LuaVal const& handlerKey, + AIOScript::ArgFunc a1, AIOScript::ArgFunc a2, AIOScript::ArgFunc a3, + AIOScript::ArgFunc a4, AIOScript::ArgFunc a5, AIOScript::ArgFunc a6) { - _initMessageHooks.push_back(std::move(func)); + RegisterAIOInitArgsOnHandlers(_aioHandlers, scriptKey, handlerKey, + std::move(a1), std::move(a2), std::move(a3), std::move(a4), std::move(a5), std::move(a6)); } void ScriptMgr::OnAddonMessage(Player* sender, std::string const& message) @@ -2607,204 +2602,32 @@ void ScriptMgr::OnAddonMessage(Player* sender, std::string const& message) return; uint32 const maxIncoming = sWorld->getIntConfig(CONFIG_AIO_MAX_INCOMING); - if (message.size() > maxIncoming) - { - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, - "AIO: Incoming message size {} exceeds limit {}. Sender: {}", message.size(), maxIncoming, sender->GetName()); - return; - } - - LuaVal mainTable = LuaVal::loads(message); - if (!mainTable.istable()) - { - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_DEBUG, - "AIO: Ignoring non-table addon payload from {} ({} bytes)", sender->GetName(), message.size()); - return; - } + uint32 const maxBlocks = sWorld->getIntConfig(CONFIG_AIO_MAX_BLOCKS); + Trinity::AIO::LoadMessageOutcome const outcome = Trinity::AIO::TryLoadIncomingMessage(message, maxIncoming, maxBlocks); - // Call handlers from all blocks in order - for (unsigned int i = 1; i <= mainTable.len(); ++i) + switch (outcome.result) { - LuaVal block = mainTable.get(static_cast(i)); - if (!block.istable()) - continue; - - LuaVal nArgsVal = block.get(1); - LuaVal scriptKeyVal = block.get(2); - LuaVal handlerKeyVal = block.get(3); - if (!nArgsVal.isnumber() || scriptKeyVal.isnil() || handlerKeyVal.isnil()) - continue; - - if (nArgsVal.num() > 15.0) - { + case Trinity::AIO::LoadMessageResult::Oversize: sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, - "AIO: Block from '{}' has over 15 arguments (n={:.0f}). Sender: {}", - scriptKeyVal.tostring(), nArgsVal.num(), sender->GetName()); - continue; - } - - if (AIOScript* aioScript = AIOScript::FindByKey(scriptKeyVal)) - aioScript->HandleAddonBlock(sender, handlerKeyVal, block); - } -} - -AIOScript::AIOScript(LuaVal const& scriptKey) - : ScriptObject(scriptKey.tostring().c_str()), _key(scriptKey) -{ - if (AIOScript::_scriptByKeyMap.find(scriptKey) != AIOScript::_scriptByKeyMap.end()) - { - sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '{}' of type tag '{}' already exist. Use another key.", scriptKey.tostring(), static_cast(scriptKey.typetag())); - ASSERT(false); - } - ScriptRegistry::Instance()->AddScript(this); - AIOScript::_scriptByKeyMap[scriptKey] = this; -} - -void AIOScript::AddInitArgs(const LuaVal &scriptKey, const LuaVal &handlerKey, ArgFunc a1, ArgFunc a2, ArgFunc a3, ArgFunc a4, ArgFunc a5, ArgFunc a6) -{ - AIOHandlers* handler = sScriptMgr->_aioHandlers; - if (!handler) - return; - - // Look for hook - std::list* list = nullptr; - for (AIOHandlers::HookListType::iterator itr = handler->_initHookList.begin(); itr != handler->_initHookList.end(); ++itr) - { - if (itr->scriptKey == scriptKey && itr->handlerKey == handlerKey) - { - list = &itr->argsList; + "AIO: Incoming message size {} exceeds limit {}. Sender: {}", message.size(), maxIncoming, sender->GetName()); + return; + case Trinity::AIO::LoadMessageResult::ParseError: + if (WorldSession* session = sender->GetSession()) + session->NotifyAIOIncomingParseFailure(sender); + return; + case Trinity::AIO::LoadMessageResult::NotTable: + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_DEBUG, + "AIO: Ignoring non-table addon payload from {} ({} bytes)", sender->GetName(), message.size()); + return; + case Trinity::AIO::LoadMessageResult::TooManyBlocks: + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "AIO: Incoming message has {} blocks (limit {}). Sender: {}", outcome.table.len(), maxBlocks, sender->GetName()); + return; + case Trinity::AIO::LoadMessageResult::Ok: break; - } } - // Add hook - if (!list) - { - handler->_initHookList.push_back(AIOHandlers::InitHookInfo(scriptKey, handlerKey)); - list = &handler->_initHookList.back().argsList; - } - - // Add args - if (a1) - list->push_back(a1); - if (a2) - list->push_back(a2); - if (a3) - list->push_back(a3); - if (a4) - list->push_back(a4); - if (a5) - list->push_back(a5); - if (a6) - list->push_back(a6); -} - -AIOScript* AIOScript::FindByKey(LuaVal const& scriptKey) -{ - AIOScriptByKeyMap::const_iterator itr = _scriptByKeyMap.find(scriptKey); - if (itr == _scriptByKeyMap.end()) - return nullptr; - - return itr->second; -} - -void AIOScript::HandleAddonBlock(Player* sender, LuaVal const& handlerKey, LuaVal const& args) -{ - OnHandle(sender, handlerKey, args); -} - -bool AIOScript::AddAddon(std::string const& addonName, std::string const& addonFile, uint32 permission) -{ - return sWorld->AddAddon(World::AIOAddon(addonName, addonFile, permission)); -} - -void AIOScript::OnHandle(Player* sender, LuaVal const& handlerKey, LuaVal const& args) -{ - HandlerMapType::const_iterator itr = _handlerMap.find(handlerKey); - if (itr != _handlerMap.end()) - { - itr->second(sender, args); - return; - } - - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIO: No handler '{}' on script '{}'. Sender: {}", - handlerKey.tostring(), _key.tostring(), sender->GetName()); -} - -AIOHandlers::AIOHandlers() - : AIOScript("AIO") -{ - AddHandler("Init", std::bind(&AIOHandlers::HandleInit, this, std::placeholders::_1, std::placeholders::_2)); - AddHandler("Error", std::bind(&AIOHandlers::HandleError, this, std::placeholders::_1, std::placeholders::_2)); -} - -void AIOHandlers::HandleInit(Player* sender, LuaVal const& args) -{ - // Init hasn't cooled down - if (sender->isAIOInitOnCooldown()) - return; - - LuaVal versionVal = args.get(4); - LuaVal clientDataVal = args.get(5); - if (!versionVal.isnumber() || !clientDataVal.istable()) - { - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIOHandlers::HandleInit: Invalid version value or clientData value. Sender: {}, Args: {}", sender->GetName(), args.dumps()); - return; - } - - if (std::abs(versionVal.num() - AIO_VERSION) > 0.01) - { - Trinity::AIO::Handle(sender, "AIO", "Init", AIO_VERSION); - return; - } - - sender->setAIOIntOnCooldown(true); - - LuaVal addonTable(TTABLE); - LuaVal cacheTable(TTABLE); - uint32 const nAddons = sWorld->PrepareClientAddons(clientDataVal, addonTable, cacheTable, sender); - - LuaVal argsToSend(TTABLE); - - uint32 blockIndex = 1; - for (HookListType::const_iterator itr = _initHookList.begin(); itr != _initHookList.end(); ++itr) - { - uint32 index = 3; - LuaVal hookBlock(TTABLE); - - hookBlock[1] = static_cast(itr->argsList.size()) + 1u; - hookBlock[2] = itr->scriptKey; - hookBlock[3] = itr->handlerKey; - for (std::list::const_iterator it = itr->argsList.begin(); it != itr->argsList.end(); ++it) - hookBlock[static_cast(++index)] = (*it)(sender); - - argsToSend[static_cast(++blockIndex)] = hookBlock; - } - - LuaVal AIOInitBlock(TTABLE); - AIOInitBlock[1] = 5; - AIOInitBlock[2] = "AIO"; - AIOInitBlock[3] = "Init"; - AIOInitBlock[4] = AIO_VERSION; - AIOInitBlock[5] = static_cast(nAddons); - AIOInitBlock[6] = addonTable; - AIOInitBlock[7] = cacheTable; - - argsToSend[1] = AIOInitBlock; - - for (AIOScript::InitMessageFunc const& hook : _initMessageHooks) - hook(sender, argsToSend); - - sender->SendSimpleAIOMessage(argsToSend.dumps()); -} - -void AIOHandlers::HandleError(Player* sender, LuaVal const& args) -{ - LuaVal msgVal = args.get(4); - if (!msgVal.isstring()) - return; - - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "{} Received client addon error: {}", sender->GetSession()->GetPlayerInfo(), msgVal.str()); + AIOScript::DispatchIncomingBlocks(sender, outcome.table); } void PlayerScript::OnPVPKill(Player* /*killer*/, Player* /*killed*/) diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index a564538761c83..6cd35fe204659 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -821,7 +821,6 @@ class TC_GAME_API GroupScript : public ScriptObject class TC_GAME_API ScriptMgr { friend class ScriptObject; - friend class AIOScript; private: ScriptMgr(); @@ -1090,6 +1089,10 @@ class TC_GAME_API ScriptMgr public: /* AIOScript */ void OnAddonMessage(Player* sender, std::string const& message); + void RegisterAIOInitHook(AIOScript::InitMessageFunc func); + void RegisterAIOInitArgs(LuaVal const& scriptKey, LuaVal const& handlerKey, + AIOScript::ArgFunc a1, AIOScript::ArgFunc a2, AIOScript::ArgFunc a3, + AIOScript::ArgFunc a4, AIOScript::ArgFunc a5, AIOScript::ArgFunc a6); private: uint32 _scriptCount; diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index d19c9628100aa..e01d4a3d71229 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -49,6 +49,7 @@ #include "PacketUtilities.h" #include "Player.h" #include "Realm.h" +#include "AIO.h" #include "AIOCodec.h" #include "ScriptMgr.h" #include "SocialMgr.h" @@ -500,28 +501,64 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) return true; } -bool WorldSession::AllowNextAIOIncomingMessage(Player* sender) +WorldSession::AIOIncomingGateResult WorldSession::CheckAIOIncomingGate(Player* sender, size_t payloadBytes) { uint32 const minInterval = sWorld->getIntConfig(CONFIG_AIO_MSG_RATE_MS); if (!minInterval) - return true; + return AIOIncomingGateResult::Allow; uint32 const now = getMSTime(); if (_aioLastCompleteMessageMs && (now - _aioLastCompleteMessageMs) < minInterval) { - sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_DEBUG, - "AIO: Rate limited incoming message from {} ({} ms since last)", sender->GetName(), now - _aioLastCompleteMessageMs); - return false; + ++_aioRateLimitedCount; + if (_aioRateLimitedCount == 1 || (_aioRateLimitedCount % 25) == 0) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_WARN, + "AIO: Rate limited {} incoming message(s) from {} (latest {} bytes, {} ms since last accepted)", + _aioRateLimitedCount, sender->GetName(), payloadBytes, now - _aioLastCompleteMessageMs); + } + return AIOIncomingGateResult::RateLimited; } _aioLastCompleteMessageMs = now; - return true; + _aioRateLimitedCount = 0; + return AIOIncomingGateResult::Allow; +} + +void WorldSession::NotifyAIOIncomingParseFailure(Player* sender) +{ + ++_aioParseFailureCount; + uint32 const maxFailures = sWorld->getIntConfig(CONFIG_AIO_MAX_PARSE_FAILURES); + + if (_aioParseFailureCount == 1 || (_aioParseFailureCount % 5) == 0) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_WARN, + "AIO: Failed to parse incoming smallfolk payload from {} (failures: {})", + sender->GetName(), _aioParseFailureCount); + } + + if (maxFailures && _aioParseFailureCount >= maxFailures) + { + RecordAIOIncomingAbuse(sender, "parse failure threshold"); + _aioParseFailureCount = 0; + } +} + +void WorldSession::RecordAIOIncomingAbuse(Player* sender, char const* reason) +{ + if (!_addonMessageBuffer.empty()) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_WARN, + "AIO: Cleared {} reassembly buffer(s) for {} ({})", + uint32(_addonMessageBuffer.size()), sender->GetName(), reason); + _addonMessageBuffer.clear(); + } } WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhisper(Player* sender, Player* receiver, std::string const& msg) { size_t delimPos = 0; - if (!Trinity::AIO::Codec::IsClientPrefix(sWorld->GetAIOPrefix(), msg, delimPos)) + if (!Trinity::AIO::Codec::IsClientPrefix(sWorld->GetAIOClientWirePrefix(), msg, delimPos)) return IncomingAIOWhisperResult::NotAIO; if (receiver != sender) @@ -534,8 +571,20 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis if (messageId == 0) { - if (AllowNextAIOIncomingMessage(sender)) - sScriptMgr->OnAddonMessage(sender, msg.substr(delimPos + 3)); + std::string const& payload = msg.substr(delimPos + 3); + if (payload.size() > sWorld->getIntConfig(CONFIG_AIO_MAX_INCOMING)) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "AIO: Short message size {} exceeds limit {}. Sender: {}", + payload.size(), sWorld->getIntConfig(CONFIG_AIO_MAX_INCOMING), sender->GetName()); + return IncomingAIOWhisperResult::Consumed; + } + + if (CheckAIOIncomingGate(sender, payload.size()) == AIOIncomingGateResult::Allow) + sScriptMgr->OnAddonMessage(sender, payload); + else + RecordAIOIncomingAbuse(sender, "rate limit"); + return IncomingAIOWhisperResult::Consumed; } } @@ -561,6 +610,7 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "HandleIncomingAIOClientWhisper: Received AIO addon message with too many parts: {} (> {}). Message Id: {}, Sender: {}", parts, maxparts, messageId, sender->GetName()); + RecordAIOIncomingAbuse(sender, "too many parts"); return IncomingAIOWhisperResult::DropPacket; } @@ -575,10 +625,28 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis std::string const partPayload = msg.substr(delimPos + 7); uint32 const maxBufferSize = sWorld->getIntConfig(CONFIG_AIO_MAX_BUFFER_SIZE); + uint32 const maxPacketLen = std::min(sWorld->getIntConfig(CONFIG_AIO_MSG_MAX_LEN), AIO_MAX_WHISPER_LENGTH); + uint32 const longHeaderLen = uint32(sWorld->GetAIOClientWirePrefix().size()) + 1u + 6u; + uint32 const maxPartPayload = maxPacketLen > longHeaderLen ? maxPacketLen - longHeaderLen : 1u; + if (partPayload.size() > maxPartPayload) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "HandleIncomingAIOClientWhisper: AIO part payload {} bytes exceeds per-part limit {}. Message Id: {}, Sender: {}", + partPayload.size(), maxPartPayload, messageId, sender->GetName()); + RecordAIOIncomingAbuse(sender, "oversized part"); + return IncomingAIOWhisperResult::DropPacket; + } AddonMessageBufferMap::iterator messagePartsItr = _addonMessageBuffer.find(messageId); if (messagePartsItr == _addonMessageBuffer.end()) + { + if (_addonMessageBuffer.size() >= maxparts) + { + RecordAIOIncomingAbuse(sender, "too many concurrent reassemblies"); + return IncomingAIOWhisperResult::DropPacket; + } messagePartsItr = _addonMessageBuffer.insert(std::make_pair(messageId, LongMessageBufferInfo())).first; + } else if (parts != messagePartsItr->second.Parts) messagePartsItr->second = LongMessageBufferInfo(); @@ -586,7 +654,7 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis { sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "HandleIncomingAIOClientWhisper: Duplicate part {} for message id {}. Sender: {}", partId, messageId, sender->GetName()); - _addonMessageBuffer.erase(messagePartsItr); + RecordAIOIncomingAbuse(sender, "duplicate part"); return IncomingAIOWhisperResult::DropPacket; } @@ -598,7 +666,7 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "HandleIncomingAIOClientWhisper: AIO reassembly buffer exceeded {} bytes. Message Id: {}, Sender: {}", maxBufferSize, messageId, sender->GetName()); - _addonMessageBuffer.erase(messagePartsItr); + RecordAIOIncomingAbuse(sender, "reassembly buffer exceeded"); return IncomingAIOWhisperResult::DropPacket; } @@ -625,8 +693,20 @@ WorldSession::IncomingAIOWhisperResult WorldSession::HandleIncomingAIOClientWhis for (uint32 expectedPart = 1; expectedPart <= parts; ++expectedPart) actualAIOMessage += messagePartsItr->second.Map.find(expectedPart)->second; - if (AllowNextAIOIncomingMessage(sender)) + if (actualAIOMessage.size() > sWorld->getIntConfig(CONFIG_AIO_MAX_INCOMING)) + { + sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, + "AIO: Reassembled message size {} exceeds limit {}. Sender: {}", + actualAIOMessage.size(), sWorld->getIntConfig(CONFIG_AIO_MAX_INCOMING), sender->GetName()); + _addonMessageBuffer.erase(messagePartsItr); + return IncomingAIOWhisperResult::Consumed; + } + + if (CheckAIOIncomingGate(sender, actualAIOMessage.size()) == AIOIncomingGateResult::Allow) sScriptMgr->OnAddonMessage(sender, actualAIOMessage); + else + RecordAIOIncomingAbuse(sender, "rate limit"); + _addonMessageBuffer.erase(messagePartsItr); return IncomingAIOWhisperResult::Consumed; } diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index a08a0979a1823..81f64aa8b298f 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -1371,8 +1371,18 @@ class TC_GAME_API WorldSession AddonMessageBufferMap _addonMessageBuffer; uint32 _aioMsgCacheSweepTimer = 0; uint32 _aioLastCompleteMessageMs = 0; + uint32 _aioRateLimitedCount = 0; + uint32 _aioParseFailureCount = 0; - bool AllowNextAIOIncomingMessage(Player* sender); + enum class AIOIncomingGateResult : uint8 + { + Allow, + RateLimited + }; + + AIOIncomingGateResult CheckAIOIncomingGate(Player* sender, size_t payloadBytes); + void NotifyAIOIncomingParseFailure(Player* sender); + void RecordAIOIncomingAbuse(Player* sender, char const* reason); }; #endif /// @} diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 9c1be0ace2e28..aa19a235a758c 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -92,7 +92,9 @@ #include #include #include +#include "AIOUtil.h" #include "AIOMsg.h" +#include "WorldAIO.h" #include "smallfolk.h" TC_GAME_API std::atomic World::m_stopEvent(false); @@ -1578,10 +1580,13 @@ void World::LoadConfigSettings(bool reload) m_int_configs[CONFIG_AIO_MAX_BUFFER_SIZE] = sConfigMgr->GetIntDefault("AIO.MaxBufferSize", 1048576); m_int_configs[CONFIG_AIO_MAX_INCOMING] = sConfigMgr->GetIntDefault("AIO.MaxIncomingMessageSize", 262144); m_int_configs[CONFIG_AIO_MSG_RATE_MS] = sConfigMgr->GetIntDefault("AIO.MsgRateLimitMs", 50); + m_int_configs[CONFIG_AIO_MAX_BLOCKS] = sConfigMgr->GetIntDefault("AIO.MaxBlocks", 32); + m_int_configs[CONFIG_AIO_MAX_PARSE_FAILURES] = sConfigMgr->GetIntDefault("AIO.MaxParseFailures", 16); m_aioclientpath = sConfigMgr->GetStringDefault("AIO.ClientScriptPath", "lua_client_scripts"); m_aioprefix = sConfigMgr->GetStringDefault("AIO.Prefix", "AIO"); if (m_aioprefix.size() > 15u) m_aioprefix = m_aioprefix.substr(0, 15); + m_aioClientWirePrefix = "C" + m_aioprefix; if (m_int_configs[CONFIG_AIO_MAX_INCOMING] > m_int_configs[CONFIG_AIO_MAX_BUFFER_SIZE]) m_int_configs[CONFIG_AIO_MAX_INCOMING] = m_int_configs[CONFIG_AIO_MAX_BUFFER_SIZE]; @@ -3627,6 +3632,12 @@ bool World::AddAddon(AIOAddon const& addon) if (addon.file.empty()) return false; + if (!Trinity::AIO::IsSafeAddonRelativePath(addon.file)) + { + sLog->outAIOMessage(0, LOG_LEVEL_ERROR, "AIO AddAddon: rejected unsafe path '{}' for addon {}", addon.file, addon.name); + return false; + } + // Check if addon already exist for (AddonCodeListType::iterator itr = m_AddonList.begin(); itr != m_AddonList.end(); ++itr) { @@ -3701,8 +3712,12 @@ void World::ValidateAIOSettings() if (m_aioprefix.empty()) TC_LOG_ERROR("server.loading", "AIO.Prefix must not be empty."); - if (m_int_configs[CONFIG_AIO_MSG_MAX_LEN] != 255) - TC_LOG_WARN("server.loading", "AIO.MsgMaxLen is {} but 255 is required for 3.3.5 addon whispers.", m_int_configs[CONFIG_AIO_MSG_MAX_LEN]); + if (m_int_configs[CONFIG_AIO_MSG_MAX_LEN] != AIO_MAX_WHISPER_LENGTH) + { + TC_LOG_WARN("server.loading", "AIO.MsgMaxLen is {} but {} is required for 3.3.5 addon whispers; clamping.", + m_int_configs[CONFIG_AIO_MSG_MAX_LEN], AIO_MAX_WHISPER_LENGTH); + m_int_configs[CONFIG_AIO_MSG_MAX_LEN] = AIO_MAX_WHISPER_LENGTH; + } boost::filesystem::path clientPath(m_aioclientpath); if (!boost::filesystem::exists(clientPath)) @@ -3710,8 +3725,9 @@ void World::ValidateAIOSettings() else if (!boost::filesystem::is_directory(clientPath)) TC_LOG_ERROR("server.loading", "AIO.ClientScriptPath '{}' is not a directory.", m_aioclientpath); - TC_LOG_INFO("server.loading", "AIO: prefix '{}', client scripts '{}', max incoming {} bytes, rate limit {} ms.", - m_aioprefix, m_aioclientpath, m_int_configs[CONFIG_AIO_MAX_INCOMING], m_int_configs[CONFIG_AIO_MSG_RATE_MS]); + TC_LOG_INFO("server.loading", "AIO: wire prefix '{}', client scripts '{}', max incoming {} bytes, max blocks {}, rate limit {} ms, parse failure limit {}.", + m_aioClientWirePrefix, m_aioclientpath, m_int_configs[CONFIG_AIO_MAX_INCOMING], m_int_configs[CONFIG_AIO_MAX_BLOCKS], + m_int_configs[CONFIG_AIO_MSG_RATE_MS], m_int_configs[CONFIG_AIO_MAX_PARSE_FAILURES]); } bool World::ReloadAddons() @@ -3790,21 +3806,12 @@ void World::ForceResetPlayerAddons(uint32 permission) void World::AIOMessageAll(AIOMsg& msg, uint32 permission) { - std::string messageStr = msg.dumps(); - for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) - { - if (itr->second->GetPlayer() && itr->second->HasPermission(permission)) - itr->second->GetPlayer()->SendSimpleAIOMessage(messageStr); - } + Trinity::AIO::MessageAll(this, msg, permission); } -void World::SendAllSimpleAIOMessage(const std::string &message, uint32 permission) +void World::SendAllSimpleAIOMessage(std::string const& message, uint32 permission) { - for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr) - { - if (itr->second->GetPlayer() && itr->second->HasPermission(permission)) - itr->second->GetPlayer()->SendSimpleAIOMessage(message); - } + Trinity::AIO::SendAllSimple(this, message, permission); } void World::RemoveOldCorpses() diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index ad8d7f13bb777..2401101eea6c8 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -402,6 +402,8 @@ enum WorldIntConfigs : uint32 CONFIG_AIO_MAX_BUFFER_SIZE, CONFIG_AIO_MAX_INCOMING, CONFIG_AIO_MSG_RATE_MS, + CONFIG_AIO_MAX_BLOCKS, + CONFIG_AIO_MAX_PARSE_FAILURES, CONFIG_AUCTION_GETALL_DELAY, CONFIG_AUCTION_SEARCH_DELAY, CONFIG_TALENTS_INSPECTING, @@ -797,6 +799,7 @@ class TC_GAME_API World }; std::string GetAIOPrefix() const { return m_aioprefix; } + std::string const& GetAIOClientWirePrefix() const { return m_aioClientWirePrefix; } std::string GetAIOClientScriptPath() const { return m_aioclientpath; } void ForceReloadPlayerAddons(uint32 permission = AIO_DEFAULT_ADDON_PERMISSION); @@ -934,6 +937,7 @@ class TC_GAME_API World typedef std::list AddonCodeListType; AddonCodeListType m_AddonList; std::string m_aioprefix; + std::string m_aioClientWirePrefix; std::string m_aioclientpath; friend class AIOScript; diff --git a/src/server/scripts/AIO/ExampleWindowStub.cpp b/src/server/scripts/AIO/ExampleWindowStub.cpp new file mode 100644 index 0000000000000..95e3dc4514e29 --- /dev/null +++ b/src/server/scripts/AIO/ExampleWindowStub.cpp @@ -0,0 +1,24 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#ifndef WITH_CAIO_EXAMPLES + +void AddSC_ExampleWindow() +{ +} + +#endif diff --git a/src/server/scripts/AIO/aio_script_loader.cpp b/src/server/scripts/AIO/aio_script_loader.cpp index b2ae745ac43f4..ab6e3114f2d9c 100644 --- a/src/server/scripts/AIO/aio_script_loader.cpp +++ b/src/server/scripts/AIO/aio_script_loader.cpp @@ -15,15 +15,10 @@ * with this program. If not, see . */ -#ifdef WITH_CAIO_EXAMPLES +// ExampleWindow.cpp is excluded from the AIO module when WITH_CAIO_EXAMPLES is off. void AddSC_ExampleWindow(); -#endif -// The name of this function should match: -// void Add${NameOfDirectory}Scripts() void AddAIOScripts() { -#ifdef WITH_CAIO_EXAMPLES AddSC_ExampleWindow(); -#endif } diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt index e029640a700ac..5b12eefad6ce6 100644 --- a/src/server/scripts/CMakeLists.txt +++ b/src/server/scripts/CMakeLists.txt @@ -225,6 +225,10 @@ target_include_directories(scripts PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +if(WITH_CAIO_EXAMPLES) + target_compile_definitions(scripts PRIVATE WITH_CAIO_EXAMPLES) +endif() + set_target_properties(scripts PROPERTIES COMPILE_WARNING_AS_ERROR ${WITH_WARNINGS_AS_ERRORS} diff --git a/src/server/scripts/Commands/cs_caio.cpp b/src/server/scripts/Commands/cs_caio.cpp index e459e801416c2..d727c3d72b5b8 100644 --- a/src/server/scripts/Commands/cs_caio.cpp +++ b/src/server/scripts/Commands/cs_caio.cpp @@ -7,6 +7,7 @@ EndScriptData */ #include "ScriptMgr.h" #include "AIO.h" +#include "AIOUtil.h" #include "Chat.h" #include "ChatCommand.h" #include "Language.h" @@ -17,20 +18,6 @@ EndScriptData */ using namespace Trinity::ChatCommands; -namespace -{ -bool IsSafeAddonRelativePath(std::string const& path) -{ - if (path.empty() || path.front() == '/' || path.front() == '\\') - return false; - - if (path.find("..") != std::string::npos) - return false; - - return path.find_first_of(":*?\"<>|") == std::string::npos; -} -} - class caio_commandscript : public CommandScript { public: @@ -68,7 +55,8 @@ class caio_commandscript : public CommandScript static bool HandleSendCommand(ChatHandler* handler, PlayerIdentifier const& target, QuotedString message) { - if (!LuaVal::loads(message).istable()) + Trinity::AIO::LoadMessageOutcome const outcome = Trinity::AIO::TryLoadIncomingMessage(message, sWorld->getIntConfig(CONFIG_AIO_MAX_INCOMING), sWorld->getIntConfig(CONFIG_AIO_MAX_BLOCKS)); + if (outcome.result != Trinity::AIO::LoadMessageResult::Ok) { handler->SendSysMessage("CAIO: message must be smallfolk-serialized table data (use AIOMsg or AIO.Msg on the server)."); return false; @@ -107,7 +95,8 @@ class caio_commandscript : public CommandScript static bool HandleSendAllCommand(ChatHandler* handler, QuotedString message, Optional permission) { - if (!LuaVal::loads(message).istable()) + Trinity::AIO::LoadMessageOutcome const outcome = Trinity::AIO::TryLoadIncomingMessage(message, sWorld->getIntConfig(CONFIG_AIO_MAX_INCOMING), sWorld->getIntConfig(CONFIG_AIO_MAX_BLOCKS)); + if (outcome.result != Trinity::AIO::LoadMessageResult::Ok) { handler->SendSysMessage("CAIO: message must be smallfolk-serialized table data (use AIOMsg or AIO.Msg on the server)."); return false; @@ -145,7 +134,7 @@ class caio_commandscript : public CommandScript static bool HandleAddAddonCommand(ChatHandler* handler, std::string addonName, QuotedString addonFile, Optional permission) { - if (!IsSafeAddonRelativePath(addonFile)) + if (!Trinity::AIO::IsSafeAddonRelativePath(addonFile)) { handler->SendSysMessage("CAIO: addon file path must be relative to AIO.ClientScriptPath and must not contain '..'."); return false; diff --git a/src/server/worldserver/lua_client_scripts/ExampleWindow/ExampleWindow.lua b/src/server/worldserver/lua_client_scripts/ExampleWindow/ExampleWindow.lua new file mode 100644 index 0000000000000..78d281a07530f --- /dev/null +++ b/src/server/worldserver/lua_client_scripts/ExampleWindow/ExampleWindow.lua @@ -0,0 +1,26 @@ +-- Optional CAIO example client script (pairs with ExampleWindow.cpp when built with WITH_CAIO_EXAMPLES). +-- Requires AIO_Client in Interface/AddOns with matching AIO 1.75. + +local AIO = AIO or require("AIO") + +if not AIO.AddAddon then + return +end + +AIO.AddAddon("ExampleWindow") + +local function OnInit(initData) + -- Server may pass init args via AddInitArgs on script "AIOExample" / handler "Init". +end + +AIO.RegisterEvent("AIOExample", "Init", OnInit) + +AIO.RegisterEvent("AIOExample", "StressTest", function(data) + if type(data) == "string" then + print("ExampleWindow: StressTest received", #data, "bytes") + end +end) + +AIO.RegisterEvent("AIOExample", "Print", function(button, input, slider) + AIO.Msg():Add("AIOExample", "Print", button, input, slider):Send() +end) diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index cd46ff119008c..2206398f5b3db 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -4141,6 +4141,14 @@ PacketSpoof.BanDuration = 86400 # Minimum milliseconds between completed incoming AIO messages per session (0 = disabled). # Default: 50 # +# AIO.MaxBlocks +# Maximum number of handler blocks per incoming AIO message. +# Default: 32 +# +# AIO.MaxParseFailures +# Parse failures per session before reassembly buffers are cleared (0 = no threshold). +# Default: 16 +# # AIO.ClientScriptPath # Path to client lua scripts to send to the client # Default: "lua_client_scripts" @@ -4155,6 +4163,8 @@ AIO.MsgCacheDelay = 5000 AIO.MaxBufferSize = 1048576 AIO.MaxIncomingMessageSize = 262144 AIO.MsgRateLimitMs = 50 +AIO.MaxBlocks = 32 +AIO.MaxParseFailures = 16 AIO.ClientScriptPath = "lua_client_scripts" # diff --git a/tests/game/AIOCodec.cpp b/tests/game/AIOCodec.cpp index 1132add888830..7584233e2c672 100644 --- a/tests/game/AIOCodec.cpp +++ b/tests/game/AIOCodec.cpp @@ -31,17 +31,17 @@ TEST_CASE("AIO codec byte decoding", "[AIO]") TEST_CASE("AIO client prefix detection", "[AIO]") { - SECTION("matches configured prefix") + SECTION("matches configured wire prefix") { size_t delimPos = 0; - REQUIRE(IsClientPrefix("AIO", "CAIO\tpayload", delimPos)); - REQUIRE(delimPos == 3); + REQUIRE(IsClientPrefix("CAIO", "CAIO\tpayload", delimPos)); + REQUIRE(delimPos == 4); } SECTION("rejects server prefix") { size_t delimPos = 0; - REQUIRE_FALSE(IsClientPrefix("AIO", "SAIO\tpayload", delimPos)); + REQUIRE_FALSE(IsClientPrefix("CAIO", "SAIO\tpayload", delimPos)); } SECTION("rejects missing tab") diff --git a/tests/game/AIOUtil.cpp b/tests/game/AIOUtil.cpp new file mode 100644 index 0000000000000..921c308e71676 --- /dev/null +++ b/tests/game/AIOUtil.cpp @@ -0,0 +1,70 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * 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 . + */ + +#include "tc_catch2.h" + +#include "AIOUtil.h" +#include "AIOMsg.h" + +TEST_CASE("AIO addon path validation", "[AIO]") +{ + REQUIRE(Trinity::AIO::IsSafeAddonRelativePath("ExampleWindow/ExampleWindow.lua")); + REQUIRE_FALSE(Trinity::AIO::IsSafeAddonRelativePath("../secret.lua")); + REQUIRE_FALSE(Trinity::AIO::IsSafeAddonRelativePath("/etc/passwd")); + REQUIRE_FALSE(Trinity::AIO::IsSafeAddonRelativePath("")); +} + +TEST_CASE("AIO incoming message load", "[AIO]") +{ + AIOMsg msg("TestScript", "Handler", LuaVal("arg")); + std::string const serialized = msg.dumps(); + + SECTION("accepts valid table") + { + Trinity::AIO::LoadMessageOutcome const outcome = Trinity::AIO::TryLoadIncomingMessage(serialized, 4096, 8); + REQUIRE(outcome.result == Trinity::AIO::LoadMessageResult::Ok); + REQUIRE(outcome.table.istable()); + REQUIRE(outcome.table.len() == 1); + } + + SECTION("rejects oversize payload") + { + Trinity::AIO::LoadMessageOutcome const outcome = Trinity::AIO::TryLoadIncomingMessage(serialized, 4, 8); + REQUIRE(outcome.result == Trinity::AIO::LoadMessageResult::Oversize); + } + + SECTION("rejects invalid smallfolk") + { + Trinity::AIO::LoadMessageOutcome const outcome = Trinity::AIO::TryLoadIncomingMessage("not smallfolk", 4096, 8); + REQUIRE(outcome.result == Trinity::AIO::LoadMessageResult::ParseError); + } +} + +TEST_CASE("AIOMsg round trip", "[AIO]") +{ + AIOMsg msg; + msg.Add("ScriptA", "HandlerA", LuaVal(1)); + msg.Add("ScriptB", "HandlerB", LuaVal("x")); + + Trinity::AIO::LoadMessageOutcome const outcome = Trinity::AIO::TryLoadIncomingMessage(msg.dumps(), 4096, 8); + REQUIRE(outcome.result == Trinity::AIO::LoadMessageResult::Ok); + REQUIRE(outcome.table.len() == 2); + + LuaVal block = outcome.table.get(1); + REQUIRE(block.get(2).tostring() == "ScriptA"); + REQUIRE(block.get(3).tostring() == "HandlerA"); +} From 4e14c2e0bb170e8dd01739a358e1e92919442821 Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Sat, 6 Jun 2026 17:54:01 +0300 Subject: [PATCH 28/33] Fix GetProjectNameOfScriptModule to use its module argument. --- cmake/macros/ConfigureScripts.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/macros/ConfigureScripts.cmake b/cmake/macros/ConfigureScripts.cmake index 7ad99f95ba72a..de6304b8d8ac4 100644 --- a/cmake/macros/ConfigureScripts.cmake +++ b/cmake/macros/ConfigureScripts.cmake @@ -36,7 +36,7 @@ endfunction() # Stores the project name of the given module in the variable function(GetProjectNameOfScriptModule module variable) - string(TOLOWER "scripts_${SCRIPT_MODULE}" GENERATED_NAME) + string(TOLOWER "scripts_${module}" GENERATED_NAME) set(${variable} "${GENERATED_NAME}" PARENT_SCOPE) endfunction() From 614dc24df803248d77701c1344768c1de67d8d1c Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Sat, 6 Jun 2026 18:02:02 +0300 Subject: [PATCH 29/33] Fix AIOScript registration and unload for split translation unit. --- src/server/game/Scripting/AIOScript.cpp | 7 ++++++- src/server/game/Scripting/AIOScript.h | 1 + src/server/game/Scripting/ScriptMgr.cpp | 7 ++++++- src/server/game/Scripting/ScriptMgr.h | 1 + 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/server/game/Scripting/AIOScript.cpp b/src/server/game/Scripting/AIOScript.cpp index 11b3eba39c93d..690c9f83bf932 100644 --- a/src/server/game/Scripting/AIOScript.cpp +++ b/src/server/game/Scripting/AIOScript.cpp @@ -114,10 +114,15 @@ AIOScript::AIOScript(LuaVal const& scriptKey) sLog->outAIOMessage(0, LOG_LEVEL_FATAL, "AIO scriptKey '{}' of type tag '{}' already exist. Use another key.", scriptKey.tostring(), static_cast(scriptKey.typetag())); ASSERT(false); } - ScriptRegistry::Instance()->AddScript(this); + sScriptMgr->RegisterAIOScript(this); AIOScript::_scriptByKeyMap[scriptKey] = this; } +void AIOScript::ClearScriptByKeyMap() +{ + _scriptByKeyMap.clear(); +} + AIOScript* AIOScript::FindByKey(LuaVal const& scriptKey) { AIOScriptByKeyMap::const_iterator itr = _scriptByKeyMap.find(scriptKey); diff --git a/src/server/game/Scripting/AIOScript.h b/src/server/game/Scripting/AIOScript.h index 154e21d9ea3eb..6b21c7e2433aa 100644 --- a/src/server/game/Scripting/AIOScript.h +++ b/src/server/game/Scripting/AIOScript.h @@ -38,6 +38,7 @@ class TC_GAME_API AIOScript : public ScriptObject bool IsDatabaseBound() const { return false; } static AIOScript* FindByKey(LuaVal const& scriptKey); + static void ClearScriptByKeyMap(); void HandleAddonBlock(Player* sender, LuaVal const& handlerKey, LuaVal const& args); static void DispatchIncomingBlocks(Player* sender, LuaVal const& mainTable); diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 720b4247938e0..6a93f2f526a7d 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -1134,7 +1134,7 @@ std::shared_ptr void ScriptMgr::Unload() { sScriptRegistryCompositum->Unload(); - AIOScript::_scriptByKeyMap.clear(); + AIOScript::ClearScriptByKeyMap(); DestroyAIOHandlers(_aioHandlers); delete[] SpellSummary; @@ -2583,6 +2583,11 @@ PlayerScript::PlayerScript(char const* name) ScriptRegistry::Instance()->AddScript(this); } +void ScriptMgr::RegisterAIOScript(AIOScript* script) +{ + ScriptRegistry::Instance()->AddScript(script); +} + void ScriptMgr::RegisterAIOInitHook(AIOScript::InitMessageFunc func) { RegisterAIOInitHookOnHandlers(_aioHandlers, std::move(func)); diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 6cd35fe204659..1bd9268a93cd5 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -1089,6 +1089,7 @@ class TC_GAME_API ScriptMgr public: /* AIOScript */ void OnAddonMessage(Player* sender, std::string const& message); + void RegisterAIOScript(AIOScript* script); void RegisterAIOInitHook(AIOScript::InitMessageFunc func); void RegisterAIOInitArgs(LuaVal const& scriptKey, LuaVal const& handlerKey, AIOScript::ArgFunc a1, AIOScript::ArgFunc a2, AIOScript::ArgFunc a3, From 997815c2b777c9171b91037e84873494e17fb10d Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Sat, 6 Jun 2026 18:13:20 +0300 Subject: [PATCH 30/33] Fix AIO compile errors: qualify MAX_BLOCK_ARGS and grant ScriptMgr friend access. --- src/server/game/Scripting/AIOScript.cpp | 4 ++-- src/server/game/Server/WorldSession.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/server/game/Scripting/AIOScript.cpp b/src/server/game/Scripting/AIOScript.cpp index 690c9f83bf932..1f6bfaeb00519 100644 --- a/src/server/game/Scripting/AIOScript.cpp +++ b/src/server/game/Scripting/AIOScript.cpp @@ -151,11 +151,11 @@ void AIOScript::DispatchIncomingBlocks(Player* sender, LuaVal const& mainTable) if (!nArgsVal.isnumber() || scriptKeyVal.isnil() || handlerKeyVal.isnil()) continue; - if (nArgsVal.num() > double(MAX_BLOCK_ARGS)) + if (nArgsVal.num() > double(Trinity::AIO::MAX_BLOCK_ARGS)) { sLog->outAIOMessage(sender->GetGUID().GetCounter(), LOG_LEVEL_ERROR, "AIO: Block from '{}' has over {} arguments (n={:.0f}). Sender: {}", - scriptKeyVal.tostring(), MAX_BLOCK_ARGS, nArgsVal.num(), sender->GetName()); + scriptKeyVal.tostring(), Trinity::AIO::MAX_BLOCK_ARGS, nArgsVal.num(), sender->GetName()); continue; } diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 81f64aa8b298f..fe6d78efdb1e6 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -1237,6 +1237,7 @@ class TC_GAME_API WorldSession AsyncCallbackProcessor _queryHolderProcessor; friend class World; + friend class ScriptMgr; protected: class DosProtection { From eaf72518fa74330515815cacf6283f53fc56248a Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Sat, 6 Jun 2026 18:19:38 +0300 Subject: [PATCH 31/33] Allow CreateAIOHandlers to construct private AIOHandlers. --- src/server/game/Scripting/AIOScript.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/game/Scripting/AIOScript.cpp b/src/server/game/Scripting/AIOScript.cpp index 1f6bfaeb00519..50ff0fa90a734 100644 --- a/src/server/game/Scripting/AIOScript.cpp +++ b/src/server/game/Scripting/AIOScript.cpp @@ -81,6 +81,7 @@ class AIOHandlers : public AIOScript } private: + friend AIOHandlers* CreateAIOHandlers(); AIOHandlers(); void HandleInit(Player* sender, LuaVal const& args); void HandleError(Player* sender, LuaVal const& args); From 7394d37862a8e95aad4682cddc51a48b361ce6ce Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Sat, 6 Jun 2026 18:27:28 +0300 Subject: [PATCH 32/33] Propagate WITH_CAIO_EXAMPLES to all dynamic script PCH consumers. --- src/server/scripts/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt index 5b12eefad6ce6..d620594c99165 100644 --- a/src/server/scripts/CMakeLists.txt +++ b/src/server/scripts/CMakeLists.txt @@ -238,8 +238,10 @@ set_target_properties(scripts if(USE_SCRIPTPCH) set(SCRIPT_PCH_REUSE_PROJECTS ${DYNAMIC_SCRIPT_MODULE_PROJECTS}) if(WITH_CAIO_EXAMPLES) + foreach(script_project ${DYNAMIC_SCRIPT_MODULE_PROJECTS}) + target_compile_definitions(${script_project} PRIVATE WITH_CAIO_EXAMPLES) + endforeach() GetProjectNameOfScriptModule("AIO" SCRIPT_AIO_PROJECT_NAME) - target_compile_definitions(${SCRIPT_AIO_PROJECT_NAME} PRIVATE WITH_CAIO_EXAMPLES) list(REMOVE_ITEM SCRIPT_PCH_REUSE_PROJECTS ${SCRIPT_AIO_PROJECT_NAME}) endif() add_cxx_pch("scripts" ${PRIVATE_PCH_HEADER} ${DYNAMIC_SCRIPT_MODULE_PROJECTS}) From 669524249c8f3cb8ba8e0a03704071a802b86d7e Mon Sep 17 00:00:00 2001 From: Rochet2 Date: Sat, 6 Jun 2026 18:42:47 +0300 Subject: [PATCH 33/33] Treat nil LuaVal from loads as AIO parse error. --- src/server/game/AIO/AIOUtil.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/server/game/AIO/AIOUtil.cpp b/src/server/game/AIO/AIOUtil.cpp index 49053103798f0..c8b62f106b1c3 100644 --- a/src/server/game/AIO/AIOUtil.cpp +++ b/src/server/game/AIO/AIOUtil.cpp @@ -57,6 +57,12 @@ LoadMessageOutcome TryLoadIncomingMessage(std::string const& message, uint32 max return outcome; } + if (outcome.table.isnil()) + { + outcome.result = LoadMessageResult::ParseError; + return outcome; + } + if (!outcome.table.istable()) { outcome.result = LoadMessageResult::NotTable;