From e181fa5fd4d14e1c7747f051289deed8ef891caa Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Sat, 27 Jul 2024 09:47:03 +0000 Subject: [PATCH] add codebase --- .editorconfig | 84 +++ BetterAmongUs.csproj | 37 + BetterAmongUs.sln | 25 + LICENSE.txt | 674 ++++++++++++++++++ Modules/AntiCheat.cs | 369 ++++++++++ Modules/BAUDataManager.cs | 188 +++++ Modules/BAUNotificationManager.cs | 102 +++ Modules/BetterHostManager.cs | 99 +++ Modules/ClientOptionItem.cs | 154 ++++ Modules/DebugMenu.cs | 235 ++++++ Modules/Debugger.cs | 35 + Modules/Encryptor.cs | 39 + Modules/EventLogManager.cs | 41 ++ Modules/ExtendedPlayerControl.cs | 145 ++++ Modules/ExtendedPlayerInfo.cs | 21 + Modules/FileChecker.cs | 188 +++++ Modules/GameState.cs | 51 ++ Modules/LateTask.cs | 55 ++ Modules/RPC.cs | 161 +++++ Modules/Utils.cs | 229 ++++++ Modules/Zoom.cs | 96 +++ Patches/ButtonsPatch.cs | 48 ++ Patches/ChatNotificationPatch.cs | 40 ++ Patches/ChatPatch.cs | 229 ++++++ Patches/ClientOptionsPatch.cs | 106 +++ Patches/ClientPatch.cs | 28 + Patches/CommandsPatch.cs | 623 ++++++++++++++++ Patches/DisconnectPenaltyPatch.cs | 17 + Patches/DiscordPatch.cs | 67 ++ Patches/EOSManagerPatch.cs | 15 + Patches/GamePlayManagerPatch.cs | 34 + Patches/HudManagerPatch.cs | 80 +++ Patches/LobbyPatch.cs | 19 + Patches/MainMenuPatch.cs | 46 ++ Patches/MeetingHudPatch.cs | 166 +++++ Patches/MiniMapBehaviourPatch.cs | 107 +++ Patches/ModManagerPatch.cs | 22 + Patches/PingTrackerPatch.cs | 80 +++ Patches/PlayerControlPatch.cs | 227 ++++++ Patches/PlayerJoinAndLeftPatch.cs | 110 +++ Patches/RoleManagerPatch.cs | 219 ++++++ Patches/VersionShowerPatch.cs | 14 + .../ReactorCoroutines/Coroutines.cs | 56 ++ .../DebugWindowRes/ReactorImGui/DragWindow.cs | 20 + .../ReactorImGui/GUIExtensions.cs | 29 + .../DebugWindowRes/ReactorImGui/Window.cs | 44 ++ Resources/Images/BetterAmongUs-Icon.png | Bin 0 -> 35047 bytes Resources/Images/BetterAmongUs-Logo.png | Bin 0 -> 106343 bytes Resources/Images/BetterAmongUs-Mod.png | Bin 0 -> 37268 bytes main.cs | 134 ++++ 50 files changed, 5608 insertions(+) create mode 100644 .editorconfig create mode 100644 BetterAmongUs.csproj create mode 100644 BetterAmongUs.sln create mode 100644 LICENSE.txt create mode 100644 Modules/AntiCheat.cs create mode 100644 Modules/BAUDataManager.cs create mode 100644 Modules/BAUNotificationManager.cs create mode 100644 Modules/BetterHostManager.cs create mode 100644 Modules/ClientOptionItem.cs create mode 100644 Modules/DebugMenu.cs create mode 100644 Modules/Debugger.cs create mode 100644 Modules/Encryptor.cs create mode 100644 Modules/EventLogManager.cs create mode 100644 Modules/ExtendedPlayerControl.cs create mode 100644 Modules/ExtendedPlayerInfo.cs create mode 100644 Modules/FileChecker.cs create mode 100644 Modules/GameState.cs create mode 100644 Modules/LateTask.cs create mode 100644 Modules/RPC.cs create mode 100644 Modules/Utils.cs create mode 100644 Modules/Zoom.cs create mode 100644 Patches/ButtonsPatch.cs create mode 100644 Patches/ChatNotificationPatch.cs create mode 100644 Patches/ChatPatch.cs create mode 100644 Patches/ClientOptionsPatch.cs create mode 100644 Patches/ClientPatch.cs create mode 100644 Patches/CommandsPatch.cs create mode 100644 Patches/DisconnectPenaltyPatch.cs create mode 100644 Patches/DiscordPatch.cs create mode 100644 Patches/EOSManagerPatch.cs create mode 100644 Patches/GamePlayManagerPatch.cs create mode 100644 Patches/HudManagerPatch.cs create mode 100644 Patches/LobbyPatch.cs create mode 100644 Patches/MainMenuPatch.cs create mode 100644 Patches/MeetingHudPatch.cs create mode 100644 Patches/MiniMapBehaviourPatch.cs create mode 100644 Patches/ModManagerPatch.cs create mode 100644 Patches/PingTrackerPatch.cs create mode 100644 Patches/PlayerControlPatch.cs create mode 100644 Patches/PlayerJoinAndLeftPatch.cs create mode 100644 Patches/RoleManagerPatch.cs create mode 100644 Patches/VersionShowerPatch.cs create mode 100644 Resources/DebugWindowRes/ReactorCoroutines/Coroutines.cs create mode 100644 Resources/DebugWindowRes/ReactorImGui/DragWindow.cs create mode 100644 Resources/DebugWindowRes/ReactorImGui/GUIExtensions.cs create mode 100644 Resources/DebugWindowRes/ReactorImGui/Window.cs create mode 100644 Resources/Images/BetterAmongUs-Icon.png create mode 100644 Resources/Images/BetterAmongUs-Logo.png create mode 100644 Resources/Images/BetterAmongUs-Mod.png create mode 100644 main.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d38bb89 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,84 @@ +[*.cs] + +# IDE0022: Use block body for method +csharp_style_expression_bodied_methods = false:silent +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent + +# CS8604: Possible null reference argument. +dotnet_diagnostic.CS8604.severity = silent + +# CS8602: Dereference of a possibly null reference. +dotnet_diagnostic.CS8602.severity = none + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_prefer_collection_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion diff --git a/BetterAmongUs.csproj b/BetterAmongUs.csproj new file mode 100644 index 0000000..7fe3fb7 --- /dev/null +++ b/BetterAmongUs.csproj @@ -0,0 +1,37 @@ + + + + net6.0 + enable + enable + Library + preview + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + diff --git a/BetterAmongUs.sln b/BetterAmongUs.sln new file mode 100644 index 0000000..910995f --- /dev/null +++ b/BetterAmongUs.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34511.84 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BetterAmongUs", "BetterAmongUs.csproj", "{41193AE4-8181-49F8-9DA4-F4B7B275A0EA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {41193AE4-8181-49F8-9DA4-F4B7B275A0EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41193AE4-8181-49F8-9DA4-F4B7B275A0EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41193AE4-8181-49F8-9DA4-F4B7B275A0EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41193AE4-8181-49F8-9DA4-F4B7B275A0EA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D51C3994-D13F-43E5-9B5E-E029EC534208} + EndGlobalSection +EndGlobal diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/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/Modules/AntiCheat.cs b/Modules/AntiCheat.cs new file mode 100644 index 0000000..f443e10 --- /dev/null +++ b/Modules/AntiCheat.cs @@ -0,0 +1,369 @@ +using AmongUs.GameOptions; +using Hazel; +using InnerNet; + +namespace BetterAmongUs; + +class AntiCheat +{ + public static Dictionary PlayerData = []; // HashPuid, FriendCode + public static Dictionary SickoData = []; // HashPuid, FriendCode + public static Dictionary AUMData = []; // HashPuid, FriendCode + private static bool IsEnabled = true; + + public static void PauseAntiCheat() + { + float time = 2f; + if (IsEnabled) + { + IsEnabled = false; + + _ = new LateTask(() => + { + IsEnabled = true; + }, time, "PauseAntiCheat"); + } + } + + // Handle RPC before anti cheat detection + public static void HandleRPCBeforeCheck(PlayerControl player, byte callId, MessageReader reader) + { + if (player == PlayerControl.LocalPlayer || PlayerControl.LocalPlayer == null || player == null || !IsEnabled) return; + + if (callId is unchecked((byte)CustomRPC.Sicko) && Main.AntiCheat.Value) + { + if (reader.BytesRemaining == 0) + { + var flag = SickoData.ContainsKey(Utils.GetHashPuid(player)); + + if (reader.BytesRemaining == 0 && !flag) + { + AmongUsClient.Instance.ReportPlayer(player.GetClientId(), ReportReasons.Cheating_Hacking); + BAUNotificationManager.NotifyCheat(player, $"Sicko Menu", newText: "Has been detected with a cheat client"); + PlayerData[Utils.GetHashPuid(player)] = player.FriendCode; + SickoData[Utils.GetHashPuid(player)] = player.FriendCode; + BAUDataManager.SaveCheatData(Utils.GetHashPuid(player), player.Data.FriendCode, player.Data.PlayerName, "cheatData", "Sicko Menu RPC"); + BAUDataManager.SaveCheatData(Utils.GetHashPuid(player), player.Data.FriendCode, player.Data.PlayerName, "sickoData", "Sicko Menu RPC"); + } + } + + return; + } + + if (callId is unchecked((byte)CustomRPC.AUM) && Main.AntiCheat.Value) + { + try + { + var aumid = reader.ReadByte(); + + if (aumid == player.PlayerId) + { + var flag = AUMData.ContainsKey(Utils.GetHashPuid(player)); + + if (!flag) + { + AmongUsClient.Instance.ReportPlayer(player.GetClientId(), ReportReasons.Cheating_Hacking); + BAUNotificationManager.NotifyCheat(player, $"AUM", newText: "Has been detected with a cheat client"); + PlayerData[Utils.GetHashPuid(player)] = player.FriendCode; + AUMData[Utils.GetHashPuid(player)] = player.FriendCode; + BAUDataManager.SaveCheatData(Utils.GetHashPuid(player), player.Data.FriendCode, player.Data.PlayerName, "cheatData", "AUM RPC"); + BAUDataManager.SaveCheatData(Utils.GetHashPuid(player), player.Data.FriendCode, player.Data.PlayerName, "aumData", "AUM RPC"); + } + } + } + catch { } + + return; + } + + if (callId is unchecked((byte)CustomRPC.AUMChat)) + { + try + { + var nameString = reader.ReadString(); + var msgString = reader.ReadString(); + var colorId = reader.ReadInt32(); + + Utils.AddChatPrivate($"{msgString}", overrideName: $"AUM Chat - {nameString}"); + + PlayerControl AUMPlayer = Main.AllPlayerControls.First(pc => pc.Data.PlayerName == nameString && pc.CurrentOutfit.ColorId == colorId); + + if (AUMPlayer == null || !Main.AntiCheat.Value) return; + + var flag = string.IsNullOrEmpty(nameString) && string.IsNullOrEmpty(msgString); + var flag2 = AUMData.ContainsKey(Utils.GetHashPuid(AUMPlayer)); + + if (!flag && !flag2) + { + AmongUsClient.Instance.ReportPlayer(player.GetClientId(), ReportReasons.Cheating_Hacking); + BAUNotificationManager.NotifyCheat(AUMPlayer, $"AUM", newText: "Has been detected with a cheat client"); + PlayerData[Utils.GetHashPuid(AUMPlayer)] = AUMPlayer.FriendCode; + AUMData[Utils.GetHashPuid(AUMPlayer)] = AUMPlayer.FriendCode; + BAUDataManager.SaveCheatData(Utils.GetHashPuid(player), player.Data.FriendCode, player.Data.PlayerName, "cheatData", "AUM Chat RPC"); + BAUDataManager.SaveCheatData(Utils.GetHashPuid(player), player.Data.FriendCode, player.Data.PlayerName, "aumData", "AUM Chat RPC"); + } + } + catch { } + + return; + } + } + + // Check and notify for invalid rpcs + public static void CheckRPC(PlayerControl player, byte callId, MessageReader reader) + { + try + { + if (PlayerControl.LocalPlayer == null || player == null || player == PlayerControl.LocalPlayer || reader == null || !IsEnabled || !Main.AntiCheat.Value) return; + + RoleTypes? Role = player?.Data?.RoleType; + Role ??= RoleTypes.Crewmate; + bool IsImpostor = player.IsImpostorTeam(); + bool IsCrewmate = !player.IsImpostorTeam(); + string hashPuid = Utils.GetHashPuid(player); + + if (callId is (byte)RpcCalls.EnterVent or (byte)RpcCalls.ExitVent) + { + if ((IsCrewmate && Role != RoleTypes.Engineer) || player.Data.IsDead) + BAUNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); + + return; + } + + if (callId is (byte)RpcCalls.CheckProtect or (byte)RpcCalls.ProtectPlayer) + { + if (Role is not RoleTypes.GuardianAngel) + BAUNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); + + return; + } + + if (callId is (byte)RpcCalls.CheckMurder or (byte)RpcCalls.MurderPlayer) + { + PlayerControl target = reader.ReadNetObject(); + + if (IsCrewmate || !player.IsAlive() || !target.IsAlive() || target.IsImpostorTeam()) + BAUNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); + + return; + } + + if (callId is (byte)RpcCalls.SendChat or (byte)RpcCalls.SendQuickChat) + { + if (player.IsAlive() && GameStates.InGame && !GameStates.IsLobby && !GameStates.IsMeeting && !GameStates.IsExilling && GameStates.IsInTask) + BAUNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); + + return; + } + + if (callId is (byte)RpcCalls.SetLevel) + { + if (reader.BytesRemaining > 0) + { + uint level = reader.ReadPackedUInt32(); + if (level >= 200) + { + BAUNotificationManager.NotifyCheat(player, $"Invalid Level: {level}"); + } + } + + return; + } + + if (IsCrewmate) + { + if (GameStates.IsInGamePlay) + { + if (callId is (byte)RpcCalls.CloseDoorsOfType) + { + byte Type = reader.ReadByte(); + BAUNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)} - {Enum.GetName((SystemTypes)callId)}"); + } + if (callId is (byte)RpcCalls.UpdateSystem) + { + byte Type = reader.ReadByte(); + if (Type is (byte)SystemTypes.Reactor + or (byte)SystemTypes.Laboratory + or (byte)SystemTypes.Comms + or (byte)SystemTypes.LifeSupp + or (byte)SystemTypes.MushroomMixupSabotage + or (byte)SystemTypes.HeliSabotage) + { + BAUNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)} - {Enum.GetName((SystemTypes)callId)}"); + } + } + } + + return; + } + + if (IsImpostor) + { + if (callId is (byte)RpcCalls.CompleteTask or (byte)RpcCalls.BootFromVent) + { + BAUNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); + } + + return; + } + + if (callId is (byte)RpcCalls.Pet or (byte)RpcCalls.CancelPet) + { + if (player?.CurrentOutfit?.PetId == "pet_EmptyPet") + { + BAUNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); + } + + return; + } + } + catch (Exception ex) + { + Logger.Error(ex.ToString()); + } + } + + // Check notify and cancel out request for invalid rpcs + public static bool CheckCancelRPC(PlayerControl player, byte callId, MessageReader reader) + { + try + { + if (PlayerControl.LocalPlayer == null || player == null || player == PlayerControl.LocalPlayer || reader == null || !IsEnabled || !Main.AntiCheat.Value) return true; + + RoleTypes Role = player.Data.RoleType; + bool IsImpostor = player.IsImpostorTeam(); + bool IsCrewmate = !player.IsImpostorTeam(); + + if (TrustedRPCs(callId) != true) + { + BAUNotificationManager.NotifyCheat(player, $"Untrusted RPC received: {callId}"); + return false; + } + + if (callId is (byte)RpcCalls.CheckMurder or (byte)RpcCalls.MurderPlayer) + { + PlayerControl target = reader.ReadNetObject(); + + if (!target.IsAlive() || target.IsImpostorTeam()) + { + BAUNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); + return false; + } + } + + if (callId is (byte)RpcCalls.CheckShapeshift or (byte)RpcCalls.Shapeshift) + { + var flag = reader.ReadBoolean(); + + if (Role is not RoleTypes.Shapeshifter || !player.IsAlive()) + { + BAUNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); + return false; + } + + if (!flag && !player.IsInVent()) + { + BAUNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); + return false; + } + } + + if (callId is (byte)RpcCalls.CheckVanish or (byte)RpcCalls.CheckAppear or (byte)RpcCalls.StartVanish or (byte)RpcCalls.StartAppear) + { + if (Role is not RoleTypes.Phantom || player.Data.IsDead) + { + BAUNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); + return false; + } + } + + if (!player.IsHost()) + { + if (callId is (byte)RpcCalls.SetTasks + or (byte)RpcCalls.ExtendLobbyTimer + or (byte)RpcCalls.CloseMeeting) + { + BAUNotificationManager.NotifyCheat(player, $"Invalid Host RPC: {Enum.GetName((RpcCalls)callId)}"); + return false; + } + } + + if (player.DataIsCollected() == true) + { + if (callId is (byte)RpcCalls.SetName or (byte)RpcCalls.CheckName or (byte)RpcCalls.SetLevel) + { + BAUNotificationManager.NotifyCheat(player, $"Invalid Set RPC: {Enum.GetName((RpcCalls)callId)}"); + return false; + } + } + + if (GameStates.IsInGamePlay) + { + if (callId is (byte)RpcCalls.SetColor + or (byte)RpcCalls.SetHat + or (byte)RpcCalls.SetSkin + or (byte)RpcCalls.SetVisor + or (byte)RpcCalls.SetPet + or (byte)RpcCalls.SetNamePlate) + { + BAUNotificationManager.NotifyCheat(player, $"Invalid Set RPC: {Enum.GetName((RpcCalls)callId)}"); + return false; + } + } + + if (GameStates.IsInGame && GameStates.IsLobby) + { + if (callId is (byte)RpcCalls.StartMeeting + or (byte)RpcCalls.ReportDeadBody + or (byte)RpcCalls.CloseMeeting + or (byte)RpcCalls.Exiled + or (byte)RpcCalls.CastVote + or (byte)RpcCalls.ClearVote + or (byte)RpcCalls.SetRole + or (byte)RpcCalls.ClimbLadder + or (byte)RpcCalls.UsePlatform + or (byte)RpcCalls.UseZipline + or (byte)RpcCalls.CompleteTask + or (byte)RpcCalls.BootFromVent + or (byte)RpcCalls.EnterVent + or (byte)RpcCalls.ExitVent + or (byte)RpcCalls.CloseDoorsOfType + or (byte)RpcCalls.MurderPlayer + or (byte)RpcCalls.CheckMurder + or (byte)RpcCalls.Shapeshift + or (byte)RpcCalls.RejectShapeshift + or (byte)RpcCalls.CheckShapeshift + or (byte)RpcCalls.CheckProtect + or (byte)RpcCalls.ProtectPlayer + or (byte)RpcCalls.StartAppear + or (byte)RpcCalls.StartVanish + or (byte)RpcCalls.CheckAppear + or (byte)RpcCalls.CheckVanish) + { + BAUNotificationManager.NotifyCheat(player, $"Invalid Lobby RPC: {Enum.GetName((RpcCalls)callId)}"); + return false; + } + } + + return true; + } + catch (Exception ex) + { + Logger.Error(ex.ToString()); + return true; + } + } + + // Check if RPC is known + private static bool TrustedRPCs(int RPCId) + { + foreach (RpcCalls rpc in Enum.GetValues(typeof(RpcCalls))) + if ((byte)rpc == RPCId || unchecked((byte)rpc) == RPCId || unchecked((byte)(short)rpc) == RPCId) + return true; + foreach (CustomRPC rpc in Enum.GetValues(typeof(CustomRPC))) + if ((byte)rpc == RPCId || unchecked((byte)rpc) == RPCId || unchecked((byte)(short)rpc) == RPCId) + return true; + + return false; + } +} diff --git a/Modules/BAUDataManager.cs b/Modules/BAUDataManager.cs new file mode 100644 index 0000000..b9d26fb --- /dev/null +++ b/Modules/BAUDataManager.cs @@ -0,0 +1,188 @@ +using System.Text.Json; + +namespace BetterAmongUs; + +class BAUDataManager +{ + public static string GetFilePath(string name) + { + return Path.Combine(Main.GetDataPathToAmongUs(), $"{name}.json"); + } + + public static void SetUp() + { + string filePath = GetFilePath("BetterData"); + + if (!File.Exists(filePath)) + { + // Initialize with predefined categories + var initialData = new Dictionary>> + { + { "Data", new Dictionary>() }, // Default category + { "cheatData", new Dictionary>() }, + { "sickoData", new Dictionary>() }, + { "aumData", new Dictionary>() } + }; + + string json = JsonSerializer.Serialize(initialData, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(filePath, json); + } + else + { + // Load the existing JSON data + string json = File.ReadAllText(filePath); + var jsonData = JsonSerializer.Deserialize>>>(json) ?? new Dictionary>>(); + + // Check and add missing categories + if (!jsonData.ContainsKey("Data")) + { + jsonData["Data"] = new Dictionary>(); + } + if (!jsonData.ContainsKey("cheatData")) + { + jsonData["cheatData"] = new Dictionary>(); + } + if (!jsonData.ContainsKey("sickoData")) + { + jsonData["sickoData"] = new Dictionary>(); + } + if (!jsonData.ContainsKey("aumData")) + { + jsonData["aumData"] = new Dictionary>(); + } + + // Write the updated JSON data back to the file + json = JsonSerializer.Serialize(jsonData, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(filePath, json); + } + } + + public static void Save(string name, string dataToSave, string category = "Data") + { + string filePath = GetFilePath("BetterData"); + + string json = File.ReadAllText(filePath); + var jsonData = JsonSerializer.Deserialize>>(json) ?? new Dictionary>(); + + if (!jsonData.ContainsKey(category)) + { + jsonData[category] = new Dictionary(); + } + + jsonData[category][name] = dataToSave; + + json = JsonSerializer.Serialize(jsonData, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(filePath, json); + } + + public static string Load(string name, string category = "Data") + { + string filePath = GetFilePath("BetterData"); + + string json = File.ReadAllText(filePath); + var jsonData = JsonSerializer.Deserialize>>(json); + + if (jsonData != null && jsonData.ContainsKey(category) && jsonData[category].ContainsKey(name)) + { + return jsonData[category][name]; + } + + return string.Empty; + } + + public static void SaveCheatData(string puid, string friendCode, string name, string category = "cheatData", string reason = "None") + { + string filePath = GetFilePath("BetterData"); + + string json = File.ReadAllText(filePath); + var jsonData = JsonSerializer.Deserialize>>>(json) ?? new Dictionary>>(); + + if (!jsonData.ContainsKey(category)) + { + jsonData[category] = new Dictionary>(); + } + + if (!jsonData[category].ContainsKey(name)) + { + jsonData[category][name] = new Dictionary(); + } + + jsonData[category][name]["FriendCode"] = friendCode; + jsonData[category][name]["HashPUID"] = puid; + jsonData[category][name]["Reason"] = reason; + + json = JsonSerializer.Serialize(jsonData, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(filePath, json); + } + + public static void ClearCheatData() + { + string filePath = GetFilePath("BetterData"); + string json = File.ReadAllText(filePath); + var jsonData = JsonSerializer.Deserialize>>>(json) + ?? new Dictionary>>(); + + // Clear in-save data + if (jsonData != null) + { + if (jsonData.ContainsKey("cheatData")) + { + jsonData["cheatData"].Clear(); + } + + if (jsonData.ContainsKey("sickoData")) + { + jsonData["sickoData"].Clear(); + } + + if (jsonData.ContainsKey("aumData")) + { + jsonData["aumData"].Clear(); + } + + json = JsonSerializer.Serialize(jsonData, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(filePath, json); + } + + // Clear in-memory data + AntiCheat.PlayerData.Clear(); + AntiCheat.SickoData.Clear(); + AntiCheat.AUMData.Clear(); + + Logger.LogCheat("Cleared cheat memory and data"); + } + + public static void LoadCheatData() + { + string filePath = GetFilePath("BetterData"); + string json = File.ReadAllText(filePath); + var jsonData = JsonSerializer.Deserialize>>>(json); + + if (jsonData != null) + { + if (jsonData.ContainsKey("cheatData")) + { + foreach (var item in jsonData["cheatData"]) + { + AntiCheat.PlayerData[item.Value["HashPUID"]] = item.Value["FriendCode"]; + } + } + + if (jsonData.ContainsKey("sickoData")) + { + foreach (var item in jsonData["sickoData"]) + { + AntiCheat.SickoData[item.Value["HashPUID"]] = item.Value["FriendCode"]; + } + } + + if (jsonData.ContainsKey("aumData")) + { + foreach (var item in jsonData["aumData"]) + { + AntiCheat.AUMData[item.Value["HashPUID"]] = item.Value["FriendCode"]; + } + } + } + } +} diff --git a/Modules/BAUNotificationManager.cs b/Modules/BAUNotificationManager.cs new file mode 100644 index 0000000..d906021 --- /dev/null +++ b/Modules/BAUNotificationManager.cs @@ -0,0 +1,102 @@ +using TMPro; +using UnityEngine; + +namespace BetterAmongUs; + +class BAUNotificationManager +{ + public static GameObject BAUNotificationManagerObj; + private static Dictionary NotifyQueue = []; + private static float showTime = 0f; + private static Camera localCamera; + private static bool Notifying = false; + + public static void Notify(string text, float Time = 5f) + { + if (BAUNotificationManagerObj != null) + { + if (Notifying) + { + if (text == BAUNotificationManagerObj.transform.Find("Sizer/ChatText (TMP)").GetComponent().text) + return; + NotifyQueue[text] = Time; + return; + } + + showTime = Time; + BAUNotificationManagerObj.SetActive(true); + BAUNotificationManagerObj.transform.Find("Sizer/ChatText (TMP)").GetComponent().text = text; + SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.TaskCompleteSound, false, 1f); + Notifying = true; + } + } + + public static void NotifyCheat(PlayerControl player, string reason, string newText = "", bool kickPlayer = true) + { + string text = $"Player: {player?.CurrentOutfit.PlayerName} Has been detected doing an unauthorized action: {reason}"; + if (newText != "") + text = $"Player: {player?.CurrentOutfit.PlayerName} " + newText + $": {reason}"; + + if (!AntiCheat.PlayerData.ContainsKey(Utils.GetHashPuid(player))) + { + AntiCheat.PlayerData[Utils.GetHashPuid(player)] = player.Data.FriendCode; + BAUDataManager.SaveCheatData(Utils.GetHashPuid(player), player.Data.FriendCode, player.Data.PlayerName, "cheatData", reason); + Notify(text, Time: 8f); + } + + if (GameStates.IsHost && kickPlayer) + { + player.RpcSetName($"{player.Data.PlayerName} Has been banned by Anti-Cheat, Reason: {reason}"); + AmongUsClient.Instance.KickPlayer(player.GetClientId(), true); + } + + Logger.LogCheat(Utils.GetRawText(text)); + } + + public static void Update() + { + if (BAUNotificationManagerObj != null) + { + if (!localCamera) + { + if (DestroyableSingleton.InstanceExists) + { + localCamera = DestroyableSingleton.Instance.GetComponentInChildren(); + } + else + { + localCamera = Camera.main; + } + } + + BAUNotificationManagerObj.transform.position = AspectPosition.ComputeWorldPosition(localCamera, AspectPosition.EdgeAlignments.Bottom, new Vector3(-1.3f, 0.7f, localCamera.nearClipPlane + 0.1f)); + + showTime -= Time.deltaTime; + if (showTime <= 0f) + { + BAUNotificationManagerObj.transform.Find("Sizer/ChatText (TMP)").GetComponent().text = ""; + BAUNotificationManagerObj.SetActive(false); + Notifying = false; + + CheckNotifyQueue(); + } + + if (!GameStates.IsInGame) + { + BAUNotificationManagerObj.SetActive(false); + showTime = 0f; + } + } + } + + private static void CheckNotifyQueue() + { + if (NotifyQueue.Any()) + { + var key = NotifyQueue.Keys.First(); + var value = NotifyQueue[key]; + Notify(key, value); + NotifyQueue.Remove(key); + } + } +} diff --git a/Modules/BetterHostManager.cs b/Modules/BetterHostManager.cs new file mode 100644 index 0000000..6d20326 --- /dev/null +++ b/Modules/BetterHostManager.cs @@ -0,0 +1,99 @@ +using Hazel; +using System.Text; +using System.Text.RegularExpressions; + +namespace BetterAmongUs; + +class BetterHostManager +{ + public static bool CheckRPCAsHost(PlayerControl player, byte callId, MessageReader reader) + { + + + return true; + } + + // Set player names for better host + public static void SetPlayersInfoAsHost(PlayerControl player, bool forMeeting = false, bool force = false, bool IsBetterHost = true) + { + if (player == null || !player.DataIsCollected()) return; + + if (!Main.BetterHost.Value) + { + if (player.Data.PlayerName != player.CurrentOutfit.PlayerName && !player.IsInShapeshift() || force) + { + player.RpcSetName(player.Data.PlayerName); + Logger.Log($"Reset {player.Data.PlayerName} name", "RPC"); + } + return; + } + + if (!IsBetterHost && !force) return; + + string friendCode = player.Data.FriendCode; + + string pattern = @"^[a-zA-Z0-9#]+$"; + string hashtagPattern = @"^#[0-9]{4}$"; + string friendCodeColor = (Regex.Replace(friendCode, hashtagPattern, string.Empty).Length is > 10 or < 5 || !Regex.IsMatch(friendCode, pattern) || !Regex.IsMatch(friendCode, hashtagPattern)) ? "#00f7ff" : "#ff0000"; + + foreach (PlayerControl target in Main.AllPlayerControls) + { + if (target == null || target == PlayerControl.LocalPlayer || target.GetIsBetterUser()) continue; + + string NewName = player.CurrentOutfit.PlayerName; + string BottomTag = ""; + + StringBuilder sbTopTag = new StringBuilder(); + StringBuilder sbTopInfo = new StringBuilder(); + + if (GameStates.IsLobby) + { + NewName = player.Data.PlayerName; + + if (player.IsDev()) + sbTopTag.Append("(Dev)+++"); + + if (player == PlayerControl.LocalPlayer) + sbTopTag.Append($"Better Host+++"); + else if (player.GetIsBetterUser()) + sbTopTag.Append("Better User+++"); + + BottomTag += $"{friendCode}"; + } + else if (GameStates.IsInGame && !GameStates.IsLobby) + { + string RoleHexColor = player.IsImpostorTeam() ? "#ff1919" : "#8cffff"; + string Role = $"{player.GetRoleName()}"; + if (!player.IsImpostorTeam() && !target.IsImpostorTeam()) + { + if (target.IsAlive() && player != target) + { + Role = ""; + } + } + + sbTopTag.Append($"{Role}+++"); + } + + for (int i = 0; i < sbTopTag.ToString().Split("+++").Length; i++) + { + if (!string.IsNullOrEmpty(sbTopTag.ToString().Split("+++")[i])) + { + if (i < sbTopTag.ToString().Split("+++").Length) + { + sbTopInfo.Append(sbTopTag.ToString().Split("+++")[i]); + } + if (i != sbTopTag.ToString().Split("+++").Length - 2) + { + sbTopInfo.Append(" - "); + } + } + } + + NewName = $"{sbTopInfo}\n{NewName}\n{BottomTag}"; + + player.RpcSetNamePrivate(NewName, target); + Logger.Log($"Set {player.Data.PlayerName} name to {NewName.Replace("\n", "-")} for {target.Data.PlayerName}", "RPC"); + } + } +} diff --git a/Modules/ClientOptionItem.cs b/Modules/ClientOptionItem.cs new file mode 100644 index 0000000..52a1826 --- /dev/null +++ b/Modules/ClientOptionItem.cs @@ -0,0 +1,154 @@ +using BepInEx.Configuration; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace BetterAmongUs; + +// Code from: https://github.com/tukasa0001/TownOfHost/pull/1265 +// Code from: https://github.com/0xDrMoe/TownofHost-Enhanced +public class ClientOptionItem +{ + public ConfigEntry Config; + public ToggleButtonBehaviour ToggleButton; + + public static SpriteRenderer CustomBackground; + private static int numOptions = 0; + + private ClientOptionItem( + string name, + ConfigEntry config, + OptionsMenuBehaviour optionsMenuBehaviour, + Action additionalOnClickAction = null, + Func toggleCheck = null, + bool IsToggle = true) + { + try + { + Config = config; + + var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; + + // 1つ目のボタンの生成時に背景も生成 + if (CustomBackground == null) + { + numOptions = 0; + CustomBackground = Object.Instantiate(optionsMenuBehaviour.Background, optionsMenuBehaviour.transform); + CustomBackground.name = "CustomBackground"; + CustomBackground.transform.localScale = new(0.9f, 0.9f, 1f); + CustomBackground.transform.localPosition += Vector3.back * 8; + CustomBackground.gameObject.SetActive(false); + + var closeButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); + closeButton.transform.localPosition = new(1.3f, -2.3f, -6f); + closeButton.name = "Back"; + closeButton.Text.text = "Back"; + closeButton.Background.color = Palette.DisabledGrey; + var closePassiveButton = closeButton.GetComponent(); + closePassiveButton.OnClick = new(); + closePassiveButton.OnClick.AddListener(new Action(() => + { + CustomBackground.gameObject.SetActive(false); + })); + + UiElement[] selectableButtons = optionsMenuBehaviour.ControllerSelectable.ToArray(); + PassiveButton leaveButton = null; + PassiveButton returnButton = null; + foreach (var button in selectableButtons) + { + if (button == null) continue; + + if (button.name == "LeaveGameButton") + leaveButton = button.GetComponent(); + else if (button.name == "ReturnToGameButton") + returnButton = button.GetComponent(); + } + var generalTab = mouseMoveToggle.transform.parent.parent.parent; + + var modOptionsButton = Object.Instantiate(mouseMoveToggle, generalTab); + modOptionsButton.transform.localPosition = leaveButton?.transform?.localPosition ?? new(0f, -2.4f, 1f); + modOptionsButton.name = "Better Options"; + modOptionsButton.Text.text = "Better Options"; + modOptionsButton.Background.color = new Color32(0, 150, 0, byte.MaxValue); + var modOptionsPassiveButton = modOptionsButton.GetComponent(); + modOptionsPassiveButton.OnClick = new(); + modOptionsPassiveButton.OnClick.AddListener(new Action(() => + { + CustomBackground.gameObject.SetActive(true); + })); + + if (leaveButton != null) + leaveButton.transform.localPosition = new(-1.35f, -2.411f, -1f); + if (returnButton != null) + returnButton.transform.localPosition = new(1.35f, -2.411f, -1f); + } + + // ボタン生成 + ToggleButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); + ToggleButton.transform.localPosition = new Vector3( + // 現在のオプション数を基に位置を計算 + numOptions % 2 == 0 ? -1.3f : 1.3f, + 2.2f - (0.5f * (numOptions / 2)), + -6f); + ToggleButton.name = name; + ToggleButton.Text.text = name; + ToggleButton.Text.text += (Config != null && Config.Value) ? ": On" : ": Off"; + + var passiveButton = ToggleButton.GetComponent(); + passiveButton.OnClick = new(); + + if (IsToggle == false) + { + ToggleButton.Text.text = name; + ToggleButton.Rollover?.ChangeOutColor(new(0, 150, 0, byte.MaxValue)); + ToggleButton.Text.color = new(1f, 1f, 1f, 1f); + + passiveButton.OnClick.AddListener(new Action(() => + { + additionalOnClickAction?.Invoke(); + })); + + return; + } + + passiveButton.OnClick.AddListener(new Action(() => + { + if (toggleCheck() == false) + { + return; + } + + if (config != null) config.Value = !config.Value; + UpdateToggle(); + additionalOnClickAction?.Invoke(); + })); + UpdateToggle(); + } + finally { numOptions++; } + } + + public static ClientOptionItem Create( + string name, + ConfigEntry config, + OptionsMenuBehaviour optionsMenuBehaviour, + Action additionalOnClickAction = null, + Func toggleCheck = null, + bool IsToggle = true) + { + toggleCheck ??= () => true; + + return new ClientOptionItem(name, config, optionsMenuBehaviour, additionalOnClickAction, toggleCheck, IsToggle); + } + + public void UpdateToggle() + { + if (ToggleButton == null) return; + + var color = (Config != null && Config.Value) ? new Color32(0, 150, 0, byte.MaxValue) : new Color32(77, 77, 77, byte.MaxValue); + var textColor = (Config != null && Config.Value) ? new Color(1f, 1f, 1f, 1f) : new Color(1f, 1f, 1f, 0.5f); + ToggleButton.Background.color = color; + ToggleButton.Rollover?.ChangeOutColor(color); + ToggleButton.Text.color = textColor; + ToggleButton.Text.text = ToggleButton.name; + ToggleButton.Text.text += (Config != null && Config.Value) ? ": On" : ": Off"; + } +} \ No newline at end of file diff --git a/Modules/DebugMenu.cs b/Modules/DebugMenu.cs new file mode 100644 index 0000000..3ff28c4 --- /dev/null +++ b/Modules/DebugMenu.cs @@ -0,0 +1,235 @@ +using UnityEngine; +using Il2CppInterop.Runtime.Attributes; +using BetterAmongUs.Resources; + +namespace BetterAmongUs; + +public class DebugMenu : MonoBehaviour +{ + public static bool RevealRoles = false; + + + [HideFromIl2Cpp] + public DragWindow Window { get; } + public bool WindowEnabled { get; set; } = true; + + private List tabs = new List(); + private List tabActions = new List(); + private Dictionary> + tabContents + = new Dictionary>(); + + private string selectedTab = null; + + public void Start() + { + InitializeMenu(); + } + + private void InitializeMenu() + { + string Main = "Main"; + string HostOnly = "Host Only"; + string Misc = "Misc"; + AddTab(Main); + AddTab(HostOnly); + AddTab(Misc); + + AddButtonToTab(Main, "Reveal All Roles", () => + { + RevealRoles = !RevealRoles; + }); + + AddButtonToTab(Main, "Trigger Anti-Cheat", () => + { + if (GameStates.IsInGame || GameStates.IsLobby || GameStates.IsFreePlay) + { + BAUNotificationManager.NotifyCheat(PlayerControl.LocalPlayer, "Debug Test", kickPlayer: false); + } + }, true); + + AddButtonToTab(Main, "Clear Anti-Cheat Data", () => + { + BAUDataManager.ClearCheatData(); + }); + + AddButtonToTab(HostOnly, "Sync All Names", () => + { + RPC.SyncAllNames(); + }); + + // Automatically select the first tab + if (tabs.Count > 0) + { + selectedTab = tabs[0]; + } + + WindowEnabled = false; + } + + public void Update() + { + if (Input.GetKeyDown(KeyCode.F1)) + Toggle(); + + if (!GameStates.IsInGamePlay) + RevealRoles = false; + } + + public DebugMenu(IntPtr ptr) : base(ptr) + { + Window = new DragWindow(new Rect(20, 20, 400, 300), "Debug Menu", () => + { + GUILayout.BeginVertical(); + try + { + GUIStyle headerStyle = new GUIStyle(GUI.skin.label) + { + fontSize = 18, + fontStyle = FontStyle.Bold, + alignment = TextAnchor.MiddleCenter, + normal = { textColor = Color.white } + }; + + GUILayout.Label("Debug Menu - Press F1 To Hide", headerStyle); + GUILayout.Space(10); + + if (tabs.Count > 0) + { + // Display tabs horizontally + GUILayout.BeginHorizontal(); + foreach (var tab in tabs) + { + // Highlight the selected tab with color change + GUI.backgroundColor = tab == selectedTab ? new Color(0.2f, 0.2f, 0.2f) : GUI.backgroundColor; + if (GUILayout.Button(tab)) + { + selectedTab = tab; + } + // Reset the background color after drawing the button + GUI.backgroundColor = Color.white; + } + GUILayout.EndHorizontal(); + + // Display content of selected tab + if (!string.IsNullOrEmpty(selectedTab) && tabContents.ContainsKey(selectedTab)) + { + var contents = tabContents[selectedTab]; + + // Display contents for selected tab + GUILayout.BeginVertical(); + int buttonCount = 0; + bool isNewRowStarted = false; + + foreach (var (isText, content, action, startNewRow, centerText, colorHex) in contents) + { + if (isText) + { + if (isNewRowStarted) + { + GUILayout.EndHorizontal(); + isNewRowStarted = false; + } + GUIStyle style = new GUIStyle(GUI.skin.label); + + // Set text color if specified + if (!string.IsNullOrEmpty(colorHex) && ColorUtility.TryParseHtmlString(colorHex, out Color color)) + { + style.normal.textColor = color; + } + + if (centerText) + { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.Label(content, style); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + } + else + { + GUILayout.Label(content, style); + } + buttonCount = 0; // Reset button count after text + } + else + { + if (startNewRow || buttonCount % 3 == 0) + { + if (isNewRowStarted) + { + GUILayout.EndHorizontal(); // End previous row if any + } + GUILayout.BeginHorizontal(); // Start a new row + isNewRowStarted = true; + } + + if (GUILayout.Button(content)) + { + action?.Invoke(); + } + + buttonCount++; + } + } + + // End the last row if not empty + if (isNewRowStarted) + { + GUILayout.EndHorizontal(); + } + + GUILayout.EndVertical(); + } + } + } + finally + { + GUILayout.EndVertical(); + } + }); + } + + + public void AddTab(string tabName) + { + tabs.Add(tabName); + tabActions.Add(() => { /* Default action, can be replaced later */ }); + tabContents[tabName] = new List<(bool isText, string content, Action action, bool startNewRow, bool centerText, string colorHex)>(); + AddNewRowToTab(tabName); + } + + public void AddButtonToTab(string tabName, string buttonLabel, Action action, bool startNewRow = false) + { + if (tabContents.ContainsKey(tabName)) + { + tabContents[tabName].Add((false, buttonLabel, action, startNewRow, false, null)); + } + } + + public void AddTextToTab(string tabName, string text, bool centerText = false, string colorHex = null) + { + if (tabContents.ContainsKey(tabName)) + { + tabContents[tabName].Add((true, text, null, false, centerText, colorHex)); + } + } + + public void AddNewRowToTab(string tabName) + { + AddTextToTab(tabName, ""); // Adding an empty text item to create a new row + } + + public void OnGUI() + { + if (WindowEnabled) + Window.OnGUI(); + } + + public void Toggle() + { + if (!GameStates.IsDev) return; + + WindowEnabled = !WindowEnabled; + } +} diff --git a/Modules/Debugger.cs b/Modules/Debugger.cs new file mode 100644 index 0000000..0179a18 --- /dev/null +++ b/Modules/Debugger.cs @@ -0,0 +1,35 @@ + +namespace BetterAmongUs; + +class Logger +{ + public static void Log(string info, string tag = "Log") + { + string mark = $"{DateTime.Now:HH:mm} [BetterLog][{tag}]"; + string logFilePath = Path.Combine(Environment.CurrentDirectory, "better-log.txt"); + string newLine = $"{mark}: {info}"; + File.AppendAllText(logFilePath, newLine + Environment.NewLine); + Main.Logger.LogInfo($"[{tag}] {info}"); + } + public static void LogCheat(string info, string tag = "AntiCheat") => Log(info, tag); + public static void Error(string info, string tag = "Error") => Log(info, tag); + public static void Test() => Log("------------------> TEST <------------------", "TEST"); + // Log in game join msg + public static void InGame(string info) + { + if (DestroyableSingleton._instance) DestroyableSingleton.Instance.Notifier.AddDisconnectMessage(info); + Log(info, "InGame"); + } + + // ------------- Private Log ------------- + // Logs that can only be accessed when dumped + + public static void LogPrivate(string info, string tag = "Log") + { + string mark = $"{DateTime.Now:HH:mm} [BetterLog][PrivateLog][{tag}]"; + string logFilePath = Path.Combine(Environment.CurrentDirectory, "better-log.txt"); + string newLine = $"{mark}: " + Encryptor.Encrypt($"{info}"); + File.AppendAllText(logFilePath, newLine + Environment.NewLine); + } +} + diff --git a/Modules/Encryptor.cs b/Modules/Encryptor.cs new file mode 100644 index 0000000..fb1af8b --- /dev/null +++ b/Modules/Encryptor.cs @@ -0,0 +1,39 @@ +using System.Security.Cryptography; +using System.Text; + +namespace BetterAmongUs; + +class Encryptor +{ + private static readonly byte[] Key = Encoding.UTF8.GetBytes("0123456789ABCDEF0123456789ABCDEF"); + private static readonly byte[] IV = Encoding.UTF8.GetBytes("ABCDEF0123456789"); + + public static string Encrypt(string input) + { + using (Aes aes = Aes.Create()) + { + aes.Key = Key; + aes.IV = IV; + + using MemoryStream memoryStream = new(); + using CryptoStream cryptoStream = new(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write); + using (StreamWriter streamWriter = new(cryptoStream)) + { + streamWriter.Write(input); + } + return Convert.ToBase64String(memoryStream.ToArray()); + } + } + + public static string Decrypt(string input) + { + using Aes aes = Aes.Create(); + aes.Key = Key; + aes.IV = IV; + + using MemoryStream memoryStream = new(Convert.FromBase64String(input)); + using CryptoStream cryptoStream = new(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Read); + using StreamReader streamReader = new(cryptoStream); + return streamReader.ReadToEnd(); + } +} diff --git a/Modules/EventLogManager.cs b/Modules/EventLogManager.cs new file mode 100644 index 0000000..3e86be0 --- /dev/null +++ b/Modules/EventLogManager.cs @@ -0,0 +1,41 @@ +using HarmonyLib; + +namespace BetterAmongUs; + +class EventLogManager +{ + [HarmonyPatch(typeof(PlayerControl))] + class PlayerControlPatch + { + [HarmonyPatch(nameof(PlayerControl.MurderPlayer))] + [HarmonyPostfix] + public static void MurderPlayer__Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) => + Logger.LogPrivate($"{__instance.Data.PlayerName} Has killed {target.Data.PlayerName} as {Utils.GetRoleName(__instance.Data.RoleType)}", "EventLog"); + [HarmonyPatch(nameof(PlayerControl.Shapeshift))] + [HarmonyPostfix] + public static void Shapeshift_Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target, [HarmonyArgument(1)] bool animate) + { + if (__instance != target) + Logger.LogPrivate($"{__instance.Data.PlayerName} Has Shapeshifted into {target.Data.PlayerName}, did animate: {animate}", "EventLog"); + else + Logger.LogPrivate($"{__instance.Data.PlayerName} Has Un-Shapeshifted, did animate: {animate}", "EventLog"); + } + } + + [HarmonyPatch(typeof(PlayerPhysics))] + class PlayerPhysicsPatch + { + [HarmonyPatch(nameof(PlayerPhysics.BootFromVent))] + [HarmonyPostfix] + public static void BootFromVent_Postfix(PlayerPhysics __instance, [HarmonyArgument(0)] int ventId) => + Logger.LogPrivate($"{__instance.myPlayer.Data.PlayerName} Has been booted from vent: {ventId}, as {Utils.GetRoleName(__instance.myPlayer.Data.RoleType)}", "EventLog"); + [HarmonyPatch(nameof(PlayerPhysics.CoEnterVent))] + [HarmonyPostfix] + public static void CoEnterVent_Postfix(PlayerPhysics __instance, [HarmonyArgument(0)] int ventId) => + Logger.LogPrivate($"{__instance.myPlayer.Data.PlayerName} Has entered vent: {ventId}, as {Utils.GetRoleName(__instance.myPlayer.Data.RoleType)}", "EventLog"); + [HarmonyPatch(nameof(PlayerPhysics.CoExitVent))] + [HarmonyPostfix] + public static void CoExitVent_Postfix(PlayerPhysics __instance, [HarmonyArgument(0)] int ventId) => + Logger.LogPrivate($"{__instance.myPlayer.Data.PlayerName} Has exit vent: {ventId}, as {Utils.GetRoleName(__instance.myPlayer.Data.RoleType)}", "EventLog"); + } +} diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs new file mode 100644 index 0000000..02f249f --- /dev/null +++ b/Modules/ExtendedPlayerControl.cs @@ -0,0 +1,145 @@ +using AmongUs.GameOptions; +using InnerNet; +using TMPro; +using UnityEngine; + +namespace BetterAmongUs; + +static class ExtendedPlayerControl +{ + // Get players client + public static ClientData? GetClient(this PlayerControl player) + { + try + { + var client = AmongUsClient.Instance.allClients.ToArray().FirstOrDefault(cd => cd.Character.PlayerId == player.PlayerId); + return client; + } + catch + { + return null; + } + } + // Get players client id + public static int GetClientId(this PlayerControl player) + { + if (player == null) return -1; + var client = player.GetClient(); + return client == null ? -1 : client.Id; + } + // Set players over head text + public static void SetPlayerTextInfo(this PlayerControl player, string text, bool isBottom = false, bool isInfo = false) + { + if (player == null) return; + GameObject playerbase = player.gameObject; + string InfoType = isBottom ? "InfoText_B_TMP" : "InfoText_T_TMP"; + + if (isInfo) + { + InfoType = "InfoText_Info_TMP"; + GameObject TopText = GameObject.Find($"{playerbase.transform.name}/Names/NameText_TMP/InfoText_T_TMP"); + if (TopText != null) + { + if (TopText.GetComponent()?.text.Replace("", string.Empty).Replace("", string.Empty).Length < 1) + { + text = "" + text + ""; + } + } + } + + text = "" + text + ""; + GameObject TextObj = GameObject.Find($"{player?.gameObject.transform.name}/Names/NameText_TMP/{InfoType}"); + if (TextObj != null) + { + TextObj.GetComponent().text = text; + } + } + // Reset players over head text + public static void ResetAllPlayerTextInfo(this PlayerControl player) + { + if (player == null) return; + player.SetPlayerTextInfo("", isInfo: true); + player.SetPlayerTextInfo(""); + player.SetPlayerTextInfo("", isBottom: true); + } + // Check if players character has been created and received from the Host + public static bool DataIsCollected(this PlayerControl player) + { + if (player == null) return false; + + if (player.gameObject.transform.Find("Names/NameText_TMP").GetComponent().text + is "???" or "Player" or "Loading" or null + || player.Data == null + || string.IsNullOrEmpty(player.Data.Puid) + || player.Data.PlayerLevel == uint.MaxValue + || player.CurrentOutfit == null + || player.CurrentOutfit.ColorId == -1) + { + return false; + } + return true; + } + + // RPCs + public static void RpcSendHostChat(this PlayerControl player, string text, string title = "(System Message)", bool sendToBetterUser = true) + { + if (player == null) return; + RPC.SendHostChatToPlayer(player, text, title, sendToBetterUser); + } + public static void RpcSetNamePrivate(this PlayerControl player, string name, PlayerControl target) + { + if (player == null || target == null) return; + RPC.SetNamePrivate(player, name, target); + } + public static void RpcExile(this PlayerControl player) + { + if (player == null) return; + RPC.ExileAsync(player); + } + + public static bool IsInRoomSelect(this PlayerControl player) + { + if (player == null) return false; + return GameStates.AirshipIsActive && Vector2.Distance(player.GetTruePosition(), new(-25, 40)) < 5f; + } + + // Get vent Id that the player is in. + public static int GetPlayerVentId(this PlayerControl player) + { + if(player == null) return -1; + + if (!(ShipStatus.Instance.Systems.TryGetValue(SystemTypes.Ventilation, out var systemType) && + systemType.TryCast() is VentilationSystem ventilationSystem)) + return 0; + + return ventilationSystem.PlayersInsideVents.TryGetValue(player.PlayerId, out var playerIdVentId) ? playerIdVentId : -1; + } + + public static Vector2 GetCustomPosition(this PlayerControl player) => new(player.transform.position.x, player.transform.position.y); + + // Check if player is a dev + public static bool IsDev(this PlayerControl player) => player != null && Main.DevUser.Contains(Utils.GetHashPuid(player)); + // Check if player is alive + public static bool IsAlive(this PlayerControl player) => player?.Data != null && !player.Data.IsDead; + // Check if player is in a vent + public static bool IsInVent(this PlayerControl player) => player != null && (player.inVent || player.walkingToVent || player.MyPhysics?.Animations?.IsPlayingEnterVentAnimation() == true); + // Check if player role name + public static string GetRoleName(this PlayerControl player) => player?.Data != null && Main.GetRoleName.TryGetValue((int)player.Data.RoleType, out var roleName) ? roleName : string.Empty; + // Check if player is Shapeshifting + public static bool IsInShapeshift(this PlayerControl player) => player != null && (player.shapeshiftTargetPlayerId > -1 || player.shapeshifting); + // Check if player is on imposter team + public static bool IsImpostorTeam(this PlayerControl player) => player?.Data != null && (player.Data.RoleType is RoleTypes.Impostor or RoleTypes.ImpostorGhost or RoleTypes.Shapeshifter or RoleTypes.Phantom); + // Check if player is a imposter teammate + public static bool IsImpostorTeammate(this PlayerControl player) => + player != null && PlayerControl.LocalPlayer != null && + ((player == PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.IsImpostorTeam()) || + (PlayerControl.LocalPlayer.IsImpostorTeam() && player.IsImpostorTeam())); + // Check if player is in the Anti-Cheat list + public static bool IsCheater(this PlayerControl player) => + player != null && (AntiCheat.PlayerData.ContainsKey(Utils.GetHashPuid(player)) || + AntiCheat.SickoData.ContainsKey(Utils.GetHashPuid(player)) || + AntiCheat.AUMData.ContainsKey(Utils.GetHashPuid(player))); + // Check if player is the host + public static bool IsHost(this PlayerControl player) => player?.Data != null && GameData.Instance?.GetHost()?.Puid == player.Data.Puid; + +} diff --git a/Modules/ExtendedPlayerInfo.cs b/Modules/ExtendedPlayerInfo.cs new file mode 100644 index 0000000..b2d5932 --- /dev/null +++ b/Modules/ExtendedPlayerInfo.cs @@ -0,0 +1,21 @@ + +namespace BetterAmongUs; + +static class ExtendedPlayerInfo +{ + private static Dictionary IsBetterUser = new Dictionary(); + + public static bool GetIsBetterUser(this PlayerControl player) + { + if (IsBetterUser.ContainsKey(player)) + return IsBetterUser[player]; + else + return false; + } + + public static void SetIsBetterUser(this PlayerControl player, bool value) + { + IsBetterUser[player] = value; + } +} + diff --git a/Modules/FileChecker.cs b/Modules/FileChecker.cs new file mode 100644 index 0000000..e5690b5 --- /dev/null +++ b/Modules/FileChecker.cs @@ -0,0 +1,188 @@ +using System.Collections.ObjectModel; +using UnityEngine; + +namespace BetterAmongUs; + +class FileChecker +{ + private static bool Enabled = true; + private static readonly ReadOnlyCollection UnsupportedBepInExMods = new(new List { "TOHE", "YuAntiCheat" }); // Put BepInEx BepInPlugin name, not dll name here lol. + private static readonly ReadOnlyCollection BannedBepInExMods = new(new List { "MalumMenu", "MalumMenu-Yu", "MalumMenuYu", "AUnlocker" }); // Put BepInEx BepInPlugin name, not dll name here lol. + private static readonly ReadOnlyCollection KeyWordsInVersionInfo = new(new List { "Malum", "Sicko", "AUM" }); // Banned words for version text + public static string UnauthorizedReason = string.Empty; + public static List CheatTags = []; // For API report + public static bool HasTrySpoofFriendCode = false; + public static bool HasUnauthorizedFile = false; + public static bool HasShownPopUp = false; + + // Set up if unauthorized files have been found. + public static void UpdateUnauthorizedFiles() + { + if (Enabled == false) return; + + GameObject PlayButton = GameObject.Find("Main Buttons/PlayButton"); + + // Disable play button + if (PlayButton != null) + { + if (EOSManager.Instance.userId == null || HasTrySpoofFriendCode) + { + PlayButton.GetComponent().enabled = false; + GameObject.Find("Main Buttons/PlayButton/Inactive").GetComponent().color = Color.gray; + } + else + { + PlayButton.GetComponent().enabled = true; + GameObject.Find("Main Buttons/PlayButton/Inactive").GetComponent().color = new Color(1.0f, 1.0f, 1.0f); + } + } + + // Unauthorized file or ban detected. + if (HasUnauthorizedFile) + { + GameObject playOnlineButton = GameObject.Find("PlayOnlineButton"); + + if (playOnlineButton != null) + { + PassiveButton PassiveButtonComponent = playOnlineButton.GetComponent(); + PlayOnlineButtonSprite PlayOnlineButtonSpriteComponent = playOnlineButton.GetComponent(); + + if (PassiveButtonComponent != null) + { + if (PassiveButtonComponent != null) + PassiveButtonComponent.enabled = false; + + PlayOnlineButtonSpriteComponent?.SetGreyscale(); + } + } + + GameObject SignInStatus = GameObject.Find("SignInStatus"); + + if (SignInStatus != null) + { + SignInStatusComponent SignInStatusCom = SignInStatus.GetComponent(); + SignInStatusCom?.SetOffline(); + + GameObject.Find("Account_CTA")?.SetActive(false); + GameObject.Find("AccountTab/GameHeader/LeftSide/FriendCode")?.SetActive(false); + if (GameObject.Find("Stats_CTA") != null) GameObject.Find("Stats_CTA").transform.position = new Vector2(1.7741f, -0.2442f); + } + + SoundManager.instance?.ChangeMusicVolume(0); + return; + } + + if (EOSManager.Instance.editAccountUsername.gameObject.active || EOSManager.Instance.askToMergeAccount.gameObject.active) + { + HasTrySpoofFriendCode = true; + } + } + + private static readonly string UnauthorizedTextDetectedMsg = "Unauthorized {0} Detected"; + private static readonly string UnauthorizedFileMsg = "Unauthorized Game Files Detected"; + private static readonly string OnlineMsg = "Online access has been temporarily disabled!"; + private static readonly string UnsupportedBepInExModMsg = "Unsupported BepInEx Mod Detected"; + private static readonly string BannedBepInExModMsg = "Unauthorized BepInEx Mod Detected"; + + // Check if there's any unauthorized files. + public static bool CheckIfUnauthorizedFiles() + { + if (Enabled == false) return false; + + // Get user info for later use with API. + string ClientUserName = GameObject.Find("AccountTab")?.GetComponent()?.userName.text; + string ClientFriendCode = EOSManager.Instance.friendCode; + string ClientPUIDHash = Utils.GetHashPuid(EOSManager.Instance.ProductUserId); + + if (GameStates.IsDev) + { + Enabled = false; + return false; + } + + // Check for Banned BepInEx Mods + foreach (var bannedMod in BannedBepInExMods) + { + if (IsBepInExModLoaded(bannedMod)) + { + if (!HasUnauthorizedFile) UnauthorizedReason = BannedBepInExModMsg; + if (!CheatTags.Contains($"{bannedMod}-BepInEx")) CheatTags.Add($"{bannedMod}-BepInEx"); + HasUnauthorizedFile = true; + } + } + + // Check for Unsupported BepInEx Mods + foreach (var unsupportedMod in UnsupportedBepInExMods) + { + if (IsBepInExModLoaded(unsupportedMod)) + { + if (!HasUnauthorizedFile) UnauthorizedReason = UnsupportedBepInExModMsg; + HasUnauthorizedFile = true; + } + } + + // Check for version.dll + if (File.Exists(Path.Combine(Environment.CurrentDirectory, "version.dll"))) + { + string versiondll = "'version.dll'"; + if (!HasUnauthorizedFile) UnauthorizedReason = string.Format(UnauthorizedTextDetectedMsg, versiondll); + if (!CheatTags.Contains("version.dll")) CheatTags.Add("version.dll"); + HasUnauthorizedFile = true; + } + + // Check for banned words in VersionInfo display. Aka check cheat developers ego + foreach (var WordInVersionInfo in KeyWordsInVersionInfo) + { + if (UnityEngine.Object.FindFirstObjectByType().text.text.ToLower().Contains(WordInVersionInfo.ToLower())) + { + if (!HasUnauthorizedFile) UnauthorizedReason = UnauthorizedFileMsg; + if (!CheatTags.Contains($"{WordInVersionInfo}-VersionInfo")) CheatTags.Add($"{WordInVersionInfo}-VersionInfo"); + HasUnauthorizedFile = true; + } + } + + // Check for Sicko leftover files + if (File.Exists(Path.Combine(Environment.CurrentDirectory, "sicko-settings.json")) || + File.Exists(Path.Combine(Environment.CurrentDirectory, "sicko-log.txt")) || + File.Exists(Path.Combine(Environment.CurrentDirectory, "sicko-prev-log.txt")) || + File.Exists(Path.Combine(Environment.CurrentDirectory, "sicko-config"))) + { + if (!HasUnauthorizedFile) UnauthorizedReason = UnauthorizedFileMsg; + if (!CheatTags.Contains("Sicko-Menu-Files")) CheatTags.Add("Sicko-Menu-Files"); + HasUnauthorizedFile = true; + } + + // Check for AUM leftover files + if (File.Exists(Path.Combine(Environment.CurrentDirectory, "settings.json")) || + File.Exists(Path.Combine(Environment.CurrentDirectory, "aum-log.txt")) || + File.Exists(Path.Combine(Environment.CurrentDirectory, "aum-prev-log.txt"))) + { + if (!HasUnauthorizedFile) UnauthorizedReason = UnauthorizedFileMsg; + if (!CheatTags.Contains("AUM-Menu-Files")) CheatTags.Add("AUM-Menu-Files"); + HasUnauthorizedFile = true; + } + + // ----------- Unused Until API support is added! ----------- + + // Combine Player information and Tags + string tagsAsString = string.Join(" - ", CheatTags); + string playerInfo = $"{ClientUserName}.{ClientFriendCode}.{ClientPUIDHash} - {tagsAsString}"; // If Detection goes off send this information to the API database! + + // ---------------------------------------------------------- + + return HasUnauthorizedFile; + } + + // Get all loaded BepInEx mods and check if one is on the ban list. + private static bool IsBepInExModLoaded(string modName) + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.FullName.Contains(modName)) + { + return true; + } + } + return false; + } +} diff --git a/Modules/GameState.cs b/Modules/GameState.cs new file mode 100644 index 0000000..c88d014 --- /dev/null +++ b/Modules/GameState.cs @@ -0,0 +1,51 @@ +using AmongUs.GameOptions; +using UnityEngine; + +namespace BetterAmongUs; + +// Code from: https://github.com/0xDrMoe/TownofHost-Enhanced + +public static class GameStates +{ + /**********Check Game Status***********/ + public static bool IsDev => Main.DevUser.Contains(Utils.GetHashPuid(EOSManager.Instance.ProductUserId)); + public static bool InGame => Main.AllPlayerControls.Any(); + public static bool IsNormalGame => GameOptionsManager.Instance.CurrentGameOptions.GameMode is GameModes.Normal or GameModes.NormalFools; + public static bool IsHideNSeek => GameOptionsManager.Instance.CurrentGameOptions.GameMode is GameModes.HideNSeek or GameModes.SeekFools; + public static bool SkeldIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Skeld; + public static bool MiraHQIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Mira; + public static bool PolusIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Polus; + public static bool DleksIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Dleks; + public static bool AirshipIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Airship; + public static bool FungleIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Fungle; + public static bool IsInGame => InGame; + public static bool IsLobby => AmongUsClient.Instance?.GameState == InnerNet.InnerNetClient.GameStates.Joined; + public static bool IsInIntro => IntroCutscene.Instance != null; + public static bool IsInGamePlay => ((InGame && !IsLobby && !IsInIntro) || IsFreePlay); + public static bool IsEnded => AmongUsClient.Instance?.GameState == InnerNet.InnerNetClient.GameStates.Ended; + public static bool IsNotJoined => AmongUsClient.Instance?.GameState == InnerNet.InnerNetClient.GameStates.NotJoined; + public static bool IsOnlineGame => AmongUsClient.Instance?.NetworkMode == NetworkModes.OnlineGame; + public static bool IsVanillaServer + { + get + { + if (!IsOnlineGame) return false; + + string region = ServerManager.Instance.CurrentRegion.Name; + return (region == "North America" || region == "Europe" || region == "Asia"); + } + } + public static bool IsLocalGame => AmongUsClient.Instance?.NetworkMode == NetworkModes.LocalGame; + public static bool IsFreePlay => AmongUsClient.Instance?.NetworkMode == NetworkModes.FreePlay; + public static bool IsInTask => InGame && MeetingHud.Instance == null; + public static bool IsMeeting => InGame && MeetingHud.Instance != null; + public static bool IsVoting => IsMeeting && MeetingHud.Instance?.state is MeetingHud.VoteStates.Voted or MeetingHud.VoteStates.NotVoted; + public static bool IsProceeding => IsMeeting && MeetingHud.Instance?.state is MeetingHud.VoteStates.Proceeding; + public static bool IsExilling => ExileController.Instance != null && !(AirshipIsActive && Minigame.Instance != null && Minigame.Instance.isActiveAndEnabled); + public static bool IsCountDown => GameStartManager.InstanceExists && GameStartManager.Instance.startState == GameStartManager.StartingStates.Countdown; + /**********TOP ZOOM.cs***********/ + public static bool IsShip => ShipStatus.Instance != null; + public static bool IsHost => AmongUsClient.Instance.AmHost; + public static bool IsCanMove => PlayerControl.LocalPlayer?.CanMove is true; + public static bool IsDead => PlayerControl.LocalPlayer?.Data?.IsDead is true; +} diff --git a/Modules/LateTask.cs b/Modules/LateTask.cs new file mode 100644 index 0000000..7321d00 --- /dev/null +++ b/Modules/LateTask.cs @@ -0,0 +1,55 @@ +// Code from: https://github.com/0xDrMoe/TownofHost-Enhanced + +using System.Linq; + +namespace BetterAmongUs; + +class LateTask +{ + public string name; + public float timer; + public bool shouldLog; + public Action action; + public static List Tasks = []; + public bool Run(float deltaTime) + { + timer -= deltaTime; + if (timer <= 0) + { + action(); + + if (shouldLog == true) + Logger.Log($"{name} has finished", "LateTask"); + return true; + } + return false; + } + public LateTask(Action action, float time, string name = "No Name Task", bool shoudLog = true) + { + this.action = action; + this.timer = time; + this.name = name; + this.shouldLog = shoudLog; + Tasks.Add(this); + } + public static void Update(float deltaTime) + { + var TasksToRemove = new List(); + foreach (var task in Tasks.ToArray()) + { + try + { + if (task.Run(deltaTime)) + { + TasksToRemove.Add(task); + } + } + catch (Exception ex) + { + TasksToRemove.Add(task); + Logger.Error(ex.ToString()); + } + } + TasksToRemove.ForEach(task => Tasks.Remove(task)); + } +} \ No newline at end of file diff --git a/Modules/RPC.cs b/Modules/RPC.cs new file mode 100644 index 0000000..c7541fd --- /dev/null +++ b/Modules/RPC.cs @@ -0,0 +1,161 @@ +using BetterAmongUs.Patches; +using HarmonyLib; +using Hazel; +using static UnityEngine.GraphicsBuffer; + +namespace BetterAmongUs; + +enum CustomRPC : int +{ + // Cheat RPC's + Sicko = 420, + AUM = 42069, + AUMChat = 101, + + // TOHE + VersionCheck = 80, + RequestRetryVersionCheck = 81, + + //Better Among Us + BetterCheck = 150, + AddChat, +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))] +internal class RPCHandlerPatch +{ + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader) + { + AntiCheat.HandleRPCBeforeCheck(__instance, callId, reader); + + if (AntiCheat.CheckCancelRPC(__instance, callId, reader) != true) + { + Logger.LogCheat($"RPC canceled by Anti-Cheat: {Enum.GetName((RpcCalls)callId)}{Enum.GetName((CustomRPC)callId)} - {callId}"); + return false; + } + + if (GameStates.IsHost && BetterHostManager.CheckRPCAsHost(__instance, callId, reader) != true) + { + Logger.LogCheat($"RPC canceled by Host: {Enum.GetName((RpcCalls)callId)}{Enum.GetName((CustomRPC)callId)} - {callId}"); + return false; + } + + return true; + } + public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader) + { + AntiCheat.CheckRPC(__instance, callId, reader); + RPC.HandleRPC(__instance, callId, reader); + } +} + +internal static class RPC +{ + public static void SetNamePrivate(PlayerControl player, string name, PlayerControl target) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetName, SendOption.Reliable, target.GetClientId()); + writer.Write(player.NetId); + writer.Write(name); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void SyncAllNames(bool isForMeeting = false, bool force = false, bool isBetterHost = true) + { + if (!GameStates.IsHost) return; + + foreach (PlayerControl player in Main.AllPlayerControls) + BetterHostManager.SetPlayersInfoAsHost(player, isForMeeting, force, isBetterHost); + } + + public static void ExileAsync(PlayerControl player) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.Reliable, -1); + AmongUsClient.Instance.FinishRpcImmediately(writer); + player.Exiled(); + } + + public static async Task WaitForChatCooldown() + { + while (3f - HudManager.Instance.Chat.timeSinceLastMessage > 0f) + { + await Task.Delay(100); + } + } + + public static async void SendHostChatToPlayer(PlayerControl player, string text, string? title = "", bool sendToBetterUser = true) + { + if (!GameStates.IsHost) return; + + if (player.GetIsBetterUser()) + { + if (sendToBetterUser) + { + MessageWriter messageWriter2 = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, unchecked((byte)CustomRPC.AddChat), SendOption.None, player.GetClientId()); + messageWriter2.Write(text); + messageWriter2.Write(title); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter2); + } + + return; + } + + return; + + await WaitForChatCooldown(); + + PlayerControl asPlayer = Main.AllPlayerControls.Where(pc => pc.IsAlive()).OrderBy(pc => pc == PlayerControl.LocalPlayer ? 0 : 1).First(); + + var oldName = asPlayer.CurrentOutfit.PlayerName; + if (title == "") + title = oldName; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(asPlayer.NetId, (byte)RpcCalls.SetName, SendOption.None, player.GetClientId()); + writer.Write(asPlayer.Data.NetId); + writer.Write(title); + AmongUsClient.Instance.FinishRpcImmediately(writer); + + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(asPlayer.NetId, (byte)RpcCalls.SendChat, SendOption.None, player.GetClientId()); + messageWriter.Write(text); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + + MessageWriter writer2 = AmongUsClient.Instance.StartRpcImmediately(asPlayer.NetId, (byte)RpcCalls.SetName, SendOption.None, player.GetClientId()); + writer2.Write(asPlayer.Data.NetId); + writer2.Write(oldName); + AmongUsClient.Instance.FinishRpcImmediately(writer2); + + SyncAllNames(); + } + + public static void HandleRPC(PlayerControl player, byte callId, MessageReader reader) + { + if (PlayerControl.LocalPlayer == null || player == null || player == PlayerControl.LocalPlayer || player.Data == null) return; + + switch (callId) + { + case (byte)CustomRPC.BetterCheck: + if (reader.ReadByte() == player.NetId) + player.SetIsBetterUser(true); + break; + case (byte)CustomRPC.AddChat: + Utils.AddChatPrivate(reader.ReadString(), reader.ReadString()); + break; + case (byte)CustomRPC.VersionCheck: + if (player.IsHost()) + { + var BAU = "♻BetterAmongUs♻"; + Utils.DisconnectSelf($"{BAU} does not support TOHE"); + } + break; + case (byte)RpcCalls.SendChat: + if (player.IsHost() && player != PlayerControl.LocalPlayer) + { + if (reader.ReadString().ToLower() == "/allow") + { + CommandsPatch.Permission = player; + } + } + break; + } + } +} + diff --git a/Modules/Utils.cs b/Modules/Utils.cs new file mode 100644 index 0000000..9b8a98b --- /dev/null +++ b/Modules/Utils.cs @@ -0,0 +1,229 @@ +using AmongUs.GameOptions; +using InnerNet; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using UnityEngine; + +namespace BetterAmongUs; + +public static class Utils +{ + // Get player by client id + public static ClientData? ClientFromId(int id) => AmongUsClient.Instance.allClients.ToArray().FirstOrDefault(cd => cd.Id == id) ?? null; + // Get player from player id + public static PlayerControl? PlayerFromId(int playerId) => Main.AllPlayerControls.FirstOrDefault(player => player.PlayerId == playerId) ?? null; + // Get player from client id + public static PlayerControl? PlayerFromClientId(int clientId) => Main.AllPlayerControls.FirstOrDefault(player => player.GetClientId() == clientId) ?? null; + // Get player from net id + public static PlayerControl? PlayerFromNetId(uint netId) => Main.AllPlayerControls.FirstOrDefault(player => player.NetId == netId) ?? null; + // Add msg to chat + public static void AddChatPrivate(string text, string overrideName = "", bool setRight = false) + { + ChatController chat = HudManager.Instance.Chat; + NetworkedPlayerInfo data = PlayerControl.LocalPlayer.Data; + ChatBubble pooledBubble = chat.GetPooledBubble(); + string MsgName = "(System Message)"; + if (overrideName != "") + MsgName = overrideName; + try + { + pooledBubble.transform.SetParent(chat.scroller.Inner); + pooledBubble.transform.localScale = Vector3.one; + pooledBubble.Background.color = new Color(0.05f, 0.05f, 0.05f, 1f); + pooledBubble.SetCosmetics(data); + pooledBubble.gameObject.transform.Find("PoolablePlayer").gameObject.SetActive(false); + pooledBubble.ColorBlindName.gameObject.SetActive(false); + if (!setRight) + { + pooledBubble.SetLeft(); + pooledBubble.gameObject.transform.Find("NameText (TMP)").transform.localPosition += new Vector3(-0.7f, 0f); + pooledBubble.gameObject.transform.Find("ChatText (TMP)").transform.localPosition += new Vector3(-0.7f, 0f); + } + else + { + pooledBubble.SetRight(); + } + chat.SetChatBubbleName(pooledBubble, data, data.IsDead, false, PlayerNameColor.Get(data), null); + pooledBubble.SetText(text); + pooledBubble.AlignChildren(); + chat.AlignAllBubbles(); + pooledBubble.NameText.text = MsgName; + if (!chat.IsOpenOrOpening && chat.notificationRoutine == null) + { + chat.notificationRoutine = chat.StartCoroutine(chat.BounceDot()); + } + SoundManager.Instance.PlaySound(chat.messageSound, false, 1f, null).pitch = 0.5f + (float)data.PlayerId / 15f; + } + catch (Exception ex) + { + chat.chatBubblePool.Reclaim(pooledBubble); + Logger.Error(ex.ToString()); + throw; + } + } + // Get players HashPuid + public static string GetHashPuid(PlayerControl player) + { + if (player?.Data?.Puid == null) return ""; + + string puid = player.Data.Puid; + + using SHA256 sha256 = SHA256.Create(); + byte[] sha256Bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(puid)); + string sha256Hash = BitConverter.ToString(sha256Bytes).Replace("-", "").ToLower(); + return sha256Hash.Substring(0, 5) + sha256Hash.Substring(sha256Hash.Length - 4); + } + // Get HashPuid from puid + public static string GetHashPuid(string puid) + { + using SHA256 sha256 = SHA256.Create(); + byte[] sha256Bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(puid)); + string sha256Hash = BitConverter.ToString(sha256Bytes).Replace("-", "").ToLower(); + return sha256Hash.Substring(0, 5) + sha256Hash.Substring(sha256Hash.Length - 4); + } + // Remove Html Tags Template + public static string RemoveHtmlTagsTemplate(string str) => Regex.Replace(str, "", ""); + // Get raw text + public static string GetRawText(string text) + { + text = Regex.Replace(text, "<[^>]*>", ""); + text = Regex.Replace(text, "{[^}]*}", ""); + return text; + } + // Get name for role + public static string GetRoleName(RoleTypes role) => Main.GetRoleName[(int)role]; + public static string Color32ToHex(Color32 color) => $"#{color.r:X2}{color.g:X2}{color.b:X2}{255:X2}"; + // Disconnect client + public static void DisconnectSelf(string reason) + { + AmongUsClient.Instance.ExitGame(0); + _ = new LateTask(() => + { + SceneChanger.ChangeScene("MainMenu"); + _ = new LateTask(() => + { + var lines = "----------------------------------------------------------------------------------------------"; + DisconnectPopup.Instance._textArea.enableWordWrapping = false; + DisconnectPopup.Instance.ShowCustom($"{lines}\n\n\n{reason}\n\n\n{lines}"); + }, 0.1f, "DisconnectSelf 2"); + }, 0.2f, "DisconnectSelf 1"); + } + + public static Dictionary CachedSprites = []; + + public static Sprite? LoadSprite(string path, float pixelsPerUnit = 1f) + { + try + { + if (CachedSprites.TryGetValue(path + pixelsPerUnit, out var sprite)) + return sprite; + + var texture = LoadTextureFromResources(path); + if (texture == null) + return null; + + sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), pixelsPerUnit); + sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontSaveInEditor; + + Logger.Log($"Successfully loaded sprite from {path}"); + return CachedSprites[path + pixelsPerUnit] = sprite; + } + catch (Exception ex) + { + Logger.Error(ex.ToString()); + return null; + } + } + + public static Texture2D? LoadTextureFromResources(string path) + { + try + { + var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); + if (stream == null) + return null; + + var texture = new Texture2D(1, 1, TextureFormat.ARGB32, false); + using (var ms = new MemoryStream()) + { + stream.CopyTo(ms); + if (!ImageConversion.LoadImage(texture, ms.ToArray(), false)) + return null; + } + + Logger.Log($"Successfully loaded texture from {path}"); + return texture; + } + catch (Exception ex) + { + Logger.Error(ex.ToString()); + return null; + } + } + + // Get platform name + public static string GetPlatformName(PlayerControl player, bool useTag = false) + { + if (player == null) return string.Empty; + if (player.GetClient() == null) return string.Empty; + + string PlatformName = string.Empty; + string Tag = string.Empty; + + Platforms platform = player.GetClient().PlatformData.Platform; + + switch (platform) + { + case Platforms.StandaloneSteamPC: + PlatformName = "Steam"; + Tag = "PC"; + break; + case Platforms.StandaloneEpicPC: + PlatformName = "Epic Games"; + Tag = "PC"; + break; + case Platforms.StandaloneWin10: + PlatformName = "Microsoft Store"; + Tag = "PC"; + break; + case Platforms.StandaloneMac: + PlatformName = "Mac OS"; + Tag = "PC"; + break; + case Platforms.StandaloneItch: + PlatformName = "Itch.io"; + Tag = "PC"; + break; + case Platforms.Xbox: + PlatformName = "Xbox"; + Tag = "Console"; + break; + case Platforms.Playstation: + PlatformName = "Playstation"; + Tag = "Console"; + break; + case Platforms.Switch: + PlatformName = "Switch"; + Tag = "Console"; + break; + case Platforms.Android: + PlatformName = "Android"; + Tag = "Mobile"; + break; + case Platforms.IPhone: + PlatformName = "IPhone"; + Tag = "Mobile"; + break; + case Platforms.Unknown: + PlatformName = "None"; + break; + } + + if (useTag == false) + return PlatformName; + + return $"{Tag}: {PlatformName}"; + } +} \ No newline at end of file diff --git a/Modules/Zoom.cs b/Modules/Zoom.cs new file mode 100644 index 0000000..e3bab1c --- /dev/null +++ b/Modules/Zoom.cs @@ -0,0 +1,96 @@ +using HarmonyLib; +using UnityEngine; + +namespace BetterAmongUs; + +// Code from: https://github.com/0xDrMoe/TownofHost-Enhanced +[HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] +public static class Zoom +{ + private static bool resetButtons = false; + + public static void Postfix() + { + if ((GameStates.IsShip && !GameStates.IsMeeting && GameStates.IsCanMove && PlayerControl.LocalPlayer.Data.IsDead) || + (GameStates.IsLobby && GameStates.IsCanMove)) + { + if (Camera.main.orthographicSize > 3.0f) + resetButtons = true; + + if (Input.mouseScrollDelta.y > 0 && Camera.main.orthographicSize > 3.0f) + { + SetZoomSize(times: false); + } + else if (Input.mouseScrollDelta.y < 0 && (GameStates.IsDead || GameStates.IsFreePlay || GameStates.IsLobby) && + Camera.main.orthographicSize < 18.0f) + { + SetZoomSize(times: true); + } + + Flag.NewFlag("Zoom"); + } + else + { + Flag.Run(() => SetZoomSize(reset: true), "Zoom"); + } + } + + private static void SetZoomSize(bool times = false, bool reset = false) + { + float size = times ? 1.5f : 1 / 1.5f; + + if (reset) + { + Camera.main.orthographicSize = 3.0f; + HudManager.Instance.UICamera.orthographicSize = 3.0f; + HudManager.Instance.Chat.transform.localScale = Vector3.one; + if (GameStates.IsMeeting) + MeetingHud.Instance.transform.localScale = Vector3.one; + } + else + { + Camera.main.orthographicSize *= size; + HudManager.Instance.UICamera.orthographicSize *= size; + } + + DestroyableSingleton.Instance?.ShadowQuad?.gameObject?.SetActive((reset || Camera.main.orthographicSize == 3.0f) && PlayerControl.LocalPlayer.IsAlive()); + + if (resetButtons) + { + ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen); + resetButtons = false; + } + } + + public static void OnFixedUpdate() => + DestroyableSingleton.Instance?.ShadowQuad?.gameObject?.SetActive((Camera.main.orthographicSize == 3.0f) && PlayerControl.LocalPlayer.IsAlive()); +} + +public static class Flag +{ + private static readonly List oneTimeList = new(); + private static readonly List firstRunList = new(); + + public static void Run(Action action, string type, bool firstrun = false) + { + if (oneTimeList.Contains(type) || (firstrun && !firstRunList.Contains(type))) + { + if (!firstRunList.Contains(type)) + firstRunList.Add(type); + + oneTimeList.Remove(type); + action(); + } + } + + public static void NewFlag(string type) + { + if (!oneTimeList.Contains(type)) + oneTimeList.Add(type); + } + + public static void DeleteFlag(string type) + { + oneTimeList.Remove(type); + } +} \ No newline at end of file diff --git a/Patches/ButtonsPatch.cs b/Patches/ButtonsPatch.cs new file mode 100644 index 0000000..9a5bbda --- /dev/null +++ b/Patches/ButtonsPatch.cs @@ -0,0 +1,48 @@ +using BetterAmongUs.Role; +using HarmonyLib; +using UnityEngine; + +namespace BetterAmongUs.Patches; + +internal class ButtonsPatch +{ + // Handle EFC + [HarmonyPatch(typeof(SabotageButton))] + internal class SabotageButtonPatch + { + [HarmonyPatch(nameof(SabotageButton.DoClick))] + [HarmonyPrefix] + public static bool DoClick_Prefix(SabotageButton __instance) + { + if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && GameManager.Instance.SabotagesEnabled()) + { + DestroyableSingleton.Instance.ToggleMapVisible(new MapOptions + { + Mode = MapOptions.Modes.Sabotage + }); + } + + return false; + } + [HarmonyPatch(nameof(SabotageButton.Refresh))] + [HarmonyPrefix] + public static bool Refresh_Prefix(SabotageButton __instance) + { + if (GameManager.Instance == null || PlayerControl.LocalPlayer == null) + { + __instance.ToggleVisible(false); + __instance.SetDisabled(); + return false; + } + if (!GameManager.Instance.SabotagesEnabled() || PlayerControl.LocalPlayer.petting) + { + __instance.ToggleVisible(PlayerControl.LocalPlayer.Data.Role.IsImpostor && GameManager.Instance.SabotagesEnabled()); + __instance.SetDisabled(); + return false; + } + __instance.SetEnabled(); + + return false; + } + } +} diff --git a/Patches/ChatNotificationPatch.cs b/Patches/ChatNotificationPatch.cs new file mode 100644 index 0000000..e6fb7fa --- /dev/null +++ b/Patches/ChatNotificationPatch.cs @@ -0,0 +1,40 @@ +using HarmonyLib; +using TMPro; +using UnityEngine; + +namespace BetterAmongUs.Patches; + +[HarmonyPatch(typeof(ChatNotification))] +public class ChatNotificationPatch +{ + [HarmonyPatch(nameof(ChatNotification.Awake))] + [HarmonyPostfix] + public static void Awake_Postfix(ChatNotification __instance) + { + if (__instance.gameObject.name == "ChatNotification" && BAUNotificationManager.BAUNotificationManagerObj == null) + { + GameObject ChatNotifications = __instance.gameObject; + if (ChatNotifications != null) + { + GameObject BAUNotification = UnityEngine.Object.Instantiate(ChatNotifications); + BAUNotification.name = "BAUNotification"; + UnityEngine.Object.Destroy(BAUNotification.GetComponent()); + UnityEngine.Object.Destroy(GameObject.Find($"{BAUNotification.name}/Sizer/PoolablePlayer")); + UnityEngine.Object.Destroy(GameObject.Find($"{BAUNotification.name}/Sizer/ColorText")); + BAUNotification.GetComponent().DistanceFromEdge = new Vector3(-1.57f, 5.3f, -15f); + GameObject.Find($"{BAUNotification.name}/Sizer/NameText").transform.localPosition = new Vector3(-3.3192f, -0.0105f); + GameObject.Find($"{BAUNotification.name}/Sizer/NameText").GetComponent().text = "System Notification"; + UnityEngine.Object.DontDestroyOnLoad(BAUNotification); + BAUNotificationManager.BAUNotificationManagerObj = BAUNotification; + BAUNotification.SetActive(false); + } + } + } + [HarmonyPatch(nameof(ChatNotification.Update))] + [HarmonyPostfix] + public static void Update_Postfix(ChatNotification __instance) + { + __instance.GetComponent().DistanceFromEdge = new Vector3(-2.8f, 0.3f, -40f); + __instance.transform.localScale = new Vector3(0.45f, 0.42f); + } +} diff --git a/Patches/ChatPatch.cs b/Patches/ChatPatch.cs new file mode 100644 index 0000000..b1d3447 --- /dev/null +++ b/Patches/ChatPatch.cs @@ -0,0 +1,229 @@ +using AmongUs.Data; +using HarmonyLib; +using System.Text; +using TMPro; +using UnityEngine; + +namespace BetterAmongUs.Patches; + +// History and dark mode from: https://github.com/0xDrMoe/TownofHost-Enhanced + +class ChatPatch +{ + public static List ChatHistory = []; + public static int CurrentHistorySelection = -1; + + [HarmonyPatch(typeof(ChatController))] + class ChatControllerPatch + { + [HarmonyPatch(nameof(ChatController.Update))] + [HarmonyPostfix] + public static void Update_Postfix(ChatController __instance) + { + for (int i = 0; i < __instance.scroller.Inner.gameObject.transform.childCount; i++) + { + GameObject chatItem = __instance.scroller.Inner.transform.GetChild(i).gameObject; + + chatItem.transform.Find("Background").GetComponent().color = new Color(0.05f, 0.05f, 0.05f, 1f); + chatItem.transform.Find("ChatText (TMP)").GetComponent().color = new Color(1f, 1f, 1f, 1f); + } + + // Free chat color + __instance.freeChatField.background.color = new Color32(40, 40, 40, byte.MaxValue); + __instance.freeChatField.textArea.compoText.Color(Color.white); + __instance.freeChatField.textArea.outputText.color = Color.white; + + // Quick chat color + __instance.quickChatField.background.color = new Color32(40, 40, 40, byte.MaxValue); + __instance.quickChatField.text.color = Color.white; + + if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.X)) + { + ClipboardHelper.PutClipboardString(__instance.freeChatField.textArea.text); + __instance.freeChatField.textArea.SetText(""); + } + + if (Input.GetKeyDown(KeyCode.UpArrow) && ChatHistory.Any()) + { + CurrentHistorySelection = Mathf.Clamp(--CurrentHistorySelection, 0, ChatHistory.Count - 1); + __instance.freeChatField.textArea.SetText(ChatHistory[CurrentHistorySelection]); + } + + if (Input.GetKeyDown(KeyCode.DownArrow) && ChatHistory.Any()) + { + CurrentHistorySelection++; + if (CurrentHistorySelection < ChatHistory.Count) + __instance.freeChatField.textArea.SetText(ChatHistory[CurrentHistorySelection]); + else __instance.freeChatField.textArea.SetText(""); + } + } + + [HarmonyPatch(nameof(ChatController.AddChat))] + [HarmonyPrefix] + public static bool AddChat_Prefix(ChatController __instance, [HarmonyArgument(0)] PlayerControl sourcePlayer, [HarmonyArgument(1)] string chatText) + { + bool censor = false; + if (!sourcePlayer || !PlayerControl.LocalPlayer) + { + return false; + } + NetworkedPlayerInfo data = PlayerControl.LocalPlayer.Data; + NetworkedPlayerInfo data2 = sourcePlayer.Data; + if (data2 == null || data == null || (data2.IsDead && !data.IsDead)) + { + return false; + } + ChatBubble pooledBubble = __instance.GetPooledBubble(); + try + { + pooledBubble.transform.SetParent(__instance.scroller.Inner); + pooledBubble.transform.localScale = Vector3.one; + bool flag = sourcePlayer == PlayerControl.LocalPlayer; + if (flag) + { + pooledBubble.SetRight(); + } + else + { + pooledBubble.SetLeft(); + } + bool didVote = MeetingHud.Instance && MeetingHud.Instance.DidVote(sourcePlayer.PlayerId); + pooledBubble.Background.color = new Color(0.05f, 0.05f, 0.05f, 1f); + pooledBubble.SetCosmetics(data2); + __instance.SetChatBubbleName(pooledBubble, data2, data2.IsDead, didVote, PlayerNameColor.Get(data2), null); + if (censor && DataManager.Settings.Multiplayer.CensorChat) + { + chatText = BlockedWords.CensorWords(chatText, false); + } + pooledBubble.SetText(chatText); + pooledBubble.AlignChildren(); + __instance.AlignAllBubbles(); + if (!__instance.IsOpenOrOpening && __instance.notificationRoutine == null) + { + __instance.notificationRoutine = __instance.StartCoroutine(__instance.BounceDot()); + } + if (!flag) + { + SoundManager.Instance.PlaySound(__instance.messageSound, false, 1f, null).pitch = 0.5f + (float)sourcePlayer.PlayerId / 15f; + __instance.chatNotification.SetUp(sourcePlayer, chatText); + } + + StringBuilder sbTag = new StringBuilder(); + StringBuilder sbInfo = new StringBuilder(); + + string hashPuid = Utils.GetHashPuid(sourcePlayer); + string friendCode = sourcePlayer.Data.FriendCode; + string playerName = sourcePlayer.Data.PlayerName; + string RoleHexColor = sourcePlayer.IsImpostorTeam() ? "#ff1919" : "#8cffff"; + string Role = $"{sourcePlayer.GetRoleName()}+++"; + + if (GameStates.IsLobby && !GameStates.IsFreePlay) + { + Role = ""; + + if (sourcePlayer.IsDev()) + sbTag.Append("Dev+++"); + + if ((sourcePlayer == PlayerControl.LocalPlayer || sourcePlayer.GetIsBetterUser() == true) && GameStates.IsLobby && !GameStates.IsFreePlay) + sbTag.Append("Better User+++"); + + if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.SickoData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.SickoData.ContainsValue(friendCode)) + sbTag.Append("Sicko User+++"); + else if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.AUMData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.AUMData.ContainsValue(friendCode)) + sbTag.Append("AUM User+++"); + else if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.PlayerData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.PlayerData.ContainsValue(friendCode)) + sbTag.Append("Known Cheater+++"); + } + + if (!sourcePlayer.IsImpostorTeammate()) + { + if (PlayerControl.LocalPlayer.IsAlive() && sourcePlayer != PlayerControl.LocalPlayer) + { + Role = ""; + } + } + + sbTag.Append(Role); + + sbInfo.Append(""); + for (int i = 0; i < sbTag.ToString().Split("+++").Length; i++) + { + if (!string.IsNullOrEmpty(sbTag.ToString().Split("+++")[i])) + { + if (i < sbTag.ToString().Split("+++").Length) + { + sbInfo.Append(sbTag.ToString().Split("+++")[i]); + } + if (i != sbTag.ToString().Split("+++").Length - 2) + { + sbInfo.Append(" - "); + } + } + } + sbInfo.Append(""); + + if (flag) + { + playerName = $"{sbInfo} " + playerName; + } + else + { + playerName += $" {sbInfo}"; + } + + pooledBubble.NameText.SetText(playerName); + } + catch + { + } + + return false; + } + } + + [HarmonyPatch(typeof(FreeChatInputField))] + class FreeChatInputFieldPatch + { + [HarmonyPatch(nameof(FreeChatInputField.Awake))] + [HarmonyPostfix] + public static void Awake_Postfix(FreeChatInputField __instance) + { + __instance.textArea.allowAllCharacters = true; + __instance.textArea.AllowSymbols = true; + __instance.textArea.AllowPaste = true; + __instance.textArea.AllowEmail = true; + __instance.textArea.characterLimit = 120; + __instance.charCountText.text = "0/120"; + } + [HarmonyPatch(nameof(FreeChatInputField.UpdateCharCount))] + [HarmonyPostfix] + public static void UpdateCharCount_Postfix(FreeChatInputField __instance) + { + int length = __instance.textArea.text.Length; + __instance.charCountText.text = string.Format("{0}/120", length); + __instance.charCountText.color = GetCharColor(length, UnityEngine.Color.white); + } + } + + private static UnityEngine.Color GetCharColor(int length, UnityEngine.Color color) + { + + switch (length) + { + case int n when n > 119: + if (ColorUtility.TryParseHtmlString("#ff0000", out UnityEngine.Color newColor1)) + color = newColor1; + break; + case int n when n > 74: + if (ColorUtility.TryParseHtmlString("#ffff00", out UnityEngine.Color newColor3)) + color = newColor3; + break; + default: + if (ColorUtility.TryParseHtmlString("#00f04c", out UnityEngine.Color newColor4)) + color = newColor4; + break; + } + + return color; + } +} diff --git a/Patches/ClientOptionsPatch.cs b/Patches/ClientOptionsPatch.cs new file mode 100644 index 0000000..e21f26a --- /dev/null +++ b/Patches/ClientOptionsPatch.cs @@ -0,0 +1,106 @@ +using HarmonyLib; +using UnityEngine; + +namespace BetterAmongUs.Patches; + +// Code from: https://github.com/tukasa0001/TownOfHost/pull/1265 +// Code from: https://github.com/0xDrMoe/TownofHost-Enhanced +[HarmonyPatch(typeof(OptionsMenuBehaviour))] +public static class OptionsMenuBehaviourPatch +{ + private static ClientOptionItem AntiCheat; + private static ClientOptionItem BetterHost; + private static ClientOptionItem BetterRoleAlgorithma; + private static ClientOptionItem LobbyPlayerInfo; + private static ClientOptionItem DisableLobbyTheme; + private static ClientOptionItem UnlockFPS; + private static ClientOptionItem ShowFPS; + private static ClientOptionItem OpenSaveData; + + [HarmonyPatch(nameof(OptionsMenuBehaviour.Start))] + [HarmonyPrefix] + public static void Start_Postfix(OptionsMenuBehaviour __instance) + { + static bool toggleCheckInGame(string buttonName) + { + bool flag = GameStates.IsInGame && !GameStates.IsLobby || GameStates.IsFreePlay; + if (flag) + BAUNotificationManager.Notify($"Unable to toggle {buttonName} while in game!", 2.5f); + + return flag; + } + + if (__instance.DisableMouseMovement == null) return; + + if (AntiCheat == null || AntiCheat.ToggleButton == null) + { + AntiCheat = ClientOptionItem.Create("Anti-Cheat", Main.AntiCheat, __instance); + } + + if (BetterHost == null || BetterHost.ToggleButton == null) + { + BetterHost = ClientOptionItem.Create("Better Host", Main.BetterHost, __instance, BetterHostButtonToggle, () => !toggleCheckInGame("Better Host")); + static void BetterHostButtonToggle() => RPC.SyncAllNames(force: true); + } + + if (BetterRoleAlgorithma == null || BetterRoleAlgorithma.ToggleButton == null) + { + BetterRoleAlgorithma = ClientOptionItem.Create("Better Role Algorithma", Main.BetterRoleAlgorithma, __instance, toggleCheck: () => !toggleCheckInGame("Better Role Algorithma")); + } + + if (LobbyPlayerInfo == null || LobbyPlayerInfo.ToggleButton == null) + { + LobbyPlayerInfo = ClientOptionItem.Create("Show Lobby Info", Main.LobbyPlayerInfo, __instance); + } + + if (DisableLobbyTheme == null || DisableLobbyTheme.ToggleButton == null) + { + DisableLobbyTheme = ClientOptionItem.Create("Disable Lobby Theme", Main.DisableLobbyTheme, __instance, DisableLobbyThemeButtonToggle); + static void DisableLobbyThemeButtonToggle() + { + if (GameStates.IsLobby && !Main.DisableLobbyTheme.Value) + { + SoundManager.instance.CrossFadeSound("MapTheme", LobbyBehaviour.Instance.MapTheme, 0.5f, 1.5f); + } + } + } + + if (UnlockFPS == null || UnlockFPS.ToggleButton == null) + { + UnlockFPS = ClientOptionItem.Create("UnlockFPS", Main.UnlockFPS, __instance, UnlockFPSButtonToggle); + static void UnlockFPSButtonToggle() + { + Application.targetFrameRate = Main.UnlockFPS.Value ? 165 : 60; + } + } + + if (ShowFPS == null || ShowFPS.ToggleButton == null) + { + ShowFPS = ClientOptionItem.Create("ShowFPS", Main.ShowFPS, __instance); + } + + if (OpenSaveData == null || OpenSaveData.ToggleButton == null) + { + DisableLobbyTheme = ClientOptionItem.Create("Open Save Data", null, __instance, OpenSaveDataButtonToggle, IsToggle: false); + static void OpenSaveDataButtonToggle() + { + if (File.Exists(BAUDataManager.GetFilePath("BetterData"))) + { + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo() + { + FileName = BAUDataManager.GetFilePath("BetterData"), + UseShellExecute = true, + Verb = "open" + }); + } + } + } + } + + [HarmonyPatch(nameof(OptionsMenuBehaviour.Close))] + [HarmonyPrefix] + public static void Close_Postfix() + { + ClientOptionItem.CustomBackground?.gameObject.SetActive(false); + } +} \ No newline at end of file diff --git a/Patches/ClientPatch.cs b/Patches/ClientPatch.cs new file mode 100644 index 0000000..f8da436 --- /dev/null +++ b/Patches/ClientPatch.cs @@ -0,0 +1,28 @@ +using BepInEx.Logging; +using Cpp2IL.Core.Logging; +using GooglePlayGames.OurUtils; +using HarmonyLib; +using UnityEngine; + +namespace BetterAmongUs.Patches; + +public class ClientPatch +{ + [HarmonyPatch(typeof(CosmeticsLayer))] + public class CosmeticsLayerPatch + { + [HarmonyPatch(nameof(CosmeticsLayer.SetColorBlindColor))] + [HarmonyPrefix] + public static bool SetColorBlindColor_Prefix(CosmeticsLayer __instance, [HarmonyArgument(0)] int color) + { + if (__instance.colorBlindText == null || !__instance.showColorBlindText) + { + return false; + } + __instance.colorBlindText.text = __instance.GetColorBlindText(); + __instance.colorBlindText.color = Palette.PlayerColors[color]; + __instance.colorBlindText.transform.localPosition = new Vector3(0f, -1.5f, 0.4999f); + return false; + } + } +} diff --git a/Patches/CommandsPatch.cs b/Patches/CommandsPatch.cs new file mode 100644 index 0000000..98ce0bd --- /dev/null +++ b/Patches/CommandsPatch.cs @@ -0,0 +1,623 @@ +using AmongUs.Data; +using AmongUs.GameOptions; +using BetterAmongUs.Role; +using HarmonyLib; +using Hazel; +using Il2CppSystem.Text; +using TMPro; +using UnityEngine; + +namespace BetterAmongUs.Patches; + +[HarmonyPatch(typeof(ChatController))] +class CommandsPatch +{ + private static PlayerControl? cmdTarget = null; + private static bool HasPermission = Permission != null; + public static PlayerControl? Permission = null; + + // List of helper text when a command is being typed out + // First word is command, {} are arguments, first --- is command description, second --- is for help command. + public static string[] CommandListHelper = + { + "help---Get help with commands", + "commands---Get a list of all available commands", + "dump---Dump at the entire log to the user's desktop", + "player {id}---Get a Players information---{player id}", + "players---Get all Player information", + "whisper {id} {msg}---Privately send an message to a player---{player id}", + "w {id} {msg}---Privately send an message to a player---{player id} {text}", + "setprefix {prefix}---Set command prefix", + "name {name}---Set player name - Host Only/Host Permission---{text}", + "kick {id}---Kick a player from the game - Host Only---{player id}", + "ban {id}---Ban a player from the game - Host Only---{player id}", + "endgame {reason}---Force end the game - Host Only---{player id}", + }; + public static string[] DebugCommandListHelper = + { + "getposition---Get current player position", + "role {role}---Set your role for the next game - Host Only - DeBug", + "setrole {id} {role}---Set another players role for the next game - Host Only - DeBug", + "syncallnames---Sync all players names for better host - Host Only - DeBug", + }; + + + // Run code for specific commands + private static void HandleCommand(ChatController __instance, string[] command) + { + bool checkDebugCommand = false; + string subArgs = command.Length > 1 ? command[1].ToLower().Trim() : ""; + string subArgs2 = command.Length > 2 ? command[2].ToLower().Trim() : ""; + bool flag = subArgs.Length > 0; + bool flag2 = subArgs2.Length > 0; + string error = "Error:"; + StringBuilder sb = new StringBuilder(); + + // Commands + switch (command[0][1..].ToLower().Trim()) + { + case "help": + Utils.AddChatPrivate( + "Welcome to ♻BetterAmongUs♻ This mod enhances your gameplay experience with a variety of exciting features.\n" + + "Explore the pause menu to access more options and better settings tailored to your needs.\n" + + "For a full list of available commands, use the `/commands` command.\n\n" + + "Our features include: \n" + + "- Built-in Client-Sided Anti-Cheat: Enjoy a fair game with our anti-cheat system that detects and prevents unauthorized actions.\n" + + "- Host Enhancements: Gain additional control as a host with improved options and settings.\n" + + "- Better Options: Customize your game with a range of new and improved settings.\n" + + "- Commands: Utilize a variety of commands to manage and enhance your gameplay.\n" + + "- Client Improvements: Experience smoother and more efficient gameplay with our client-side enhancements.\n" + + "Stay tuned for more exciting features and improvements coming your way!" + ); + break; + case "commands": + string[] allCommands = CommandListHelper; + string[] allDebugCommands = DebugCommandListHelper; + string list; + var open = "┌──────── "; + var mid = "├ "; + var close = "└──────── "; + list = "Command List\n" + open; + for (int i = 0; i < allCommands.Length; i++) + { + if (i < allCommands.Length) + { + list += $"\n{mid}{Main.CommandPrefix.Value}{allCommands[i].Split(' ')[0].Split("---")[0]} {allCommands[i].Split("---")[1]}."; + } + } + if (GameStates.IsDev) + { + list += "\n" + close + "\n"; + list += "Debug Command List\n" + open; + for (int i = 0; i < allDebugCommands.Length; i++) + { + if (i < allDebugCommands.Length) + { + list += $"\n{mid}{Main.CommandPrefix.Value}{allDebugCommands[i].Split(' ')[0].Split("---")[0]} {allDebugCommands[i].Split("---")[1]}."; + } + } + } + list += "\n" + close; + Utils.AddChatPrivate(list); + break; + case "dump": + if (GameStates.IsInGamePlay && !GameStates.IsDev) return; + + string logFilePath = Path.Combine(Environment.CurrentDirectory, "better-log.txt"); + string log = File.ReadAllText(logFilePath); + string newLog = string.Empty; + string[] logArray = log.Split(new[] { Environment.NewLine }, StringSplitOptions.None); + foreach (string text in logArray) + { + if (text.Contains("[PrivateLog]")) + { + newLog += text.Split(':')[0] + ":" + text.Split(':')[1].Replace("[PrivateLog]", "") + ": " + Encryptor.Decrypt(text.Split(':')[2][1..]) + "\n"; + } + else + { + newLog += text + "\n"; + } + } + + string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); + string logFolderPath = Path.Combine(desktopPath, "BetterAmongUsLog"); + if (!Directory.Exists(logFolderPath)) + { + Directory.CreateDirectory(logFolderPath); + } + string logFileName = "log-" + DateTime.Now.ToString("yyyy.MM.dd-HH.mm.ss") + ".log"; + string newLogFilePath = Path.Combine(logFolderPath, logFileName); + File.WriteAllText(newLogFilePath, newLog); + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo() + { + FileName = logFolderPath, + UseShellExecute = true, + Verb = "open" + }); + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo() + { + FileName = newLogFilePath, + UseShellExecute = true, + Verb = "open" + }); + break; + case "player": + if (HandlePlayerArgument(command, subArgs) == true) + { + var player = cmdTarget; + var hexColor = Utils.Color32ToHex(Palette.PlayerColors[player.CurrentOutfit.ColorId]); + var format1 = "┌ •"; + var format2 = "├ •"; + var format3 = "└ •"; + sb.Append($"{player?.Data?.PlayerName} Info:\n"); + sb.Append($"{format1} ID: {player?.Data?.PlayerId}\n"); + sb.Append($"{format2} HashPUID: {Utils.GetHashPuid($"{player?.Data?.Puid}")}\n"); + sb.Append($"{format2} Platform: {Utils.GetPlatformName(player)}\n"); + sb.Append($"{format3} FriendCode: {player?.Data?.FriendCode}"); + Utils.AddChatPrivate(sb.ToString()); + } + break; + case "players": + foreach (PlayerControl player in Main.AllPlayerControls.Where(player => !player.isDummy)) + { + var hexColor = Utils.Color32ToHex(Palette.PlayerColors[player.CurrentOutfit.ColorId]); + sb.Append($"{player?.Data?.PlayerName} Info:\n"); + sb.Append($"{player?.Data?.PlayerId} - "); + sb.Append($"{Utils.GetHashPuid($"{player?.Data?.Puid}")} - "); + sb.Append($"{Utils.GetPlatformName(player)} - "); + sb.Append($"{player?.Data?.FriendCode}"); + sb.Append("\n\n"); + } + Utils.AddChatPrivate(sb.ToString()); + break; + case "whisper" or "w": + if (HandlePlayerArgument(command, subArgs) == true) + { + if (UseCommandInGame() == true) + { + var player = cmdTarget; + var hexColor = Utils.Color32ToHex(Palette.PlayerColors[player.CurrentOutfit.ColorId]); + + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)RpcCalls.SendChat, SendOption.None, player.GetClientId()); + messageWriter.Write(string.Join(" ", command[2..].ToArray())); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + Utils.AddChatPrivate($"{string.Join(" ", command[2..].ToArray())}", overrideName: $"Sent Private Message to {player.Data.PlayerName}"); + } + } + break; + case "setprefix": + Main.CommandPrefix.Value = subArgs; + break; + case "name": + if (GameStates.IsHost) + { + PlayerControl.LocalPlayer.RpcSetName(command[1]); + _ = new LateTask(() => + { + RPC.SyncAllNames(); + }, 1f, $"Command {Main.CommandPrefix}name"); + } + else if (command.Length > 1 && !string.IsNullOrWhiteSpace(command[1]) && System.Text.RegularExpressions.Regex.IsMatch(command[1], @"^[a-zA-Z0-9]+$")) + { + if (HandleHasPermission(command) == true) + { + PlayerControl.LocalPlayer.CmdCheckName(command[1]); + } + } + else + { + Utils.AddChatPrivate($"{error}\nInvalid Name!"); + return; + } + + PlayerControl.LocalPlayer.Data.PlayerName = command[1]; + DataManager.Player.customization.Name = command[1]; + Utils.AddChatPrivate($"Player name has been sent to: {command[1]}"); + break; + case "kick" or "ban": + if (HandlePlayerArgument(command, subArgs) == true) + { + if (HandleIsHost(command) == true) + { + var player = cmdTarget; + var cmdFlag = command[0][1..].ToLower().Trim() != "kick"; + AmongUsClient.Instance.KickPlayer(player.GetClientId(), cmdFlag); + } + } + break; + case "endgame": + if (HandleIsHost(command) == true) + { + if (!GameStates.IsLobby && !GameStates.IsFreePlay) + { + if (subArgs == "") + { + foreach (PlayerControl player in Main.AllPlayerControls) + { + player.roleAssigned = false; + player.RpcSetRole(RoleTypes.Crewmate, true); + } + GameManager.Instance.RpcEndGame(GameOverReason.ImpostorByKill, false); + return; + } + else if (subArgs is "impostor" or "1") + { + GameManager.Instance.RpcEndGame(GameOverReason.ImpostorByKill, false); + } + else if (subArgs is "crewmate" or "2") + { + GameManager.Instance.RpcEndGame(GameOverReason.HumansByTask, false); + } + } + } + break; + default: + if (GameStates.IsDev) + { + checkDebugCommand = true; + break; + } + + Utils.AddChatPrivate("Invalid Command!"); + break; + } + + // DeBug Commands + if (checkDebugCommand) + { + switch (command[0][1..].ToLower().Trim()) + { + case "getposition": + Utils.AddChatPrivate($"Current position: {PlayerControl.LocalPlayer.GetTruePosition()}"); + break; + case "role" or "setrole": + if (HandleIsHost(command) == true) + { + if (!Main.BetterRoleAlgorithma.Value) + { + Utils.AddChatPrivate($"{error}\nBetter Role Algorithma in better options must be turned on to use this command"); + return; + } + if (command[0][1..].ToLower().Trim() == "role") + { + var player = PlayerControl.LocalPlayer; + if (subArgs == "") + { + Utils.AddChatPrivate($"Set role to Random for the next game!"); + RoleManagerPatch.SetPlayerRole.Remove(player); + return; + } + + RoleTypes role; + switch (subArgs) + { + case "impostor" or "1": + role = RoleTypes.Impostor; + break; + case "shapeshifter" or "2": + role = RoleTypes.Shapeshifter; + break; + case "phantom" or "3": + role = RoleTypes.Phantom; + break; + case "crewmate" or "4": + role = RoleTypes.Crewmate; + break; + case "engineer" or "5": + role = RoleTypes.Engineer; + break; + case "scientist" or "6": + role = RoleTypes.Scientist; + break; + case "tracker" or "7": + role = RoleTypes.Tracker; + break; + default: + Utils.AddChatPrivate($"{error}\nInvalid RoleType!"); + return; + } + string RoleHexColor = role is RoleTypes.Impostor or RoleTypes.ImpostorGhost or RoleTypes.Shapeshifter or RoleTypes.Phantom ? "#a60d0d" : "#63bfbf"; + Utils.AddChatPrivate($"Set role to {Main.GetRoleName[(int)role]} for the next game!"); + RoleManagerPatch.SetPlayerRole[player] = role; + } + else if (HandlePlayerArgument(command, subArgs) == true) + { + var player = cmdTarget; + if (player == PlayerControl.LocalPlayer) + { + Utils.AddChatPrivate($"{error}\nUnable to use /SetRole for self, use /Role!"); + return; + } + var hexColor = Utils.Color32ToHex(Palette.PlayerColors[player.CurrentOutfit.ColorId]); + if (subArgs2 == "") + { + Utils.AddChatPrivate($"Set {player?.Data?.PlayerName} role to Random for the next game!"); + RoleManagerPatch.SetPlayerRole.Remove(player); + return; + } + RoleTypes role; + switch (subArgs2) + { + case "impostor" or "1": + role = RoleTypes.Impostor; + break; + case "shapeshifter" or "2": + role = RoleTypes.Shapeshifter; + break; + case "phantom" or "3": + role = RoleTypes.Phantom; + break; + case "crewmate" or "4": + role = RoleTypes.Crewmate; + break; + case "engineer" or "5": + role = RoleTypes.Engineer; + break; + case "scientist" or "6": + role = RoleTypes.Scientist; + break; + case "tracker" or "7": + role = RoleTypes.Tracker; + break; + default: + Utils.AddChatPrivate($"{error}\nInvalid RoleType!"); + return; + } + string RoleHexColor = role is RoleTypes.Impostor or RoleTypes.ImpostorGhost or RoleTypes.Shapeshifter or RoleTypes.Phantom ? "#a60d0d" : "#63bfbf"; + Utils.AddChatPrivate($"Set {player?.Data?.PlayerName} role to {Main.GetRoleName[(int)role]} for the next game!"); + RoleManagerPatch.SetPlayerRole[player] = role; + } + } + break; + case "syncallnames": + if (HandleIsHost(command) == true) + { + RPC.SyncAllNames(); + } + break; + default: + Utils.AddChatPrivate("Invalid Command!"); + break; + } + } + + cmdTarget = null; + } + + // Condition when a player is allowed to run a specific command + private static bool UseCommandInGame() + { + bool flag = GameStates.IsInGame && GameStates.IsMeeting || GameStates.IsExilling || GameStates.IsLobby || GameStates.IsFreePlay || !PlayerControl.LocalPlayer.IsAlive(); + if (!flag) + { + Utils.AddChatPrivate("Unable To Use Command While In Game!"); + } + return flag; + } + + // Handle player ID arguments in commands + private static bool HandlePlayerArgument(string[] command, string subArg) + { + string error = "Error:"; + + bool flag = subArg.Where(char.IsDigit).ToArray().Any(); + bool flag2 = command.Length > 2; + bool flag3 = false; + + if (flag) + flag3 = Main.AllPlayerControls.ToArray().Any(player => !player.isDummy && player.Data.PlayerId == int.Parse(subArg.Where(char.IsDigit).ToArray())); + + if (flag3 == true) + { + cmdTarget = Utils.PlayerFromId(int.Parse(subArg)); + return true; + } + else + { + if (!flag) + { + if (!flag2) + { + Utils.AddChatPrivate($"{error}\n{command[0][1..]} " + "{Player ID}"); + } + else + { + Utils.AddChatPrivate($"{error}\n{command[0][1..]} {command[1]} " + "{Player ID}"); + } + } + else + { + Utils.AddChatPrivate($"{error}\nPlayer not found"); + } + + return false; + } + } + + // Handle host only commands + private static bool HandleIsHost(string[] command) + { + string error = "Error:"; + + bool flag = PlayerControl.LocalPlayer.IsHost(); + + if (!flag) + { + Utils.AddChatPrivate($"{error}\n{command[0]} Is only available as host!"); + } + + return flag; + } + + // Handle if player has permission from the host to run command + private static bool HandleHasPermission(string[] command) + { + string error = "Error:"; + + if (HasPermission != true && !GameStates.IsHost && !GameStates.IsDev) + { + Utils.AddChatPrivate($"{error}\n{command[0]} Is only available with the host permission!\nask the host to type /allow in chat to get permissions"); + return false; + } + return true; + } + + // Check if command is typed when sending chat message + [HarmonyPatch(nameof(ChatController.SendChat))] + [HarmonyPrefix] + public static bool SendChat_Prefix(ChatController __instance) + { + string text = __instance.freeChatField.textArea.text; + + if (string.IsNullOrEmpty(text) || text.Length <= 1 || text[0].ToString() != Main.CommandPrefix.Value || 3f - __instance.timeSinceLastMessage > 0f) + { + if (GameStates.InGame && !GameStates.IsLobby && !GameStates.IsFreePlay && !GameStates.IsMeeting && !GameStates.IsExilling && PlayerControl.LocalPlayer.IsAlive()) + return false; + + if (ChatPatch.ChatHistory.Count == 0 || ChatPatch.ChatHistory[^1] != text) ChatPatch.ChatHistory.Add(text); + ChatPatch.CurrentHistorySelection = ChatPatch.ChatHistory.Count; + return true; + } + + string[] command = text.Split(' '); + + HandleCommand(__instance, command); + + if (ChatPatch.ChatHistory.Count == 0 || ChatPatch.ChatHistory[^1] != text) ChatPatch.ChatHistory.Add(text); + ChatPatch.CurrentHistorySelection = ChatPatch.ChatHistory.Count; + + __instance.timeSinceLastMessage = 0f; + __instance.freeChatField.Clear(); + __instance.quickChatMenu.Clear(); + __instance.quickChatField.Clear(); + return false; + } + + // Set up command helper + private static GameObject commandText; + private static GameObject commandInfo; + private static RandomNameGenerator NameRNG; + [HarmonyPatch(nameof(ChatController.Toggle))] + [HarmonyPostfix] + public static void Awake_Postfix(ChatController __instance) + { + if (commandText == null) + { + var TextArea = __instance.freeChatField.textArea.gameObject; + GameObject CommandDisplay = UnityEngine.Object.Instantiate(TextArea, TextArea.transform.parent.transform); + CommandDisplay.transform.SetSiblingIndex(TextArea.transform.GetSiblingIndex() + 1); + CommandDisplay.transform.DestroyChildren(); + CommandDisplay.name = "CommandArea"; + CommandDisplay.GetComponent().color = new Color(1f, 1f, 1f, 0.5f); + commandText = CommandDisplay; + } + + if (commandInfo == null) + { + var TextArea = __instance.freeChatField.textArea.gameObject; + GameObject CommandInformation = UnityEngine.Object.Instantiate(TextArea, TextArea.transform.parent.transform); + CommandInformation.transform.SetSiblingIndex(TextArea.transform.GetSiblingIndex() + 1); + CommandInformation.transform.DestroyChildren(); + CommandInformation.transform.localPosition = new Vector3(CommandInformation.transform.localPosition.x, 0.45f); + CommandInformation.name = "CommandInfoText"; + CommandInformation.GetComponent().color = Color.yellow; + CommandInformation.GetComponent().outlineColor = new Color(0f, 0f, 0f, 1f); + CommandInformation.GetComponent().outlineWidth = 0.2f; + CommandInformation.GetComponent().characterWidthAdjustment = 1.5f; + CommandInformation.GetComponent().enableWordWrapping = false; + commandInfo = CommandInformation; + } + + if (NameRNG == null) + { + RandomNameGenerator rng = __instance.gameObject.AddComponent(); + NameRNG = rng; + } + } + + // Command helper + [HarmonyPatch(nameof(ChatController.Update))] + [HarmonyPostfix] + public static void Update_Postfix(ChatController __instance) + { + string text = __instance.freeChatField.textArea.text; + + if (commandText != null && commandInfo != null) + { + // Check if the first character is the command prefix + if (text.Length > 0 && text[0].ToString() == Main.CommandPrefix.Value) + { + // Get the typed command without the prefix + string typedCommand = text.Substring(1); + + // Find the closest matching command + string closestCommand = GetClosestCommand(typedCommand.Split(' ')[0]); + string CommandInfo = GetClosestCommand(typedCommand.Split(' ')[0]); + + // Check for character mismatches + if (!string.IsNullOrEmpty(closestCommand) && !string.IsNullOrEmpty(CommandInfo) && IsMatch(typedCommand.Split(' ')[0], closestCommand)) + { + closestCommand = closestCommand.Split("---")[0]; + CommandInfo = CommandInfo.Split("---")[1]; + + // Handle arguments correctly + string[] typedParts = typedCommand.Split(' '); + string[] commandParts = closestCommand.Split(' '); + + // If there are arguments, ensure to display the appropriate part + string suggestion = commandParts[0].Substring(typedParts[0].Length); // Initial suggestion + + suggestion = typedParts[0].Substring(0, typedParts[0].Length) + suggestion; + + // Handle additional arguments + for (int i = 1; i < typedParts.Length; i++) + { + if (i < commandParts.Length && typedParts[i] != "") + { + // Add spaces to match the already typed part of the current argument + suggestion = typedCommand + " " + new string(' ', typedParts[i].Length); + } + else if (i < commandParts.Length) + { + // Add the next argument if the current one is empty + suggestion = typedCommand + " " + commandParts[i]; + } + } + + if (Input.GetKeyDown(KeyCode.Tab) && typedParts.Length < 2) + { + __instance.freeChatField.textArea.SetText(Main.CommandPrefix.Value + commandParts[0]); + } + + // Display the command suggestion + commandText.GetComponent().text = Main.CommandPrefix.Value + suggestion; + commandInfo.GetComponent().text = CommandInfo; + } + else + { + // Clear the suggestion if there is a mismatch + commandText.GetComponent().text = string.Empty; + commandInfo.GetComponent().text = string.Empty; + } + } + else + { + commandText.GetComponent().text = string.Empty; + commandInfo.GetComponent().text = string.Empty; + } + } + } + + public static string GetClosestCommand(string typedCommand) + { + var closestCommand = CommandListHelper.FirstOrDefault(c => c.StartsWith(typedCommand, StringComparison.OrdinalIgnoreCase)); + + if (closestCommand == null && PlayerControl.LocalPlayer.IsDev()) + closestCommand = DebugCommandListHelper.FirstOrDefault(c => c.StartsWith(typedCommand, StringComparison.OrdinalIgnoreCase)); + + return closestCommand ?? string.Empty; + } + + public static bool IsMatch(string typedCommand, string closestCommand) => closestCommand.Length >= typedCommand.Length && + closestCommand.Substring(0, typedCommand.Length).Equals(typedCommand, System.StringComparison.OrdinalIgnoreCase); +} diff --git a/Patches/DisconnectPenaltyPatch.cs b/Patches/DisconnectPenaltyPatch.cs new file mode 100644 index 0000000..7148df3 --- /dev/null +++ b/Patches/DisconnectPenaltyPatch.cs @@ -0,0 +1,17 @@ +using HarmonyLib; + +namespace BetterAmongUs.Patches; + +[HarmonyPatch(typeof(StatsManager), nameof(StatsManager.BanMinutesLeft), MethodType.Getter)] +public static class DisconnectPenaltyPatch { + public static bool Prefix(StatsManager __instance, ref int __result) + { + if (__instance.BanPoints != 0f) + { + __instance.BanPoints = 0f; + __result = 0; + return false; + } + return true; + } +} diff --git a/Patches/DiscordPatch.cs b/Patches/DiscordPatch.cs new file mode 100644 index 0000000..b05251b --- /dev/null +++ b/Patches/DiscordPatch.cs @@ -0,0 +1,67 @@ +using AmongUs.Data; +using Discord; +using HarmonyLib; + +namespace BetterAmongUs.Patches; + + +[HarmonyPatch(typeof(ActivityManager), nameof(ActivityManager.UpdateActivity))] +public class DiscordRPC +{ + private static string lobbycode = ""; + private static string region = ""; + + public static void Prefix([HarmonyArgument(0)] Activity activity) + { + if (activity == null) return; + + string details = $"BAU v{Main.PluginVersion}"; + activity.Details = details; + + if (activity.State == "In Menus") return; + + try + { + if (!DataManager.Settings.Gameplay.StreamerMode) + { + UpdateRegionAndLobbyCode(); + if (!string.IsNullOrEmpty(lobbycode) && !string.IsNullOrEmpty(region)) + { + if (GameStates.IsNormalGame) + details = $"BAU - {lobbycode} ({region})"; + else if (GameStates.IsHideNSeek) + details = $"BAU Hide & Seek - {lobbycode} ({region})"; + } + } + else + { + if (GameStates.IsHideNSeek) + details = $"BAU v{Main.PluginVersion} - Hide & Seek"; + } + } + catch + { + } + + activity.Details = details; + } + + private static void UpdateRegionAndLobbyCode() + { + if (GameStates.IsLobby) + { + lobbycode = GameStartManager.Instance.GameRoomNameCode.text; + region = ServerManager.Instance.CurrentRegion.Name; + region = region switch + { + "North America" => "NA", + "Europe" => "EU", + "Asia" => "AS", + _ when region.Contains("MNA") => "MNA", + _ when region.Contains("MEU") => "MEU", + _ when region.Contains("MAS") => "MAS", + _ => region + }; + } + } +} diff --git a/Patches/EOSManagerPatch.cs b/Patches/EOSManagerPatch.cs new file mode 100644 index 0000000..c2a58a6 --- /dev/null +++ b/Patches/EOSManagerPatch.cs @@ -0,0 +1,15 @@ +using HarmonyLib; + +namespace BetterAmongUs.Patches; + +[HarmonyPatch(typeof(EOSManager))] +public class EOSManagerPatch +{ + [HarmonyPatch(nameof(EOSManager.Update))] + [HarmonyPostfix] + public static void Postfix(EOSManager __instance) + { + __instance.ageOfConsent = 21; + __instance.isKWSMinor = false; + } +} diff --git a/Patches/GamePlayManagerPatch.cs b/Patches/GamePlayManagerPatch.cs new file mode 100644 index 0000000..9c0e924 --- /dev/null +++ b/Patches/GamePlayManagerPatch.cs @@ -0,0 +1,34 @@ +using HarmonyLib; + +namespace BetterAmongUs.Patches; + +class GamePlayManager +{ + [HarmonyPatch(typeof(GameStartManager))] + public class GameStartManagerPatch + { + [HarmonyPatch(nameof(GameStartManager.BeginGame))] + [HarmonyPostfix] + public static void Postfix(/*GameStartManager __instance*/) + { + AntiCheat.PauseAntiCheat(); + } + } + + [HarmonyPatch(typeof(GameManager))] + public class GameManagerPatch + { + [HarmonyPatch(nameof(GameManager.EndGame))] + [HarmonyPostfix] + public static void Postfix(/*GameManager __instance*/) + { + if (GameStates.IsHost) + { + foreach (PlayerControl player in Main.AllPlayerControls) + { + player.RpcSetName(player.Data.PlayerName); + } + } + } + } +} diff --git a/Patches/HudManagerPatch.cs b/Patches/HudManagerPatch.cs new file mode 100644 index 0000000..0997beb --- /dev/null +++ b/Patches/HudManagerPatch.cs @@ -0,0 +1,80 @@ +using HarmonyLib; +using UnityEngine; + + +namespace BetterAmongUs.Patches; + +[HarmonyPatch(typeof(HudManager))] +public class HudManagerPatch +{ + public static string WelcomeMessage = $"Welcome To Better Among Us\n{Main.GetVersionText()}\n" + + "Thanks for downloading!\n \n" + + "BAU Is a mod for improving the vanilla Among Us experience with a built-in Anti-Cheat and other futures, BAU is a client-sided mod so it can be used with other vanilla Among Us players.\n\n" + + "BAU Also has a option in Better Options on the pause menu called Better Host, Better Host is designed to enhance the vanilla experience for other players without having BAU installed!"; + + private static bool HasBeenWelcomed = false; + [HarmonyPatch(nameof(HudManager.Start))] + [HarmonyPostfix] + public static void Start_Postfix(HudManager __instance) + { + _ = new LateTask(() => + { + try + { + if (BAUNotificationManager.BAUNotificationManagerObj == null) + { + bool ChatState = GameObject.Find("ChatUi"); + __instance.Chat.gameObject.SetActive(true); + __instance.Chat.chatNotification.gameObject.SetActive(true); + __instance.Chat.chatNotification.SetUp(PlayerControl.LocalPlayer, ""); + __instance.Chat.chatNotification.timeOnScreen = 0f; + __instance.Chat.chatNotification.gameObject.SetActive(false); + __instance.Chat.gameObject.SetActive(ChatState); + } + + if (!HasBeenWelcomed && GameStates.IsInGame && GameStates.IsLobby && !GameStates.IsFreePlay) + { + BAUNotificationManager.Notify("Welcome To Better Among Us!", 8f); + + Utils.AddChatPrivate(WelcomeMessage, overrideName: " "); + HasBeenWelcomed = true; + } + } + catch { } + }, 1f, "HudManagerPatch Start"); + } + [HarmonyPatch(nameof(HudManager.Update))] + [HarmonyPostfix] + public static void Update_Postfix(HudManager __instance) + { + try + { + GameObject gameStart = GameObject.Find("GameStartManager"); + if (gameStart != null) + gameStart.transform.SetLocalY(-2.8f); + + if (GameStates.InGame && __instance?.Chat?.gameObject.active != true) + __instance.Chat.gameObject.SetActive(true); + + GameObject TaskDisplay = GameObject.Find("TaskDisplay"); + GameObject ProgressTracker = GameObject.Find("ProgressTracker"); + if (TaskDisplay != null && ProgressTracker != null) + { + if (PlayerControl.LocalPlayer.IsImpostorTeam()) + { + __instance.CrewmatesKilled.gameObject.SetActive(true); + __instance.CrewmatesKilled.gameObject.transform.SetLocalZ(-25); + TaskDisplay.transform.localPosition = new Vector3(0f, -0.4f, -5f); + ProgressTracker.transform.localPosition = new Vector3(-2.8733f, 3.1f); + } + else if (__instance.CrewmatesKilled.isActiveAndEnabled) + { + __instance.CrewmatesKilled.gameObject.SetActive(GameManager.Instance.ShowCrewmatesKilled()); + TaskDisplay.transform.localPosition = new Vector3(0f, 0f); + ProgressTracker.transform.localPosition = new Vector3(-2.8733f, 2.7f, 0); + } + } + } + catch { } + } +} diff --git a/Patches/LobbyPatch.cs b/Patches/LobbyPatch.cs new file mode 100644 index 0000000..129ceb3 --- /dev/null +++ b/Patches/LobbyPatch.cs @@ -0,0 +1,19 @@ +using HarmonyLib; + +namespace BetterAmongUs.Patches; + +public class LobbyPatch +{ + [HarmonyPatch(typeof(LobbyBehaviour))] + internal class LobbyBehaviourPatch + { + // Disabled annoying music + [HarmonyPatch(nameof(LobbyBehaviour.Update))] + [HarmonyPostfix] + public static void Postfix(PingTracker __instance) + { + if (Main.DisableLobbyTheme.Value) + SoundManager.instance.StopSound(LobbyBehaviour.Instance.MapTheme); + } + } +} diff --git a/Patches/MainMenuPatch.cs b/Patches/MainMenuPatch.cs new file mode 100644 index 0000000..90545e8 --- /dev/null +++ b/Patches/MainMenuPatch.cs @@ -0,0 +1,46 @@ +using HarmonyLib; +using UnityEngine; + +namespace BetterAmongUs.Patches; + +internal class MainMenuPatch +{ + private static readonly string OnlineMsg = "Online access has been temporarily disabled!"; + + // Handle FileChecker + [HarmonyPatch(typeof(AccountManager))] + internal class AccountManagerPatch + { + [HarmonyPatch(nameof(AccountManager.CanPlayOnline))] + [HarmonyPostfix] + public static void Postfix() + { + if (!FileChecker.HasShownPopUp && FileChecker.CheckIfUnauthorizedFiles()) + { + var lines = "----------------------------------------------------------------------------------------------"; + var icon = "♻"; + var warning = "⚠"; + FileChecker.HasShownPopUp = true; + DisconnectPopup.Instance._textArea.enableWordWrapping = false; + DisconnectPopup.Instance.ShowCustom($"{lines}\n{icon}Better Among Us{icon}\n{warning}{FileChecker.UnauthorizedReason}{warning}\n\n\n{OnlineMsg}\n{lines}"); + } + } + } + + // Replace AU logo with BAU logo + [HarmonyPatch(typeof(MainMenuManager))] + internal class MainMenuManagerPatch + { + [HarmonyPatch(nameof(MainMenuManager.Start))] + [HarmonyPostfix] + public static void Postfix(/*MainMenuManager __instance*/) + { + GameObject logo = GameObject.Find("LeftPanel/Sizer/LOGO-AU"); + GameObject sizer = logo.transform.parent.gameObject; + sizer.transform.localPosition = new Vector3(sizer.transform.localPosition.x, sizer.transform.localPosition.y - 0.035f, sizer.transform.localPosition.z); + sizer.transform.position = new Vector3(sizer.transform.position.x, sizer.transform.position.y, -0.5f); + logo.transform.localScale = new Vector3(0.003f, 0.0025f, 0f); + logo.GetComponent().sprite = Utils.LoadSprite("BetterAmongUs.Resources.Images.BetterAmongUs-Logo.png", 1f); + } + } +} diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs new file mode 100644 index 0000000..5ecfb2e --- /dev/null +++ b/Patches/MeetingHudPatch.cs @@ -0,0 +1,166 @@ +using HarmonyLib; +using System.Text; +using TMPro; +using UnityEngine; + +namespace BetterAmongUs; +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] +class MeetingHudStartPatch +{ + // Set up meeting player info text + public static void Postfix(MeetingHud __instance) + { + foreach (var pva in __instance.playerStates) + { + var TextTopMeeting = UnityEngine.Object.Instantiate(pva.NameText, pva.NameText.transform); + TextTopMeeting.gameObject.name = "TextTop"; + TextTopMeeting.DestroyChildren(); + TextTopMeeting.transform.position = pva.NameText.transform.position; + TextTopMeeting.transform.position += new Vector3(0f, 0.15f); + TextTopMeeting.GetComponent().text = ""; + + var TextInfoMeeting = UnityEngine.Object.Instantiate(pva.NameText, pva.NameText.transform); + TextInfoMeeting.gameObject.name = "TextInfo"; + TextInfoMeeting.DestroyChildren(); + TextInfoMeeting.transform.position = pva.NameText.transform.position; + TextInfoMeeting.transform.position += new Vector3(0f, 0.28f); + TextInfoMeeting.GetComponent().text = ""; + + var PlayerLevel = pva.transform.Find("PlayerLevel"); + PlayerLevel.localPosition = new Vector3(PlayerLevel.localPosition.x, PlayerLevel.localPosition.y, -2f); + var LevelDisplay = UnityEngine.Object.Instantiate(PlayerLevel, pva.transform); + LevelDisplay.transform.SetSiblingIndex(pva.transform.Find("PlayerLevel").GetSiblingIndex() + 1); + LevelDisplay.gameObject.name = "PlayerId"; + LevelDisplay.GetComponent().color = new Color(1f, 0f, 1f, 1f); + var IdLabel = LevelDisplay.transform.Find("LevelLabel"); + var IdNumber = LevelDisplay.transform.Find("LevelNumber"); + UnityEngine.Object.Destroy(IdLabel.GetComponent()); + IdLabel.GetComponent().text = "ID"; + IdNumber.GetComponent().text = pva.TargetPlayerId.ToString(); + IdLabel.name = "IdLabel"; + IdNumber.name = "IdNumber"; + PlayerLevel.transform.position += new Vector3(0.23f, 0f); + } + + RPC.SyncAllNames(isForMeeting: true); + } +} +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Update))] +class MeetingHudUpdatePatch +{ + // Set player meeting info + public static void Postfix(MeetingHud __instance) + { + foreach (var pva in __instance.playerStates) + { + if (pva == null) return; + + if (pva.ColorBlindName.isActiveAndEnabled) + { + pva.ColorBlindName.transform.localPosition = new Vector3(-0.91f, -0.19f, -0.05f); + pva.ColorBlindName.color = Palette.PlayerColors[pva.PlayerIcon.ColorId]; + pva.ColorBlindName.outlineWidth = 0.2745f; + } + + TextMeshPro TopText = pva.NameText.transform.Find("TextTop").gameObject.GetComponent(); + TextMeshPro InfoText = pva.NameText.transform.Find("TextInfo").gameObject.GetComponent(); + + bool flag = Main.AllPlayerControls.Any(player => player.PlayerId == pva.TargetPlayerId); + + if (!flag) + { + SetPlayerTextInfoMeeting(pva, "Disconnected", isInfo: true); + SetPlayerTextInfoMeeting(pva, ""); + pva.transform.Find("votePlayerBase").gameObject.SetActive(false); + pva.transform.Find("deadX_border").gameObject.SetActive(false); + pva.ClearForResults(); + pva.SetDisabled(); + } + else if (TopText != null && InfoText != null) + { + var target = Utils.PlayerFromId(pva.TargetPlayerId); + if (target == null) return; + string hashPuid = Utils.GetHashPuid(target); + string friendCode = target.Data.FriendCode; + + StringBuilder sbTag = new StringBuilder(); + StringBuilder sbInfo = new StringBuilder(); + + // Put +++ at the end of each tag + + if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.SickoData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.SickoData.ContainsValue(friendCode)) + sbTag.Append("Sicko User+++"); + else if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.AUMData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.AUMData.ContainsValue(friendCode)) + sbTag.Append("AUM User+++"); + else if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.PlayerData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.PlayerData.ContainsValue(friendCode)) + sbTag.Append("Known Cheater+++"); + + for (int i = 0; i < sbTag.ToString().Split("+++").Length; i++) + { + if (!string.IsNullOrEmpty(sbTag.ToString().Split("+++")[i])) + { + if (i < sbTag.ToString().Split("+++").Length) + { + sbInfo.Append(sbTag.ToString().Split("+++")[i]); + } + if (i != sbTag.ToString().Split("+++").Length - 2) + { + sbInfo.Append(" - "); + } + } + } + + string RoleHexColor = target.IsImpostorTeam() ? "#ff1919" : "#8cffff"; + string Role = $"{target.GetRoleName()}"; + if (!target.IsImpostorTeammate()) + { + if (PlayerControl.LocalPlayer.IsAlive() && target != PlayerControl.LocalPlayer) + { + if (!DebugMenu.RevealRoles) + { + Role = ""; + } + } + } + + Vector3 textPos; + + if (Role != "" && !string.IsNullOrEmpty(sbInfo.ToString())) + textPos = new Vector3(pva.NameText.transform.localPosition.x, -0.045f); + else if (Role != "" || !string.IsNullOrEmpty(sbInfo.ToString())) + textPos = new Vector3(pva.NameText.transform.localPosition.x, -0.03f); + else + textPos = new Vector3(pva.NameText.transform.localPosition.x, 0.015f); + + pva.NameText.transform.localPosition = textPos; + + SetPlayerTextInfoMeeting(pva, $"{sbInfo}", isInfo: true); + SetPlayerTextInfoMeeting(pva, $"{Role}"); + } + } + } + + private static void SetPlayerTextInfoMeeting(PlayerVoteArea pva, string text, bool isInfo = false) + { + string InfoType = "TextTop"; + if (isInfo) + { + InfoType = "TextInfo"; + GameObject TopText = pva.NameText.transform.Find("TextTop").gameObject; + if (TopText != null) + { + if (TopText.GetComponent()?.text.Replace("", string.Empty).Replace("", string.Empty).Length < 1) + { + text = "" + text + ""; + } + } + } + + text = "" + text + ""; + GameObject TextObj = pva.NameText.transform.Find(InfoType).gameObject; ; + if (TextObj != null) + { + TextObj.GetComponent().text = text; + } + } +} \ No newline at end of file diff --git a/Patches/MiniMapBehaviourPatch.cs b/Patches/MiniMapBehaviourPatch.cs new file mode 100644 index 0000000..55c32ed --- /dev/null +++ b/Patches/MiniMapBehaviourPatch.cs @@ -0,0 +1,107 @@ +using HarmonyLib; +using UnityEngine; + + +namespace BetterAmongUs.Patches; + +public class MiniMapBehaviourPatch +{ + [HarmonyPatch(typeof(MapBehaviour))] + class MapBehaviourPatch + { + [HarmonyPatch(nameof(MapBehaviour.Awake))] + [HarmonyPostfix] + public static void Awake_Postfix(MapBehaviour __instance) + { + PoolablePlayer NewDisplay = __instance.HerePoint.gameObject.AddComponent(); + NewDisplay.SetBodyCosmeticsVisible(true); + } + [HarmonyPatch(nameof(MapBehaviour.Show))] + [HarmonyPrefix] + public static bool Show_Prefix(MapBehaviour __instance, ref MapOptions opts) + { + if (__instance.IsOpen) + { + __instance.Close(); + return false; + } + if (!PlayerControl.LocalPlayer.CanMove && !PlayerControl.LocalPlayer.IsInVent() && !PlayerControl.LocalPlayer.onLadder && !PlayerControl.LocalPlayer.inMovingPlat && !GameStates.IsMeeting) + { + return false; + } + switch (opts.Mode) + { + case MapOptions.Modes.Normal: + __instance.ShowNormalMap(); + break; + case MapOptions.Modes.CountOverlay: + __instance.ShowCountOverlay(opts.AllowMovementWhileMapOpen, opts.ShowLivePlayerPosition, opts.IncludeDeadBodies); + break; + case MapOptions.Modes.Sabotage: + __instance.ShowSabotageMap(); + break; + } + + return false; + } + [HarmonyPatch(nameof(MapBehaviour.ShowNormalMap))] + [HarmonyPrefix] + public static bool ShowNormalMap_Prefix(MapBehaviour __instance) + { + if (__instance.IsOpen) + { + __instance.Close(); + return false; + } + if (!PlayerControl.LocalPlayer.CanMove && !PlayerControl.LocalPlayer.IsInVent() && !PlayerControl.LocalPlayer.onLadder && !PlayerControl.LocalPlayer.inMovingPlat && !GameStates.IsMeeting) + { + return false; + } + PlayerControl.LocalPlayer.SetPlayerMaterialColors(__instance.HerePoint); + if (PlayerControl.LocalPlayer.isTrackingPlayer) + { + if (PlayerControl.LocalPlayer.trackedPlayer.Data.Disconnected) + { + __instance.TrackedHerePoint.gameObject.SetActive(false); + } + else + { + __instance.SetTrackedHerePointColor(PlayerControl.LocalPlayer.trackedPlayerColorID); + __instance.TrackedHerePoint.gameObject.SetActive(true); + __instance.UpdateTrackedPosition(); + } + } + __instance.GenericShow(); + __instance.taskOverlay.Show(); + __instance.ColorControl.SetColor(new Color(0.05f, 0.6f, 1f, 1f)); + DestroyableSingleton.Instance.SetHudActive(false); + return false; + } + [HarmonyPatch(nameof(MapBehaviour.ShowSabotageMap))] + [HarmonyPrefix] + public static bool ShowSabotageMap_Prefix(MapBehaviour __instance) + { + if (!PlayerControl.LocalPlayer.CanMove && !PlayerControl.LocalPlayer.IsInVent() && !PlayerControl.LocalPlayer.onLadder && !PlayerControl.LocalPlayer.inMovingPlat) + { + return false; + } + PlayerControl.LocalPlayer.SetPlayerMaterialColors(__instance.HerePoint); + __instance.GenericShow(); + __instance.infectedOverlay.gameObject.SetActive(true); + __instance.taskOverlay.Show(); + __instance.ColorControl.SetColor(new Color(1f, 0.3f, 0f, 1f)); + return false; + } + [HarmonyPatch(nameof(MapBehaviour.ShowCountOverlay))] + [HarmonyPostfix] + public static void ShowCountOverlay_Postfix(MapBehaviour __instance) => __instance.ColorControl.SetColor(new Color(0.2f, 0.5f, 0f, 1f)); + } + + [HarmonyPatch(typeof(MapConsole))] + class MapConsolePatch + { + [HarmonyPatch(nameof(MapConsole.Use))] + [HarmonyPostfix] + public static void ShowCountOverlay_Postfix() => MapBehaviour.Instance.ColorControl.SetColor(new Color(0.2f, 0.5f, 0f, 1f)); + } +} diff --git a/Patches/ModManagerPatch.cs b/Patches/ModManagerPatch.cs new file mode 100644 index 0000000..bf9c476 --- /dev/null +++ b/Patches/ModManagerPatch.cs @@ -0,0 +1,22 @@ +using HarmonyLib; +using UnityEngine; + +namespace BetterAmongUs.Patches; + +[HarmonyPatch(typeof(ModManager))] +public class ModManagerPatch +{ + [HarmonyPatch(nameof(ModManager.LateUpdate))] + [HarmonyPostfix] + public static void LateUpdate_Postfix(ModManager __instance) + { + // __instance.ShowModStamp(); + + if (__instance.ModStamp.gameObject.active == true) + __instance.ModStamp.GetComponent().sprite = Utils.LoadSprite("BetterAmongUs.Resources.Images.BetterAmongUs-Mod.png", 250f); + + FileChecker.UpdateUnauthorizedFiles(); + LateTask.Update(Time.deltaTime); + BAUNotificationManager.Update(); + } +} diff --git a/Patches/PingTrackerPatch.cs b/Patches/PingTrackerPatch.cs new file mode 100644 index 0000000..af80add --- /dev/null +++ b/Patches/PingTrackerPatch.cs @@ -0,0 +1,80 @@ +using HarmonyLib; +using System.Text; +using UnityEngine; + +namespace BetterAmongUs.Patches; + +[HarmonyPatch(typeof(PingTracker))] +public class PingTrackerPatch +{ + [HarmonyPatch(nameof(PingTracker.Update))] + [HarmonyPrefix] + public static bool Prefix(PingTracker __instance) + { + try + { + PlayerControl Host = AmongUsClient.Instance.GetHost().Character; + + if (GameStates.IsFreePlay) + { + __instance.gameObject.SetActive(false); + } + + StringBuilder sb = new StringBuilder(); + + sb.AppendFormat("PING: {0}\n", GetPingColor(AmongUsClient.Instance.Ping)); + sb.Append($"BetterAmongUs {Main.GetVersionText()}\n"); + sb.AppendFormat("{0}\n", Main.Github); + + if (Main.BetterHost.Value && GameStates.IsHost) + { + sb.Append("Better Host: Enabled\n"); + } + + if (Main.ShowFPS.Value) + { + float FPSNum = 1.0f / Time.deltaTime; + sb.AppendFormat("FPS: {0}\n", (int)FPSNum); + } + + // Add Host Info if not in lobby + if (GameStates.IsInGamePlay && Host != null) + { + string hostColor = Utils.Color32ToHex(Palette.PlayerColors[Host.CurrentOutfit.ColorId]); + sb.AppendFormat("Host: {1}\n", hostColor, Host.CurrentOutfit.PlayerName); + } + + __instance.aspectPosition.DistanceFromEdge = new Vector3(4f, 0.1f, -5); + __instance.aspectPosition.Alignment = AspectPosition.EdgeAlignments.RightTop; + __instance.text.outlineWidth = 0.3f; + __instance.text.text = sb.ToString(); + } + catch { } + + return false; + } + + private static string GetPingColor(int ping) + { + string color; + switch (ping) + { + case int n when n > 250: + color = "#ff0000"; + break; + case int n when n > 100: + color = "#ffa200"; + break; + case int n when n > 50: + color = "#ffff00"; + break; + default: + color = "#00f04c"; + break; + } + + string newPing = $"{ping} ms"; + + return newPing; + } +} diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs new file mode 100644 index 0000000..9a46efc --- /dev/null +++ b/Patches/PlayerControlPatch.cs @@ -0,0 +1,227 @@ +using AmongUs.Data; +using HarmonyLib; +using Hazel; +using Il2CppSystem.Linq; +using LibCpp2IL; +using System.Text; +using System.Text.RegularExpressions; +using TMPro; +using UnityEngine; + +namespace BetterAmongUs.Patches; + +[HarmonyPatch(typeof(PlayerControl))] +class PlayerControlPatch +{ + public static float infotime = 0f; + [HarmonyPatch(nameof(PlayerControl.FixedUpdate))] + [HarmonyPostfix] + public static void FixedUpdate_Postfix(PlayerControl __instance) + { + // Set up player text info + var NameText = GameObject.Find($"{__instance.gameObject.transform.name}/Names/NameText_TMP"); + var InfoText = GameObject.Find($"{__instance.gameObject.transform.name}/Names/NameText_TMP/InfoText_T_TMP"); + if (NameText != null && InfoText == null) + { + GameObject playerInfoDisplayInfo = UnityEngine.Object.Instantiate(NameText, NameText.transform); + playerInfoDisplayInfo.name = "InfoText_Info_TMP"; + playerInfoDisplayInfo.transform.DestroyChildren(); + playerInfoDisplayInfo.transform.position += new Vector3(0f, 0.25f); + playerInfoDisplayInfo.GetComponent().text = string.Empty; + playerInfoDisplayInfo.SetActive(true); + + GameObject playerInfoDisplayTop = UnityEngine.Object.Instantiate(NameText, NameText.transform); + playerInfoDisplayTop.name = "InfoText_T_TMP"; + playerInfoDisplayTop.transform.DestroyChildren(); + playerInfoDisplayTop.transform.position += new Vector3(0f, 0.15f); + playerInfoDisplayTop.GetComponent().text = string.Empty; + playerInfoDisplayTop.SetActive(true); + + GameObject playerInfoDisplayBottom = UnityEngine.Object.Instantiate(NameText, NameText.transform); + playerInfoDisplayBottom.name = "InfoText_B_TMP"; + playerInfoDisplayBottom.transform.DestroyChildren(); + playerInfoDisplayBottom.transform.position += new Vector3(0f, -0.15f); + playerInfoDisplayBottom.GetComponent().text = string.Empty; + playerInfoDisplayBottom.SetActive(true); + } + + // Set color blind text on player + if (__instance.DataIsCollected() && !__instance.shapeshifting) + { + __instance.cosmetics.SetColorBlindColor(__instance.CurrentOutfit.ColorId); + } + else + { + __instance.cosmetics.colorBlindText.text = string.Empty; + } + + infotime -= Time.deltaTime; + + if (infotime <= 0) + { + SetPlayerInfo(__instance); + infotime = 0.4f; + } + + if (GameStates.IsInGame && GameStates.IsHost) + RPC.SyncAllNames(isBetterHost: false); + } + + public static void SetPlayerInfo(PlayerControl player) + { + if (player == null) return; + + // Set player text info + if (player.DataIsCollected() != true) + { + if (player.gameObject.transform.Find("Names/NameText_TMP").GetComponent().text is "???" or "Player") + { + player.gameObject.transform.Find("Names/NameText_TMP").GetComponent().text = "Loading"; + } + + return; + } + + if (Main.AllPlayerControls.Any(pc => player.Data.PlayerId == pc.shapeshiftTargetPlayerId)) + return; + + string NewName = player.Data.PlayerName; + string hashPuid = Utils.GetHashPuid(player); + string friendCode = player.Data.FriendCode; + int playerId = player.PlayerId; + string Lv = " - Lv: " + player.Data.PlayerLevel.ToString() + ""; + + string pattern = @"^[a-zA-Z0-9#]+$"; + string hashtagPattern = @"^#[0-9]{4}$"; + + string friendCodeColor = (Regex.Replace(friendCode, hashtagPattern, string.Empty).Length is > 10 or < 5 || !Regex.IsMatch(friendCode, pattern) || !Regex.IsMatch(friendCode, hashtagPattern)) ? "#00f7ff" : "#ff0000"; + + if (string.IsNullOrEmpty(friendCode) || friendCode == "") + friendCode = "???"; + + if (DataManager.Settings.Gameplay.StreamerMode == true) + friendCode = string.Concat('*').Repeat(friendCode.Length); + + StringBuilder sbTag = new StringBuilder(); + StringBuilder sbInfo = new StringBuilder(); + + // Put +++ at the end of each tag + + if (player.IsDev() && !GameStates.IsInGamePlay) + sbTag.Append("(Dev)+++"); + + if ((player == PlayerControl.LocalPlayer || player.GetIsBetterUser() == true) && GameStates.IsLobby && !GameStates.IsFreePlay) + sbTag.Append("Better User+++"); + + if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.SickoData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.SickoData.ContainsValue(friendCode)) + sbTag.Append("Sicko User+++"); + else if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.AUMData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.AUMData.ContainsValue(friendCode)) + sbTag.Append("AUM User+++"); + else if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.PlayerData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.PlayerData.ContainsValue(friendCode)) + sbTag.Append("Known Cheater+++"); + + if (GameStates.IsLobby && !GameStates.IsFreePlay) + sbTag.Append($"ID: {playerId}+++"); + + for (int i = 0; i < sbTag.ToString().Split("+++").Length; i++) + { + if (!string.IsNullOrEmpty(sbTag.ToString().Split("+++")[i])) + { + if (i < sbTag.ToString().Split("+++").Length) + { + sbInfo.Append(sbTag.ToString().Split("+++")[i]); + } + if (i != sbTag.ToString().Split("+++").Length - 2) + { + sbInfo.Append(" - "); + } + } + } + + PlayerControl Host = AmongUsClient.Instance.GetHost().Character; + if (player.IsHost() && GameStates.InGame && GameStates.IsLobby && !GameStates.IsFreePlay) + { + if (Main.LobbyPlayerInfo.Value == true) + { + string hexColor = Utils.Color32ToHex(Palette.PlayerColors[Host.CurrentOutfit.ColorId]); + NewName = $"{player.Data.PlayerName}"; + } + } + if (player.IsImpostorTeammate() && player != PlayerControl.LocalPlayer) + { + NewName = $"{player.Data.PlayerName}"; + } + + if (!player.IsInShapeshift()) + player.RawSetName(NewName); + + if (!GameStates.IsVanillaServer) + { + friendCode = string.Empty; + Lv = string.Empty; + } + + StringBuilder sbTag2 = new StringBuilder(); + StringBuilder sbInfo2 = new StringBuilder(); + + // Put +++ at the end of each tag + + sbTag2.Append($"{Utils.GetPlatformName(player, useTag: true)}"); // Don't put +++ here + sbTag2.Append($"{Lv}+++"); + + for (int i = 0; i < sbTag2.ToString().Split("+++").Length; i++) + { + if (!string.IsNullOrEmpty(sbTag2.ToString().Split("+++")[i])) + { + if (i < sbTag2.ToString().Split("+++").Length) + { + sbInfo2.Append(sbTag2.ToString().Split("+++")[i]); + } + if (i != sbTag2.ToString().Split("+++").Length - 2) + { + sbInfo2.Append(" - "); + } + } + } + + if (!GameStates.IsInGamePlay) + { + if (Main.LobbyPlayerInfo.Value == true) + { + player.SetPlayerTextInfo($"{sbInfo}", isInfo: true); + player.SetPlayerTextInfo($"{sbInfo2}"); + player.SetPlayerTextInfo($"{friendCode}", isBottom: true); + } + else + { + player.ResetAllPlayerTextInfo(); + } + } + else + { + string RoleHexColor = player.IsImpostorTeam() ? "#ff1919" : "#8cffff"; + string Role = $"{player.GetRoleName()}"; + if (!player.IsImpostorTeammate()) + { + if (PlayerControl.LocalPlayer.IsAlive() && player != PlayerControl.LocalPlayer) + { + if (!DebugMenu.RevealRoles) + { + Role = ""; + } + } + } + player.SetPlayerTextInfo($"{sbInfo}", isInfo: true); + player.SetPlayerTextInfo($"{Role}"); + player.SetPlayerTextInfo("", isBottom: true); + } + } + + [HarmonyPatch(nameof(PlayerControl.MurderPlayer))] + [HarmonyPostfix] + public static void MurderPlayer_Postfix(/*PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target*/) + { + if (GameStates.IsInGame || GameStates.IsFreePlay && !GameStates.IsLobby && !GameStates.IsHideNSeek && HudManager.Instance.CrewmatesKilled.isActiveAndEnabled) + HudManager.Instance?.NotifyOfDeath(); + } +} diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs new file mode 100644 index 0000000..82d86d9 --- /dev/null +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -0,0 +1,110 @@ +using InnerNet; +using HarmonyLib; +using Hazel; +using BetterAmongUs.Patches; + +namespace BetterAmongUs; + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameJoined))] +class OnGameJoinedPatch +{ + public static void Postfix(/*AmongUsClient __instance*/) + { + PlayerControlPatch.infotime = 0f; + + AntiCheat.PauseAntiCheat(); + + // Fix host icon in lobby on modded servers + if (!GameStates.IsVanillaServer) + { + var host = AmongUsClient.Instance.GetHost().Character; + host.SetColor(-2); + host.SetColor(host.CurrentOutfit.ColorId); + } + + _ = new LateTask(() => + { + // Send Better Among Us Check RPC + if (GameStates.IsInGame) + { + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.BetterCheck, SendOption.None, -1); + messageWriter.Write((byte)PlayerControl.LocalPlayer.NetId); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } + }, 1.5f, "OnGameJoinedPatch"); + + Logger.Log($"Successfully joined {GameCode.IntToGameName(AmongUsClient.Instance.GameId)}", "OnGameJoinedPatch"); + } +} +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerJoined))] +public static class OnPlayerJoinedPatch +{ + public static void Postfix(/*AmongUsClient __instance,*/ [HarmonyArgument(0)] ClientData client) + { + PlayerControlPatch.infotime = 0f; + + _ = new LateTask(() => + { + if (GameStates.IsInGame) + { + // Send Better Among Us Check RPC + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, unchecked((byte)CustomRPC.BetterCheck), SendOption.None, -1); + messageWriter.Write((byte)PlayerControl.LocalPlayer.NetId); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + + RPC.SyncAllNames(force: true); + + if (Main.BetterHost.Value) + client.Character.RpcSendHostChat(HudManagerPatch.WelcomeMessage, sendToBetterUser: false); + } + }, 1.5f, "OnPlayerJoinedPatch", false); + } +} +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerLeft))] +class OnPlayerLeftPatch +{ + public static void Postfix(/*AmongUsClient __instance,*/ [HarmonyArgument(0)] ClientData data, [HarmonyArgument(1)] DisconnectReasons reason) + { + if ((GameStates.IsInGame || GameStates.IsFreePlay) && !GameStates.IsLobby && !GameStates.IsHideNSeek && HudManager.Instance.CrewmatesKilled.isActiveAndEnabled) + HudManager.Instance?.NotifyOfDisconnect(data.Character); + } +} + +[HarmonyPatch(typeof(GameData), nameof(GameData.ShowNotification))] +class GameDataPatch +{ + public static bool Prefix(AmongUsClient __instance, ref string playerName, ref DisconnectReasons reason) + { + string ReasonText; + switch (reason) + { + case DisconnectReasons.ExitGame: + ReasonText = $"{playerName} Left the game!"; + break; + case DisconnectReasons.ClientTimeout: + ReasonText = $"{playerName} Disconnected!"; + break; + case DisconnectReasons.Kicked: + ReasonText = $"{playerName} Was kicked by {AmongUsClient.Instance.GetHost().Character.Data.PlayerName}!"; + break; + case DisconnectReasons.Banned: + ReasonText = $"{playerName} Was banned by {AmongUsClient.Instance.GetHost().Character.Data.PlayerName}!"; + break; + case DisconnectReasons.Hacking: + ReasonText = $"{playerName} Was banned by Innersloth Anti-Cheat!"; + break; + case DisconnectReasons.Error: + ReasonText = $"{playerName} Was kicked due to an error!"; + break; + case DisconnectReasons.Unknown: + ReasonText = $"{playerName} Left the game due to unknown reason?"; + break; + default: + ReasonText = $"{playerName} Left the game!"; + break; + } + + DestroyableSingleton.Instance.Notifier.AddDisconnectMessage(ReasonText); + return false; + } +} diff --git a/Patches/RoleManagerPatch.cs b/Patches/RoleManagerPatch.cs new file mode 100644 index 0000000..ec1b58b --- /dev/null +++ b/Patches/RoleManagerPatch.cs @@ -0,0 +1,219 @@ +using AmongUs.GameOptions; +using HarmonyLib; + +namespace BetterAmongUs.Role; + +[HarmonyPatch(typeof(RoleManager))] +public class RoleManagerPatch +{ + public static Dictionary SetPlayerRole = []; // Player, Role + public static Dictionary ImpostorMultiplier = []; // HashPuid, Multiplier + private static Random random = new Random(); + + // Better role algorithm + [HarmonyPatch(nameof(RoleManager.SelectRoles))] + [HarmonyPrefix] + public static bool RoleManager_Prefix(/*RoleManager __instance*/) + { + if (!Main.BetterRoleAlgorithma.Value) return true; + + Logger.Log($" Better Role Assignment Has Started!", "RoleManager"); + Logger.Log($"--------------------------------------------", "RoleManager"); + + // Set roles up + foreach (var addplayer in Main.AllPlayerControls.Where(pc => !ImpostorMultiplier.ContainsKey(Utils.GetHashPuid(pc)))) + ImpostorMultiplier[Utils.GetHashPuid(addplayer)] = 0; + + int NumImpostors = GameOptionsManager.Instance.CurrentGameOptions.NumImpostors; + + List Impostors = []; + List Crewmates = []; + + Dictionary ImpostorRoles = new() // Role, Amount + { + { RoleTypes.Shapeshifter, 0 }, + { RoleTypes.Phantom, 0 } + }; + + Dictionary CrewmateRoles = new() // Role, Amount + { + { RoleTypes.Engineer, 0 }, + { RoleTypes.Scientist, 0 }, + { RoleTypes.Tracker, 0 }, + { RoleTypes.Noisemaker, 0 } + }; + + List Roles = [.. ImpostorRoles.Keys, .. CrewmateRoles.Keys]; + + foreach (RoleTypes role in Roles) + { + if (IsImpostorRole(role)) + ImpostorRoles[role] = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(role); + else + CrewmateRoles[role] = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(role); + } + + // Override player role assignment + if (SetPlayerRole.Keys.Any()) + { + foreach (var kvp in SetPlayerRole.Where(kvp => kvp.Key != null).OrderBy(kvp => kvp.Key == PlayerControl.LocalPlayer ? 0 : 1)) + { + var player = kvp.Key; + var role = kvp.Value; + + if (role is RoleTypes.Impostor) + { + if (Impostors.Count < NumImpostors) + { + Impostors.Add(player); + player.RpcSetRole(RoleTypes.Impostor); + player.roleAssigned = true; + Logger.Log($"Override Assigned {Utils.GetRoleName(RoleTypes.Impostor)} role to {player.Data.PlayerName}", "RoleManager"); + } + else continue; + } + else if (role is RoleTypes.Crewmate) + { + Crewmates.Add(player); + player.RpcSetRole(RoleTypes.Crewmate); + player.roleAssigned = true; + Logger.Log($"Override Assigned {Utils.GetRoleName(RoleTypes.Crewmate)} role to {player.Data.PlayerName}", "RoleManager"); + } + else + { + if (IsImpostorRole(role)) + { + if (Impostors.Count < NumImpostors && ImpostorRoles[role] > 0) + { + ImpostorRoles[role]--; + Impostors.Add(player); + } + else continue; + } + else + { + if (CrewmateRoles[role] > 0) + { + CrewmateRoles[role]--; + Crewmates.Add(player); + } + else continue; + } + + player.RpcSetRole(role); + player.roleAssigned = true; + Logger.Log($"Override Assigned {Utils.GetRoleName(role)} role to {player.Data.PlayerName}", "RoleManager"); + } + } + } + + // Get players in random order + List players = Main.AllPlayerControls + .Where(player => !Impostors.Contains(player) && !Crewmates.Contains(player) && player.roleAssigned == false) + .ToList(); + + int n = players.Count; + while (n > 1) + { + n--; + int k = random.Next(n + 1); + PlayerControl value = players[k]; + players[k] = players[n]; + players[n] = value; + } + + // Assign roles + foreach (PlayerControl pc in players) + { + if (pc == null || pc.roleAssigned == true) continue; + + if (Impostors.Count < NumImpostors && RNG() > ImpostorMultiplier[Utils.GetHashPuid(pc)]) + { + var impRoles = Shuffle(ImpostorRoles); + foreach (var kvp in impRoles) + { + if (RNG() < GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(kvp.Key) && kvp.Value > 0) + { + ImpostorMultiplier[Utils.GetHashPuid(pc)] += 15; + ImpostorRoles[kvp.Key]--; + Impostors.Add(pc); + pc.RpcSetRole(kvp.Key); + pc.roleAssigned = true; + Logger.Log($"Assigned {Utils.GetRoleName(kvp.Key)} role to {pc.Data.PlayerName}", "RoleManager"); + break; + } + } + + if (!Impostors.Contains(pc)) + { + ImpostorMultiplier[Utils.GetHashPuid(pc)] += 15; + Impostors.Add(pc); + pc.RpcSetRole(RoleTypes.Impostor); + pc.roleAssigned = true; + Logger.Log($"Assigned {Utils.GetRoleName(RoleTypes.Impostor)} role to {pc.Data.PlayerName}", "RoleManager"); + } + } + else + { + var crewRoles = Shuffle(CrewmateRoles); + foreach (var kvp in crewRoles) + { + if (RNG() < GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(kvp.Key) && kvp.Value > 0) + { + ImpostorMultiplier[Utils.GetHashPuid(pc)] = 0; + CrewmateRoles[kvp.Key]--; + Crewmates.Add(pc); + pc.RpcSetRole(kvp.Key); + pc.roleAssigned = true; + Logger.Log($"Assigned {Utils.GetRoleName(kvp.Key)} role to {pc.Data.PlayerName}", "RoleManager"); + break; + } + } + + if (!Crewmates.Contains(pc)) + { + ImpostorMultiplier[Utils.GetHashPuid(pc)] = 0; + Crewmates.Add(pc); + pc.RpcSetRole(RoleTypes.Crewmate); + pc.roleAssigned = true; + Logger.Log($"Assigned {Utils.GetRoleName(RoleTypes.Crewmate)} role to {pc.Data.PlayerName}", "RoleManager"); + } + } + } + + SetPlayerRole.Clear(); + RPC.SyncAllNames(); + + Logger.Log($"--------------------------------------------", "RoleManager"); + Logger.Log($" Better Role Assignment Has Finished!", "RoleManager"); + + return false; + } + + private static bool IsImpostorRole(RoleTypes role) => role is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom; + + private static Dictionary Shuffle(Dictionary dictionary) + { + List keys = dictionary.Keys.ToList(); + + // Fisher-Yates shuffle algorithm + for (int i = keys.Count - 1; i > 0; i--) + { + int j = random.Next(0, i + 1); + TKey temp = keys[i]; + keys[i] = keys[j]; + keys[j] = temp; + } + + // Rebuild dictionary with shuffled keys + Dictionary shuffledDictionary = new Dictionary(); + foreach (var key in keys) + { + shuffledDictionary[key] = dictionary[key]; + } + + return shuffledDictionary; + } + + private static int RNG() => random.Next(0, 100); +} \ No newline at end of file diff --git a/Patches/VersionShowerPatch.cs b/Patches/VersionShowerPatch.cs new file mode 100644 index 0000000..f952dfb --- /dev/null +++ b/Patches/VersionShowerPatch.cs @@ -0,0 +1,14 @@ +using HarmonyLib; + +namespace BetterAmongUs.Patches; + +[HarmonyPatch(typeof(VersionShower))] +public class VersionShowerPatch +{ + [HarmonyPatch(nameof(VersionShower.Start))] + [HarmonyPostfix] + public static void Postfix(VersionShower __instance) + { + __instance.text.text = $"♻BetterAmongUs♻ {Main.GetVersionText()} - " + __instance.text.text; + } +} diff --git a/Resources/DebugWindowRes/ReactorCoroutines/Coroutines.cs b/Resources/DebugWindowRes/ReactorCoroutines/Coroutines.cs new file mode 100644 index 0000000..51fde9c --- /dev/null +++ b/Resources/DebugWindowRes/ReactorCoroutines/Coroutines.cs @@ -0,0 +1,56 @@ +using BepInEx.Unity.IL2CPP.Utils; +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace BetterAmongUs.Resources; + +public static class Coroutines +{ + internal sealed class Component : MonoBehaviour + { + internal static Component? Instance { get; set; } + + public Component(IntPtr ptr) : base(ptr) + { + } + + private void Awake() + { + Instance = this; + } + + private void OnDestroy() + { + Instance = null; + } + } + + internal static readonly ConditionalWeakTable _ourCoroutineStore = new(); + + internal static IEnumerator GenericRoutine(IEnumerator coroutine) + { + yield return coroutine; + _ourCoroutineStore.Remove(coroutine); + } + + [return: NotNullIfNotNull("coroutine")] + public static IEnumerator? Start(IEnumerator? coroutine) + { + if (coroutine != null) + { + _ourCoroutineStore.AddOrUpdate(coroutine, Component.Instance!.StartCoroutine(GenericRoutine(coroutine))); + } + + return coroutine; + } + + public static void Stop(IEnumerator? coroutine) + { + if (coroutine != null && _ourCoroutineStore.TryGetValue(coroutine, out var routine)) + { + Component.Instance!.StopCoroutine(routine); + } + } +} \ No newline at end of file diff --git a/Resources/DebugWindowRes/ReactorImGui/DragWindow.cs b/Resources/DebugWindowRes/ReactorImGui/DragWindow.cs new file mode 100644 index 0000000..9c80203 --- /dev/null +++ b/Resources/DebugWindowRes/ReactorImGui/DragWindow.cs @@ -0,0 +1,20 @@ +using UnityEngine; + +namespace BetterAmongUs.Resources; + +public class DragWindow : Window +{ + public DragWindow(Rect rect, string title, Action func) : base(rect, title, func) + { + Func = id => + { + func(id); + + GUI.DragWindow(new Rect(0, 0, 10000, 20)); + Rect = Rect.ClampScreen(); + }; + } + public DragWindow(Rect rect, string title, Action func) : this(rect, title, _ => func()) + { + } +} diff --git a/Resources/DebugWindowRes/ReactorImGui/GUIExtensions.cs b/Resources/DebugWindowRes/ReactorImGui/GUIExtensions.cs new file mode 100644 index 0000000..4eefc1a --- /dev/null +++ b/Resources/DebugWindowRes/ReactorImGui/GUIExtensions.cs @@ -0,0 +1,29 @@ +using UnityEngine; + +namespace BetterAmongUs.Resources; + +public static class GUIExtensions +{ + public static Rect ClampScreen(this Rect rect) + { + rect.x = Mathf.Clamp(rect.x, 0, Screen.width - rect.width); + rect.y = Mathf.Clamp(rect.y, 0, Screen.height - rect.height); + + return rect; + } + public static Rect ResetSize(this Rect rect) + { + rect.width = rect.height = 0; + + return rect; + } + public static Sprite CreateSprite(this Texture2D texture, Vector2? pivot = null, float pixelsPerUnit = 100f) + { + return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), pivot ?? new Vector2(0.5f, 0.5f), pixelsPerUnit); + } + public static void SetSize(this RectTransform rectTransform, float width, float height) + { + rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width); + rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height); + } +} diff --git a/Resources/DebugWindowRes/ReactorImGui/Window.cs b/Resources/DebugWindowRes/ReactorImGui/Window.cs new file mode 100644 index 0000000..62f0e1e --- /dev/null +++ b/Resources/DebugWindowRes/ReactorImGui/Window.cs @@ -0,0 +1,44 @@ +using UnityEngine; + +namespace BetterAmongUs.Resources; + +public class Window +{ + private static int _lastWindowId; + public static int NextWindowId() + { + return _lastWindowId++; + } + public int Id { get; set; } = NextWindowId(); + public bool Enabled { get; set; } = true; + public Rect Rect { get; set; } + public Action Func { get; set; } + public string Title { get; set; } + public Window(Rect rect, string title, Action func) + { + Rect = rect; + Title = title; + Func = func; + } + public Window(Rect rect, string title, Action func) : this(rect, title, _ => func()) + { + } + public virtual void OnGUI() + { + if (Enabled) + { + if (Event.current.type == EventType.Layout) + { + Rect = Rect.ResetSize(); + } + + GUI.skin.label.wordWrap = false; + Rect = GUILayout.Window(Id, Rect, Func, Title, GUILayout.MinWidth(GUI.skin.label.CalcSize(new GUIContent(Title)).x * 2)); + + if ((Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1)) && Rect.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y))) + { + Input.ResetInputAxes(); + } + } + } +} \ No newline at end of file diff --git a/Resources/Images/BetterAmongUs-Icon.png b/Resources/Images/BetterAmongUs-Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..23b3ce8dc133d3b72e1d16135b316f669afc3dd1 GIT binary patch literal 35047 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 OJM^@SHEYzJBL4%6uma5h literal 0 HcmV?d00001 diff --git a/Resources/Images/BetterAmongUs-Mod.png b/Resources/Images/BetterAmongUs-Mod.png new file mode 100644 index 0000000000000000000000000000000000000000..761fcccb3f6bbfe3548042e79fbac99e807acf08 GIT binary patch literal 37268 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 LNkvXXu0mjfBXPQq literal 0 HcmV?d00001 diff --git a/main.cs b/main.cs new file mode 100644 index 0000000..abf5e9c --- /dev/null +++ b/main.cs @@ -0,0 +1,134 @@ +using UnityEngine; +using BepInEx; +using BepInEx.Configuration; +using BepInEx.Unity.IL2CPP; +using BepInEx.Logging; +using HarmonyLib; +using Innersloth.IO; +using Il2CppInterop.Runtime.Injection; + +namespace BetterAmongUs; + +public enum ReleaseTypes : int +{ + Release, + Canary, + Dev, +} + +[BepInPlugin(PluginGuid, "BetterAmongUs", PluginVersion)] +[BepInProcess("Among Us.exe")] +public class Main : BasePlugin +{ + public const ReleaseTypes ReleaseBuildType = ReleaseTypes.Canary; + public const string CanaryNum = "1"; + public const string PluginGuid = "com.d1gq.betteramongus"; + public const string PluginVersion = "1.0.0"; + public const string ReleaseDate = "07.27.2024"; // dd/mm/yyyy + public const string Github = "https://github.com/D1GQ/BetterAmongUs"; + public static string GetVersionText() + { + if (ReleaseBuildType == ReleaseTypes.Release) + return $"v{BetterAmongUsVersion}"; + else if (ReleaseBuildType == ReleaseTypes.Canary) + return $"v{BetterAmongUsVersion} Canary {Main.CanaryNum}"; + else if (Main.ReleaseBuildType == ReleaseTypes.Dev) + return $"v{BetterAmongUsVersion} Dev {Main.ReleaseDate}"; + } + public Harmony Harmony { get; } = new Harmony(PluginGuid); + + public static string BetterAmongUsVersion => PluginVersion; + public static string AmongUsVersion => Application.version; + + public static List SupportedAmongUsVersions = + [ + "2024.6.18", + ]; + + public static string[] DevUser = + [ + "8f23c48e2", + ]; + + public static PlayerControl[] AllPlayerControls => PlayerControl.AllPlayerControls.ToArray().Where(pc => pc != null).ToArray(); + + public static PlayerControl[] AllAlivePlayerControls => AllPlayerControls.ToArray().Where(pc => pc.IsAlive()).ToArray(); + + public static Dictionary GetRoleName = new Dictionary + { + { 0, "Crewmate" }, + { 1, "Impostor" }, + { 2, "Scientist" }, + { 3, "Engineer" }, + { 4, "Guardian Angel" }, + { 5, "Shapeshifter" }, + { 6, "Crewmate" }, + { 7, "Impostor" }, + { 8, "Noisemaker" }, + { 9, "Phantom" }, + { 10, "Tracker" } + }; + + public static ManualLogSource Logger; + public static DebugMenu debugmenu { get; set; } = null; + + public override void Load() + { + try + { + Logger = BepInEx.Logging.Logger.CreateLogSource(PluginGuid); + + Harmony.PatchAll(); + BAUDataManager.SetUp(); + BAUDataManager.LoadCheatData(); + LoadOptions(); + + if (File.Exists(Path.Combine($"{Environment.CurrentDirectory}/Among Us_Data/Plugins/x86", "steam_api.dll"))) + File.WriteAllText(Path.Combine(Environment.CurrentDirectory, "steam_appid.txt"), "945360"); + + if (File.Exists(Path.Combine(Environment.CurrentDirectory, "better-log.txt"))) + File.WriteAllText(Path.Combine(Environment.CurrentDirectory, "better-previous-log.txt"), File.ReadAllText(Path.Combine(Environment.CurrentDirectory, "better-log.txt"))); + + File.WriteAllText(Path.Combine(Environment.CurrentDirectory, "better-log.txt"), ""); + BetterAmongUs.Logger.Log("Better Among Us successfully loaded!"); + + // Set up debug menu + ClassInjector.RegisterTypeInIl2Cpp(); + ClassInjector.RegisterTypeInIl2Cpp(); + debugmenu = AddComponent(); + AddComponent(); + + string SupportedVersions = string.Empty; + foreach (string text in SupportedAmongUsVersions.ToArray()) + SupportedVersions += $"{text} "; + BetterAmongUs.Logger.Log($"BetterAmongUs {BetterAmongUsVersion}-{ReleaseDate} - [{AmongUsVersion} --> {SupportedVersions.Substring(0, SupportedVersions.Length - 1)}]"); + } + catch (Exception ex) + { + BetterAmongUs.Logger.Error(ex.ToString()); + } + } + + public static ConfigEntry AntiCheat { get; private set; } + public static ConfigEntry BetterHost { get; private set; } + public static ConfigEntry BetterRoleAlgorithma { get; private set; } + public static ConfigEntry LobbyPlayerInfo { get; private set; } + public static ConfigEntry DisableLobbyTheme { get; private set; } + public static ConfigEntry UnlockFPS { get; private set; } + public static ConfigEntry ShowFPS { get; private set; } + public static ConfigEntry CommandPrefix { get; set; } + private void LoadOptions() + { + AntiCheat = Config.Bind("Better Options", "AntiCheat", true); + BetterHost = Config.Bind("Better Options", "BetterHost", false); + BetterRoleAlgorithma = Config.Bind("Better Options", "BetterRoleAlgorithma", true); + LobbyPlayerInfo = Config.Bind("Better Options", "LobbyPlayerInfo", true); + DisableLobbyTheme = Config.Bind("Better Options", "DisableLobbyTheme", true); + UnlockFPS = Config.Bind("Better Options", "UnlockFPS", false); + ShowFPS = Config.Bind("Better Options", "ShowFPS", false); + CommandPrefix = Config.Bind("Client Options", "CommandPrefix", "/"); + } + + public static string GetDataPathToAmongUs() => FileIO.GetRootDataPath(); + public static string GetGamePathToAmongUs() => Environment.CurrentDirectory; +}