init
This commit is contained in:
commit
215030f626
150
.gitignore
vendored
Normal file
150
.gitignore
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
### Windows
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Shortcuts
|
||||
*.lnk
|
||||
|
||||
### OSX
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear on external disk
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### Visual Studio
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
build
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
### IDA
|
||||
*.id0
|
||||
*.id1
|
||||
*.id2
|
||||
*.nam
|
||||
*.til
|
||||
|
||||
### Custom user files
|
||||
# User scripts
|
||||
user*.bat
|
||||
|
||||
# Premake binary
|
||||
#premake5.exe
|
37
.gitmodules
vendored
Normal file
37
.gitmodules
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
[submodule "deps/asmjit"]
|
||||
path = deps/asmjit
|
||||
url = https://github.com/asmjit/asmjit.git
|
||||
[submodule "deps/discord-rpc"]
|
||||
path = deps/discord-rpc
|
||||
url = https://git.alterware.dev/AlterWare/discord-rpc.git
|
||||
[submodule "deps/gsc-tool"]
|
||||
path = deps/gsc-tool
|
||||
url = https://github.com/xensik/gsc-tool.git
|
||||
[submodule "deps/GSL"]
|
||||
path = deps/GSL
|
||||
url = https://github.com/microsoft/GSL.git
|
||||
[submodule "deps/libtomcrypt"]
|
||||
path = deps/libtomcrypt
|
||||
url = https://github.com/libtom/libtomcrypt.git
|
||||
[submodule "deps/libtommath"]
|
||||
path = deps/libtommath
|
||||
url = https://github.com/libtom/libtommath.git
|
||||
[submodule "deps/minhook"]
|
||||
path = deps/minhook
|
||||
url = https://github.com/TsudaKageyu/minhook.git
|
||||
[submodule "deps/protobuf"]
|
||||
path = deps/protobuf
|
||||
url = https://github.com/protocolbuffers/protobuf.git
|
||||
branch = 3.20.x
|
||||
[submodule "deps/rapidjson"]
|
||||
path = deps/rapidjson
|
||||
url = https://github.com/Tencent/rapidjson.git
|
||||
[submodule "deps/udis86"]
|
||||
path = deps/udis86
|
||||
url = https://github.com/vmt/udis86.git
|
||||
[submodule "deps/WinToast"]
|
||||
path = deps/WinToast
|
||||
url = https://github.com/mohabouje/WinToast.git
|
||||
[submodule "deps/zlib"]
|
||||
path = deps/zlib
|
||||
url = https://github.com/madler/zlib.git
|
674
LICENSE
Normal file
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
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
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
38
README.md
Normal file
38
README.md
Normal file
@ -0,0 +1,38 @@
|
||||
# S1: Client
|
||||
|
||||
This is a client modification for S1!
|
||||
**NOTE**: You must legally own Call of Duty®: Advanced Warfare® to run this mod. Cracked/Pirated versions of the game are **NOT** supported.
|
||||
|
||||
## Build
|
||||
- Install [Visual Studio 2022][vs-link] and enable `Desktop development with C++`
|
||||
- Install [Premake5][premake5-link] and add it to your system PATH
|
||||
- Clone this repository using [Git][git-link]
|
||||
- Update the submodules using ``git submodule update --init --recursive``
|
||||
- Run Premake with this options ``premake5 vs2022`` (Visual Studio 2022). No other build systems are supported.
|
||||
- Build via solution file in `build\s1-mod.sln`.
|
||||
|
||||
Only x64 is supported. Do not attempt to build for Windows ARM 64.
|
||||
|
||||
### Premake arguments
|
||||
|
||||
| Argument | Description |
|
||||
|:----------------------------|:-----------------------------------------------|
|
||||
| `--copy-to=PATH` | Optional, copy the EXE to a custom folder after build, define the path here if wanted. |
|
||||
| `--dev-build` | Enable development builds of the client. |
|
||||
|
||||
Contributions are welcome! Please follow the guidelines below:
|
||||
|
||||
- Sign [AlterWare CLA][cla-link] and send a pull request or email your patch at patches@alterware.dev
|
||||
- Make sure that PRs have only one commit, and deal with one issue only
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This software has been created purely for the purposes of
|
||||
academic research. It is not intended to be used to attack
|
||||
other systems. Project maintainers are not responsible or
|
||||
liable for misuse of the software. Use responsibly.
|
||||
|
||||
[premake5-link]: https://premake.github.io
|
||||
[git-link]: https://git-scm.com
|
||||
[vs-link]: https://visualstudio.microsoft.com/vs
|
||||
[cla-link]: https://alterware.dev/cla
|
1
deps/GSL
vendored
Submodule
1
deps/GSL
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit e64c97fc2cfc11992098bb38eda932de275e3f4d
|
1
deps/WinToast
vendored
Submodule
1
deps/WinToast
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 821c4818ade1aa4da56ac753285c159ce26fd597
|
1
deps/asmjit
vendored
Submodule
1
deps/asmjit
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 118ae6ced160f68dd142a0d758aa7efb4356196a
|
1
deps/discord-rpc
vendored
Submodule
1
deps/discord-rpc
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 963aa9f3e5ce81a4682c6ca3d136cddda614db33
|
5946
deps/extra/udis86/libudis86/itab.c
vendored
Normal file
5946
deps/extra/udis86/libudis86/itab.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
939
deps/extra/udis86/libudis86/itab.h
vendored
Normal file
939
deps/extra/udis86/libudis86/itab.h
vendored
Normal file
@ -0,0 +1,939 @@
|
||||
#ifndef UD_ITAB_H
|
||||
#define UD_ITAB_H
|
||||
|
||||
/* itab.h -- generated by udis86:scripts/ud_itab.py, do no edit */
|
||||
|
||||
/* ud_table_type -- lookup table types (see decode.c) */
|
||||
enum ud_table_type {
|
||||
UD_TAB__OPC_VEX,
|
||||
UD_TAB__OPC_TABLE,
|
||||
UD_TAB__OPC_X87,
|
||||
UD_TAB__OPC_MOD,
|
||||
UD_TAB__OPC_RM,
|
||||
UD_TAB__OPC_OSIZE,
|
||||
UD_TAB__OPC_MODE,
|
||||
UD_TAB__OPC_VEX_L,
|
||||
UD_TAB__OPC_3DNOW,
|
||||
UD_TAB__OPC_REG,
|
||||
UD_TAB__OPC_ASIZE,
|
||||
UD_TAB__OPC_VEX_W,
|
||||
UD_TAB__OPC_SSE,
|
||||
UD_TAB__OPC_VENDOR
|
||||
};
|
||||
|
||||
/* ud_mnemonic -- mnemonic constants */
|
||||
enum ud_mnemonic_code {
|
||||
UD_Iaaa,
|
||||
UD_Iaad,
|
||||
UD_Iaam,
|
||||
UD_Iaas,
|
||||
UD_Iadc,
|
||||
UD_Iadd,
|
||||
UD_Iaddpd,
|
||||
UD_Iaddps,
|
||||
UD_Iaddsd,
|
||||
UD_Iaddss,
|
||||
UD_Iaddsubpd,
|
||||
UD_Iaddsubps,
|
||||
UD_Iaesdec,
|
||||
UD_Iaesdeclast,
|
||||
UD_Iaesenc,
|
||||
UD_Iaesenclast,
|
||||
UD_Iaesimc,
|
||||
UD_Iaeskeygenassist,
|
||||
UD_Iand,
|
||||
UD_Iandnpd,
|
||||
UD_Iandnps,
|
||||
UD_Iandpd,
|
||||
UD_Iandps,
|
||||
UD_Iarpl,
|
||||
UD_Iblendpd,
|
||||
UD_Iblendps,
|
||||
UD_Iblendvpd,
|
||||
UD_Iblendvps,
|
||||
UD_Ibound,
|
||||
UD_Ibsf,
|
||||
UD_Ibsr,
|
||||
UD_Ibswap,
|
||||
UD_Ibt,
|
||||
UD_Ibtc,
|
||||
UD_Ibtr,
|
||||
UD_Ibts,
|
||||
UD_Icall,
|
||||
UD_Icbw,
|
||||
UD_Icdq,
|
||||
UD_Icdqe,
|
||||
UD_Iclc,
|
||||
UD_Icld,
|
||||
UD_Iclflush,
|
||||
UD_Iclgi,
|
||||
UD_Icli,
|
||||
UD_Iclts,
|
||||
UD_Icmc,
|
||||
UD_Icmova,
|
||||
UD_Icmovae,
|
||||
UD_Icmovb,
|
||||
UD_Icmovbe,
|
||||
UD_Icmovg,
|
||||
UD_Icmovge,
|
||||
UD_Icmovl,
|
||||
UD_Icmovle,
|
||||
UD_Icmovno,
|
||||
UD_Icmovnp,
|
||||
UD_Icmovns,
|
||||
UD_Icmovnz,
|
||||
UD_Icmovo,
|
||||
UD_Icmovp,
|
||||
UD_Icmovs,
|
||||
UD_Icmovz,
|
||||
UD_Icmp,
|
||||
UD_Icmppd,
|
||||
UD_Icmpps,
|
||||
UD_Icmpsb,
|
||||
UD_Icmpsd,
|
||||
UD_Icmpsq,
|
||||
UD_Icmpss,
|
||||
UD_Icmpsw,
|
||||
UD_Icmpxchg,
|
||||
UD_Icmpxchg16b,
|
||||
UD_Icmpxchg8b,
|
||||
UD_Icomisd,
|
||||
UD_Icomiss,
|
||||
UD_Icpuid,
|
||||
UD_Icqo,
|
||||
UD_Icrc32,
|
||||
UD_Icvtdq2pd,
|
||||
UD_Icvtdq2ps,
|
||||
UD_Icvtpd2dq,
|
||||
UD_Icvtpd2pi,
|
||||
UD_Icvtpd2ps,
|
||||
UD_Icvtpi2pd,
|
||||
UD_Icvtpi2ps,
|
||||
UD_Icvtps2dq,
|
||||
UD_Icvtps2pd,
|
||||
UD_Icvtps2pi,
|
||||
UD_Icvtsd2si,
|
||||
UD_Icvtsd2ss,
|
||||
UD_Icvtsi2sd,
|
||||
UD_Icvtsi2ss,
|
||||
UD_Icvtss2sd,
|
||||
UD_Icvtss2si,
|
||||
UD_Icvttpd2dq,
|
||||
UD_Icvttpd2pi,
|
||||
UD_Icvttps2dq,
|
||||
UD_Icvttps2pi,
|
||||
UD_Icvttsd2si,
|
||||
UD_Icvttss2si,
|
||||
UD_Icwd,
|
||||
UD_Icwde,
|
||||
UD_Idaa,
|
||||
UD_Idas,
|
||||
UD_Idec,
|
||||
UD_Idiv,
|
||||
UD_Idivpd,
|
||||
UD_Idivps,
|
||||
UD_Idivsd,
|
||||
UD_Idivss,
|
||||
UD_Idppd,
|
||||
UD_Idpps,
|
||||
UD_Iemms,
|
||||
UD_Ienter,
|
||||
UD_Iextractps,
|
||||
UD_If2xm1,
|
||||
UD_Ifabs,
|
||||
UD_Ifadd,
|
||||
UD_Ifaddp,
|
||||
UD_Ifbld,
|
||||
UD_Ifbstp,
|
||||
UD_Ifchs,
|
||||
UD_Ifclex,
|
||||
UD_Ifcmovb,
|
||||
UD_Ifcmovbe,
|
||||
UD_Ifcmove,
|
||||
UD_Ifcmovnb,
|
||||
UD_Ifcmovnbe,
|
||||
UD_Ifcmovne,
|
||||
UD_Ifcmovnu,
|
||||
UD_Ifcmovu,
|
||||
UD_Ifcom,
|
||||
UD_Ifcom2,
|
||||
UD_Ifcomi,
|
||||
UD_Ifcomip,
|
||||
UD_Ifcomp,
|
||||
UD_Ifcomp3,
|
||||
UD_Ifcomp5,
|
||||
UD_Ifcompp,
|
||||
UD_Ifcos,
|
||||
UD_Ifdecstp,
|
||||
UD_Ifdiv,
|
||||
UD_Ifdivp,
|
||||
UD_Ifdivr,
|
||||
UD_Ifdivrp,
|
||||
UD_Ifemms,
|
||||
UD_Iffree,
|
||||
UD_Iffreep,
|
||||
UD_Ifiadd,
|
||||
UD_Ificom,
|
||||
UD_Ificomp,
|
||||
UD_Ifidiv,
|
||||
UD_Ifidivr,
|
||||
UD_Ifild,
|
||||
UD_Ifimul,
|
||||
UD_Ifincstp,
|
||||
UD_Ifist,
|
||||
UD_Ifistp,
|
||||
UD_Ifisttp,
|
||||
UD_Ifisub,
|
||||
UD_Ifisubr,
|
||||
UD_Ifld,
|
||||
UD_Ifld1,
|
||||
UD_Ifldcw,
|
||||
UD_Ifldenv,
|
||||
UD_Ifldl2e,
|
||||
UD_Ifldl2t,
|
||||
UD_Ifldlg2,
|
||||
UD_Ifldln2,
|
||||
UD_Ifldpi,
|
||||
UD_Ifldz,
|
||||
UD_Ifmul,
|
||||
UD_Ifmulp,
|
||||
UD_Ifndisi,
|
||||
UD_Ifneni,
|
||||
UD_Ifninit,
|
||||
UD_Ifnop,
|
||||
UD_Ifnsave,
|
||||
UD_Ifnsetpm,
|
||||
UD_Ifnstcw,
|
||||
UD_Ifnstenv,
|
||||
UD_Ifnstsw,
|
||||
UD_Ifpatan,
|
||||
UD_Ifprem,
|
||||
UD_Ifprem1,
|
||||
UD_Ifptan,
|
||||
UD_Ifrndint,
|
||||
UD_Ifrstor,
|
||||
UD_Ifrstpm,
|
||||
UD_Ifscale,
|
||||
UD_Ifsin,
|
||||
UD_Ifsincos,
|
||||
UD_Ifsqrt,
|
||||
UD_Ifst,
|
||||
UD_Ifstp,
|
||||
UD_Ifstp1,
|
||||
UD_Ifstp8,
|
||||
UD_Ifstp9,
|
||||
UD_Ifsub,
|
||||
UD_Ifsubp,
|
||||
UD_Ifsubr,
|
||||
UD_Ifsubrp,
|
||||
UD_Iftst,
|
||||
UD_Ifucom,
|
||||
UD_Ifucomi,
|
||||
UD_Ifucomip,
|
||||
UD_Ifucomp,
|
||||
UD_Ifucompp,
|
||||
UD_Ifxam,
|
||||
UD_Ifxch,
|
||||
UD_Ifxch4,
|
||||
UD_Ifxch7,
|
||||
UD_Ifxrstor,
|
||||
UD_Ifxsave,
|
||||
UD_Ifxtract,
|
||||
UD_Ifyl2x,
|
||||
UD_Ifyl2xp1,
|
||||
UD_Igetsec,
|
||||
UD_Ihaddpd,
|
||||
UD_Ihaddps,
|
||||
UD_Ihlt,
|
||||
UD_Ihsubpd,
|
||||
UD_Ihsubps,
|
||||
UD_Iidiv,
|
||||
UD_Iimul,
|
||||
UD_Iin,
|
||||
UD_Iinc,
|
||||
UD_Iinsb,
|
||||
UD_Iinsd,
|
||||
UD_Iinsertps,
|
||||
UD_Iinsw,
|
||||
UD_Iint,
|
||||
UD_Iint1,
|
||||
UD_Iint3,
|
||||
UD_Iinto,
|
||||
UD_Iinvd,
|
||||
UD_Iinvept,
|
||||
UD_Iinvlpg,
|
||||
UD_Iinvlpga,
|
||||
UD_Iinvvpid,
|
||||
UD_Iiretd,
|
||||
UD_Iiretq,
|
||||
UD_Iiretw,
|
||||
UD_Ija,
|
||||
UD_Ijae,
|
||||
UD_Ijb,
|
||||
UD_Ijbe,
|
||||
UD_Ijcxz,
|
||||
UD_Ijecxz,
|
||||
UD_Ijg,
|
||||
UD_Ijge,
|
||||
UD_Ijl,
|
||||
UD_Ijle,
|
||||
UD_Ijmp,
|
||||
UD_Ijno,
|
||||
UD_Ijnp,
|
||||
UD_Ijns,
|
||||
UD_Ijnz,
|
||||
UD_Ijo,
|
||||
UD_Ijp,
|
||||
UD_Ijrcxz,
|
||||
UD_Ijs,
|
||||
UD_Ijz,
|
||||
UD_Ilahf,
|
||||
UD_Ilar,
|
||||
UD_Ilddqu,
|
||||
UD_Ildmxcsr,
|
||||
UD_Ilds,
|
||||
UD_Ilea,
|
||||
UD_Ileave,
|
||||
UD_Iles,
|
||||
UD_Ilfence,
|
||||
UD_Ilfs,
|
||||
UD_Ilgdt,
|
||||
UD_Ilgs,
|
||||
UD_Ilidt,
|
||||
UD_Illdt,
|
||||
UD_Ilmsw,
|
||||
UD_Ilock,
|
||||
UD_Ilodsb,
|
||||
UD_Ilodsd,
|
||||
UD_Ilodsq,
|
||||
UD_Ilodsw,
|
||||
UD_Iloop,
|
||||
UD_Iloope,
|
||||
UD_Iloopne,
|
||||
UD_Ilsl,
|
||||
UD_Ilss,
|
||||
UD_Iltr,
|
||||
UD_Imaskmovdqu,
|
||||
UD_Imaskmovq,
|
||||
UD_Imaxpd,
|
||||
UD_Imaxps,
|
||||
UD_Imaxsd,
|
||||
UD_Imaxss,
|
||||
UD_Imfence,
|
||||
UD_Iminpd,
|
||||
UD_Iminps,
|
||||
UD_Iminsd,
|
||||
UD_Iminss,
|
||||
UD_Imonitor,
|
||||
UD_Imontmul,
|
||||
UD_Imov,
|
||||
UD_Imovapd,
|
||||
UD_Imovaps,
|
||||
UD_Imovbe,
|
||||
UD_Imovd,
|
||||
UD_Imovddup,
|
||||
UD_Imovdq2q,
|
||||
UD_Imovdqa,
|
||||
UD_Imovdqu,
|
||||
UD_Imovhlps,
|
||||
UD_Imovhpd,
|
||||
UD_Imovhps,
|
||||
UD_Imovlhps,
|
||||
UD_Imovlpd,
|
||||
UD_Imovlps,
|
||||
UD_Imovmskpd,
|
||||
UD_Imovmskps,
|
||||
UD_Imovntdq,
|
||||
UD_Imovntdqa,
|
||||
UD_Imovnti,
|
||||
UD_Imovntpd,
|
||||
UD_Imovntps,
|
||||
UD_Imovntq,
|
||||
UD_Imovq,
|
||||
UD_Imovq2dq,
|
||||
UD_Imovsb,
|
||||
UD_Imovsd,
|
||||
UD_Imovshdup,
|
||||
UD_Imovsldup,
|
||||
UD_Imovsq,
|
||||
UD_Imovss,
|
||||
UD_Imovsw,
|
||||
UD_Imovsx,
|
||||
UD_Imovsxd,
|
||||
UD_Imovupd,
|
||||
UD_Imovups,
|
||||
UD_Imovzx,
|
||||
UD_Impsadbw,
|
||||
UD_Imul,
|
||||
UD_Imulpd,
|
||||
UD_Imulps,
|
||||
UD_Imulsd,
|
||||
UD_Imulss,
|
||||
UD_Imwait,
|
||||
UD_Ineg,
|
||||
UD_Inop,
|
||||
UD_Inot,
|
||||
UD_Ior,
|
||||
UD_Iorpd,
|
||||
UD_Iorps,
|
||||
UD_Iout,
|
||||
UD_Ioutsb,
|
||||
UD_Ioutsd,
|
||||
UD_Ioutsw,
|
||||
UD_Ipabsb,
|
||||
UD_Ipabsd,
|
||||
UD_Ipabsw,
|
||||
UD_Ipackssdw,
|
||||
UD_Ipacksswb,
|
||||
UD_Ipackusdw,
|
||||
UD_Ipackuswb,
|
||||
UD_Ipaddb,
|
||||
UD_Ipaddd,
|
||||
UD_Ipaddq,
|
||||
UD_Ipaddsb,
|
||||
UD_Ipaddsw,
|
||||
UD_Ipaddusb,
|
||||
UD_Ipaddusw,
|
||||
UD_Ipaddw,
|
||||
UD_Ipalignr,
|
||||
UD_Ipand,
|
||||
UD_Ipandn,
|
||||
UD_Ipavgb,
|
||||
UD_Ipavgusb,
|
||||
UD_Ipavgw,
|
||||
UD_Ipblendvb,
|
||||
UD_Ipblendw,
|
||||
UD_Ipclmulqdq,
|
||||
UD_Ipcmpeqb,
|
||||
UD_Ipcmpeqd,
|
||||
UD_Ipcmpeqq,
|
||||
UD_Ipcmpeqw,
|
||||
UD_Ipcmpestri,
|
||||
UD_Ipcmpestrm,
|
||||
UD_Ipcmpgtb,
|
||||
UD_Ipcmpgtd,
|
||||
UD_Ipcmpgtq,
|
||||
UD_Ipcmpgtw,
|
||||
UD_Ipcmpistri,
|
||||
UD_Ipcmpistrm,
|
||||
UD_Ipextrb,
|
||||
UD_Ipextrd,
|
||||
UD_Ipextrq,
|
||||
UD_Ipextrw,
|
||||
UD_Ipf2id,
|
||||
UD_Ipf2iw,
|
||||
UD_Ipfacc,
|
||||
UD_Ipfadd,
|
||||
UD_Ipfcmpeq,
|
||||
UD_Ipfcmpge,
|
||||
UD_Ipfcmpgt,
|
||||
UD_Ipfmax,
|
||||
UD_Ipfmin,
|
||||
UD_Ipfmul,
|
||||
UD_Ipfnacc,
|
||||
UD_Ipfpnacc,
|
||||
UD_Ipfrcp,
|
||||
UD_Ipfrcpit1,
|
||||
UD_Ipfrcpit2,
|
||||
UD_Ipfrsqit1,
|
||||
UD_Ipfrsqrt,
|
||||
UD_Ipfsub,
|
||||
UD_Ipfsubr,
|
||||
UD_Iphaddd,
|
||||
UD_Iphaddsw,
|
||||
UD_Iphaddw,
|
||||
UD_Iphminposuw,
|
||||
UD_Iphsubd,
|
||||
UD_Iphsubsw,
|
||||
UD_Iphsubw,
|
||||
UD_Ipi2fd,
|
||||
UD_Ipi2fw,
|
||||
UD_Ipinsrb,
|
||||
UD_Ipinsrd,
|
||||
UD_Ipinsrq,
|
||||
UD_Ipinsrw,
|
||||
UD_Ipmaddubsw,
|
||||
UD_Ipmaddwd,
|
||||
UD_Ipmaxsb,
|
||||
UD_Ipmaxsd,
|
||||
UD_Ipmaxsw,
|
||||
UD_Ipmaxub,
|
||||
UD_Ipmaxud,
|
||||
UD_Ipmaxuw,
|
||||
UD_Ipminsb,
|
||||
UD_Ipminsd,
|
||||
UD_Ipminsw,
|
||||
UD_Ipminub,
|
||||
UD_Ipminud,
|
||||
UD_Ipminuw,
|
||||
UD_Ipmovmskb,
|
||||
UD_Ipmovsxbd,
|
||||
UD_Ipmovsxbq,
|
||||
UD_Ipmovsxbw,
|
||||
UD_Ipmovsxdq,
|
||||
UD_Ipmovsxwd,
|
||||
UD_Ipmovsxwq,
|
||||
UD_Ipmovzxbd,
|
||||
UD_Ipmovzxbq,
|
||||
UD_Ipmovzxbw,
|
||||
UD_Ipmovzxdq,
|
||||
UD_Ipmovzxwd,
|
||||
UD_Ipmovzxwq,
|
||||
UD_Ipmuldq,
|
||||
UD_Ipmulhrsw,
|
||||
UD_Ipmulhrw,
|
||||
UD_Ipmulhuw,
|
||||
UD_Ipmulhw,
|
||||
UD_Ipmulld,
|
||||
UD_Ipmullw,
|
||||
UD_Ipmuludq,
|
||||
UD_Ipop,
|
||||
UD_Ipopa,
|
||||
UD_Ipopad,
|
||||
UD_Ipopcnt,
|
||||
UD_Ipopfd,
|
||||
UD_Ipopfq,
|
||||
UD_Ipopfw,
|
||||
UD_Ipor,
|
||||
UD_Iprefetch,
|
||||
UD_Iprefetchnta,
|
||||
UD_Iprefetcht0,
|
||||
UD_Iprefetcht1,
|
||||
UD_Iprefetcht2,
|
||||
UD_Ipsadbw,
|
||||
UD_Ipshufb,
|
||||
UD_Ipshufd,
|
||||
UD_Ipshufhw,
|
||||
UD_Ipshuflw,
|
||||
UD_Ipshufw,
|
||||
UD_Ipsignb,
|
||||
UD_Ipsignd,
|
||||
UD_Ipsignw,
|
||||
UD_Ipslld,
|
||||
UD_Ipslldq,
|
||||
UD_Ipsllq,
|
||||
UD_Ipsllw,
|
||||
UD_Ipsrad,
|
||||
UD_Ipsraw,
|
||||
UD_Ipsrld,
|
||||
UD_Ipsrldq,
|
||||
UD_Ipsrlq,
|
||||
UD_Ipsrlw,
|
||||
UD_Ipsubb,
|
||||
UD_Ipsubd,
|
||||
UD_Ipsubq,
|
||||
UD_Ipsubsb,
|
||||
UD_Ipsubsw,
|
||||
UD_Ipsubusb,
|
||||
UD_Ipsubusw,
|
||||
UD_Ipsubw,
|
||||
UD_Ipswapd,
|
||||
UD_Iptest,
|
||||
UD_Ipunpckhbw,
|
||||
UD_Ipunpckhdq,
|
||||
UD_Ipunpckhqdq,
|
||||
UD_Ipunpckhwd,
|
||||
UD_Ipunpcklbw,
|
||||
UD_Ipunpckldq,
|
||||
UD_Ipunpcklqdq,
|
||||
UD_Ipunpcklwd,
|
||||
UD_Ipush,
|
||||
UD_Ipusha,
|
||||
UD_Ipushad,
|
||||
UD_Ipushfd,
|
||||
UD_Ipushfq,
|
||||
UD_Ipushfw,
|
||||
UD_Ipxor,
|
||||
UD_Ircl,
|
||||
UD_Ircpps,
|
||||
UD_Ircpss,
|
||||
UD_Ircr,
|
||||
UD_Irdmsr,
|
||||
UD_Irdpmc,
|
||||
UD_Irdrand,
|
||||
UD_Irdtsc,
|
||||
UD_Irdtscp,
|
||||
UD_Irep,
|
||||
UD_Irepne,
|
||||
UD_Iret,
|
||||
UD_Iretf,
|
||||
UD_Irol,
|
||||
UD_Iror,
|
||||
UD_Iroundpd,
|
||||
UD_Iroundps,
|
||||
UD_Iroundsd,
|
||||
UD_Iroundss,
|
||||
UD_Irsm,
|
||||
UD_Irsqrtps,
|
||||
UD_Irsqrtss,
|
||||
UD_Isahf,
|
||||
UD_Isalc,
|
||||
UD_Isar,
|
||||
UD_Isbb,
|
||||
UD_Iscasb,
|
||||
UD_Iscasd,
|
||||
UD_Iscasq,
|
||||
UD_Iscasw,
|
||||
UD_Iseta,
|
||||
UD_Isetae,
|
||||
UD_Isetb,
|
||||
UD_Isetbe,
|
||||
UD_Isetg,
|
||||
UD_Isetge,
|
||||
UD_Isetl,
|
||||
UD_Isetle,
|
||||
UD_Isetno,
|
||||
UD_Isetnp,
|
||||
UD_Isetns,
|
||||
UD_Isetnz,
|
||||
UD_Iseto,
|
||||
UD_Isetp,
|
||||
UD_Isets,
|
||||
UD_Isetz,
|
||||
UD_Isfence,
|
||||
UD_Isgdt,
|
||||
UD_Ishl,
|
||||
UD_Ishld,
|
||||
UD_Ishr,
|
||||
UD_Ishrd,
|
||||
UD_Ishufpd,
|
||||
UD_Ishufps,
|
||||
UD_Isidt,
|
||||
UD_Iskinit,
|
||||
UD_Isldt,
|
||||
UD_Ismsw,
|
||||
UD_Isqrtpd,
|
||||
UD_Isqrtps,
|
||||
UD_Isqrtsd,
|
||||
UD_Isqrtss,
|
||||
UD_Istc,
|
||||
UD_Istd,
|
||||
UD_Istgi,
|
||||
UD_Isti,
|
||||
UD_Istmxcsr,
|
||||
UD_Istosb,
|
||||
UD_Istosd,
|
||||
UD_Istosq,
|
||||
UD_Istosw,
|
||||
UD_Istr,
|
||||
UD_Isub,
|
||||
UD_Isubpd,
|
||||
UD_Isubps,
|
||||
UD_Isubsd,
|
||||
UD_Isubss,
|
||||
UD_Iswapgs,
|
||||
UD_Isyscall,
|
||||
UD_Isysenter,
|
||||
UD_Isysexit,
|
||||
UD_Isysret,
|
||||
UD_Itest,
|
||||
UD_Iucomisd,
|
||||
UD_Iucomiss,
|
||||
UD_Iud2,
|
||||
UD_Iunpckhpd,
|
||||
UD_Iunpckhps,
|
||||
UD_Iunpcklpd,
|
||||
UD_Iunpcklps,
|
||||
UD_Ivaddpd,
|
||||
UD_Ivaddps,
|
||||
UD_Ivaddsd,
|
||||
UD_Ivaddss,
|
||||
UD_Ivaddsubpd,
|
||||
UD_Ivaddsubps,
|
||||
UD_Ivaesdec,
|
||||
UD_Ivaesdeclast,
|
||||
UD_Ivaesenc,
|
||||
UD_Ivaesenclast,
|
||||
UD_Ivaesimc,
|
||||
UD_Ivaeskeygenassist,
|
||||
UD_Ivandnpd,
|
||||
UD_Ivandnps,
|
||||
UD_Ivandpd,
|
||||
UD_Ivandps,
|
||||
UD_Ivblendpd,
|
||||
UD_Ivblendps,
|
||||
UD_Ivblendvpd,
|
||||
UD_Ivblendvps,
|
||||
UD_Ivbroadcastsd,
|
||||
UD_Ivbroadcastss,
|
||||
UD_Ivcmppd,
|
||||
UD_Ivcmpps,
|
||||
UD_Ivcmpsd,
|
||||
UD_Ivcmpss,
|
||||
UD_Ivcomisd,
|
||||
UD_Ivcomiss,
|
||||
UD_Ivcvtdq2pd,
|
||||
UD_Ivcvtdq2ps,
|
||||
UD_Ivcvtpd2dq,
|
||||
UD_Ivcvtpd2ps,
|
||||
UD_Ivcvtps2dq,
|
||||
UD_Ivcvtps2pd,
|
||||
UD_Ivcvtsd2si,
|
||||
UD_Ivcvtsd2ss,
|
||||
UD_Ivcvtsi2sd,
|
||||
UD_Ivcvtsi2ss,
|
||||
UD_Ivcvtss2sd,
|
||||
UD_Ivcvtss2si,
|
||||
UD_Ivcvttpd2dq,
|
||||
UD_Ivcvttps2dq,
|
||||
UD_Ivcvttsd2si,
|
||||
UD_Ivcvttss2si,
|
||||
UD_Ivdivpd,
|
||||
UD_Ivdivps,
|
||||
UD_Ivdivsd,
|
||||
UD_Ivdivss,
|
||||
UD_Ivdppd,
|
||||
UD_Ivdpps,
|
||||
UD_Iverr,
|
||||
UD_Iverw,
|
||||
UD_Ivextractf128,
|
||||
UD_Ivextractps,
|
||||
UD_Ivhaddpd,
|
||||
UD_Ivhaddps,
|
||||
UD_Ivhsubpd,
|
||||
UD_Ivhsubps,
|
||||
UD_Ivinsertf128,
|
||||
UD_Ivinsertps,
|
||||
UD_Ivlddqu,
|
||||
UD_Ivmaskmovdqu,
|
||||
UD_Ivmaskmovpd,
|
||||
UD_Ivmaskmovps,
|
||||
UD_Ivmaxpd,
|
||||
UD_Ivmaxps,
|
||||
UD_Ivmaxsd,
|
||||
UD_Ivmaxss,
|
||||
UD_Ivmcall,
|
||||
UD_Ivmclear,
|
||||
UD_Ivminpd,
|
||||
UD_Ivminps,
|
||||
UD_Ivminsd,
|
||||
UD_Ivminss,
|
||||
UD_Ivmlaunch,
|
||||
UD_Ivmload,
|
||||
UD_Ivmmcall,
|
||||
UD_Ivmovapd,
|
||||
UD_Ivmovaps,
|
||||
UD_Ivmovd,
|
||||
UD_Ivmovddup,
|
||||
UD_Ivmovdqa,
|
||||
UD_Ivmovdqu,
|
||||
UD_Ivmovhlps,
|
||||
UD_Ivmovhpd,
|
||||
UD_Ivmovhps,
|
||||
UD_Ivmovlhps,
|
||||
UD_Ivmovlpd,
|
||||
UD_Ivmovlps,
|
||||
UD_Ivmovmskpd,
|
||||
UD_Ivmovmskps,
|
||||
UD_Ivmovntdq,
|
||||
UD_Ivmovntdqa,
|
||||
UD_Ivmovntpd,
|
||||
UD_Ivmovntps,
|
||||
UD_Ivmovq,
|
||||
UD_Ivmovsd,
|
||||
UD_Ivmovshdup,
|
||||
UD_Ivmovsldup,
|
||||
UD_Ivmovss,
|
||||
UD_Ivmovupd,
|
||||
UD_Ivmovups,
|
||||
UD_Ivmpsadbw,
|
||||
UD_Ivmptrld,
|
||||
UD_Ivmptrst,
|
||||
UD_Ivmread,
|
||||
UD_Ivmresume,
|
||||
UD_Ivmrun,
|
||||
UD_Ivmsave,
|
||||
UD_Ivmulpd,
|
||||
UD_Ivmulps,
|
||||
UD_Ivmulsd,
|
||||
UD_Ivmulss,
|
||||
UD_Ivmwrite,
|
||||
UD_Ivmxoff,
|
||||
UD_Ivmxon,
|
||||
UD_Ivorpd,
|
||||
UD_Ivorps,
|
||||
UD_Ivpabsb,
|
||||
UD_Ivpabsd,
|
||||
UD_Ivpabsw,
|
||||
UD_Ivpackssdw,
|
||||
UD_Ivpacksswb,
|
||||
UD_Ivpackusdw,
|
||||
UD_Ivpackuswb,
|
||||
UD_Ivpaddb,
|
||||
UD_Ivpaddd,
|
||||
UD_Ivpaddq,
|
||||
UD_Ivpaddsb,
|
||||
UD_Ivpaddsw,
|
||||
UD_Ivpaddusb,
|
||||
UD_Ivpaddusw,
|
||||
UD_Ivpaddw,
|
||||
UD_Ivpalignr,
|
||||
UD_Ivpand,
|
||||
UD_Ivpandn,
|
||||
UD_Ivpavgb,
|
||||
UD_Ivpavgw,
|
||||
UD_Ivpblendvb,
|
||||
UD_Ivpblendw,
|
||||
UD_Ivpclmulqdq,
|
||||
UD_Ivpcmpeqb,
|
||||
UD_Ivpcmpeqd,
|
||||
UD_Ivpcmpeqq,
|
||||
UD_Ivpcmpeqw,
|
||||
UD_Ivpcmpestri,
|
||||
UD_Ivpcmpestrm,
|
||||
UD_Ivpcmpgtb,
|
||||
UD_Ivpcmpgtd,
|
||||
UD_Ivpcmpgtq,
|
||||
UD_Ivpcmpgtw,
|
||||
UD_Ivpcmpistri,
|
||||
UD_Ivpcmpistrm,
|
||||
UD_Ivperm2f128,
|
||||
UD_Ivpermilpd,
|
||||
UD_Ivpermilps,
|
||||
UD_Ivpextrb,
|
||||
UD_Ivpextrd,
|
||||
UD_Ivpextrq,
|
||||
UD_Ivpextrw,
|
||||
UD_Ivphaddd,
|
||||
UD_Ivphaddsw,
|
||||
UD_Ivphaddw,
|
||||
UD_Ivphminposuw,
|
||||
UD_Ivphsubd,
|
||||
UD_Ivphsubsw,
|
||||
UD_Ivphsubw,
|
||||
UD_Ivpinsrb,
|
||||
UD_Ivpinsrd,
|
||||
UD_Ivpinsrq,
|
||||
UD_Ivpinsrw,
|
||||
UD_Ivpmaddubsw,
|
||||
UD_Ivpmaddwd,
|
||||
UD_Ivpmaxsb,
|
||||
UD_Ivpmaxsd,
|
||||
UD_Ivpmaxsw,
|
||||
UD_Ivpmaxub,
|
||||
UD_Ivpmaxud,
|
||||
UD_Ivpmaxuw,
|
||||
UD_Ivpminsb,
|
||||
UD_Ivpminsd,
|
||||
UD_Ivpminsw,
|
||||
UD_Ivpminub,
|
||||
UD_Ivpminud,
|
||||
UD_Ivpminuw,
|
||||
UD_Ivpmovmskb,
|
||||
UD_Ivpmovsxbd,
|
||||
UD_Ivpmovsxbq,
|
||||
UD_Ivpmovsxbw,
|
||||
UD_Ivpmovsxwd,
|
||||
UD_Ivpmovsxwq,
|
||||
UD_Ivpmovzxbd,
|
||||
UD_Ivpmovzxbq,
|
||||
UD_Ivpmovzxbw,
|
||||
UD_Ivpmovzxdq,
|
||||
UD_Ivpmovzxwd,
|
||||
UD_Ivpmovzxwq,
|
||||
UD_Ivpmuldq,
|
||||
UD_Ivpmulhrsw,
|
||||
UD_Ivpmulhuw,
|
||||
UD_Ivpmulhw,
|
||||
UD_Ivpmulld,
|
||||
UD_Ivpmullw,
|
||||
UD_Ivpor,
|
||||
UD_Ivpsadbw,
|
||||
UD_Ivpshufb,
|
||||
UD_Ivpshufd,
|
||||
UD_Ivpshufhw,
|
||||
UD_Ivpshuflw,
|
||||
UD_Ivpsignb,
|
||||
UD_Ivpsignd,
|
||||
UD_Ivpsignw,
|
||||
UD_Ivpslld,
|
||||
UD_Ivpslldq,
|
||||
UD_Ivpsllq,
|
||||
UD_Ivpsllw,
|
||||
UD_Ivpsrad,
|
||||
UD_Ivpsraw,
|
||||
UD_Ivpsrld,
|
||||
UD_Ivpsrldq,
|
||||
UD_Ivpsrlq,
|
||||
UD_Ivpsrlw,
|
||||
UD_Ivpsubb,
|
||||
UD_Ivpsubd,
|
||||
UD_Ivpsubq,
|
||||
UD_Ivpsubsb,
|
||||
UD_Ivpsubsw,
|
||||
UD_Ivpsubusb,
|
||||
UD_Ivpsubusw,
|
||||
UD_Ivpsubw,
|
||||
UD_Ivptest,
|
||||
UD_Ivpunpckhbw,
|
||||
UD_Ivpunpckhdq,
|
||||
UD_Ivpunpckhqdq,
|
||||
UD_Ivpunpckhwd,
|
||||
UD_Ivpunpcklbw,
|
||||
UD_Ivpunpckldq,
|
||||
UD_Ivpunpcklqdq,
|
||||
UD_Ivpunpcklwd,
|
||||
UD_Ivpxor,
|
||||
UD_Ivrcpps,
|
||||
UD_Ivrcpss,
|
||||
UD_Ivroundpd,
|
||||
UD_Ivroundps,
|
||||
UD_Ivroundsd,
|
||||
UD_Ivroundss,
|
||||
UD_Ivrsqrtps,
|
||||
UD_Ivrsqrtss,
|
||||
UD_Ivshufpd,
|
||||
UD_Ivshufps,
|
||||
UD_Ivsqrtpd,
|
||||
UD_Ivsqrtps,
|
||||
UD_Ivsqrtsd,
|
||||
UD_Ivsqrtss,
|
||||
UD_Ivstmxcsr,
|
||||
UD_Ivsubpd,
|
||||
UD_Ivsubps,
|
||||
UD_Ivsubsd,
|
||||
UD_Ivsubss,
|
||||
UD_Ivtestpd,
|
||||
UD_Ivtestps,
|
||||
UD_Ivucomisd,
|
||||
UD_Ivucomiss,
|
||||
UD_Ivunpckhpd,
|
||||
UD_Ivunpckhps,
|
||||
UD_Ivunpcklpd,
|
||||
UD_Ivunpcklps,
|
||||
UD_Ivxorpd,
|
||||
UD_Ivxorps,
|
||||
UD_Ivzeroall,
|
||||
UD_Ivzeroupper,
|
||||
UD_Iwait,
|
||||
UD_Iwbinvd,
|
||||
UD_Iwrmsr,
|
||||
UD_Ixadd,
|
||||
UD_Ixchg,
|
||||
UD_Ixcryptcbc,
|
||||
UD_Ixcryptcfb,
|
||||
UD_Ixcryptctr,
|
||||
UD_Ixcryptecb,
|
||||
UD_Ixcryptofb,
|
||||
UD_Ixgetbv,
|
||||
UD_Ixlatb,
|
||||
UD_Ixor,
|
||||
UD_Ixorpd,
|
||||
UD_Ixorps,
|
||||
UD_Ixrstor,
|
||||
UD_Ixsave,
|
||||
UD_Ixsetbv,
|
||||
UD_Ixsha1,
|
||||
UD_Ixsha256,
|
||||
UD_Ixstore,
|
||||
UD_Iinvalid,
|
||||
UD_I3dnow,
|
||||
UD_Inone,
|
||||
UD_Idb,
|
||||
UD_Ipause,
|
||||
UD_MAX_MNEMONIC_CODE
|
||||
};
|
||||
|
||||
extern const char * ud_mnemonics_str[];
|
||||
|
||||
#endif /* UD_ITAB_H */
|
1
deps/gsc-tool
vendored
Submodule
1
deps/gsc-tool
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 2dbff1a408096e418c5b3eda01bc9a91eee6587b
|
1
deps/libtomcrypt
vendored
Submodule
1
deps/libtomcrypt
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 7e863d21429f94ed6a720e24499a12a3f852bb31
|
1
deps/libtommath
vendored
Submodule
1
deps/libtommath
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 8314bde5e5c8e5d9331460130a9d1066e324f091
|
1
deps/minhook
vendored
Submodule
1
deps/minhook
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit f5485b8454544c2f034c78f8f127c1d03dea3636
|
34
deps/premake/asmjit.lua
vendored
Normal file
34
deps/premake/asmjit.lua
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
asmjit = {
|
||||
source = path.join(dependencies.basePath, "asmjit"),
|
||||
}
|
||||
|
||||
function asmjit.import()
|
||||
links { "asmjit" }
|
||||
asmjit.includes()
|
||||
end
|
||||
|
||||
function asmjit.includes()
|
||||
includedirs {
|
||||
path.join(asmjit.source, "src")
|
||||
}
|
||||
|
||||
defines {
|
||||
"ASMJIT_STATIC"
|
||||
}
|
||||
end
|
||||
|
||||
function asmjit.project()
|
||||
project "asmjit"
|
||||
language "C++"
|
||||
|
||||
asmjit.includes()
|
||||
|
||||
files {
|
||||
path.join(asmjit.source, "src/**.cpp"),
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, asmjit)
|
39
deps/premake/discord-rpc.lua
vendored
Normal file
39
deps/premake/discord-rpc.lua
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
discordrpc = {
|
||||
source = path.join(dependencies.basePath, "discord-rpc"),
|
||||
}
|
||||
|
||||
function discordrpc.import()
|
||||
links { "discord-rpc" }
|
||||
discordrpc.includes()
|
||||
end
|
||||
|
||||
function discordrpc.includes()
|
||||
includedirs {
|
||||
path.join(discordrpc.source, "include"),
|
||||
}
|
||||
end
|
||||
|
||||
function discordrpc.project()
|
||||
project "discord-rpc"
|
||||
language "C++"
|
||||
|
||||
discordrpc.includes()
|
||||
rapidjson.import();
|
||||
|
||||
files {
|
||||
path.join(discordrpc.source, "src/*.h"),
|
||||
path.join(discordrpc.source, "src/*.cpp"),
|
||||
}
|
||||
|
||||
removefiles {
|
||||
path.join(discordrpc.source, "src/dllmain.cpp"),
|
||||
path.join(discordrpc.source, "src/*_linux.cpp"),
|
||||
path.join(discordrpc.source, "src/*_unix.cpp"),
|
||||
path.join(discordrpc.source, "src/*_osx.cpp"),
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, discordrpc)
|
62
deps/premake/gsc-tool.lua
vendored
Normal file
62
deps/premake/gsc-tool.lua
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
gsc_tool = {
|
||||
source = path.join(dependencies.basePath, "gsc-tool"),
|
||||
}
|
||||
|
||||
function gsc_tool.import()
|
||||
links { "xsk-gsc-s1", "xsk-gsc-utils" }
|
||||
gsc_tool.includes()
|
||||
end
|
||||
|
||||
function gsc_tool.includes()
|
||||
includedirs {
|
||||
path.join(gsc_tool.source, "include"),
|
||||
}
|
||||
end
|
||||
|
||||
function gsc_tool.project()
|
||||
project "xsk-gsc-utils"
|
||||
kind "StaticLib"
|
||||
language "C++"
|
||||
|
||||
files {
|
||||
path.join(gsc_tool.source, "include/xsk/utils/*.hpp"),
|
||||
path.join(gsc_tool.source, "src/utils/*.cpp"),
|
||||
}
|
||||
|
||||
includedirs {
|
||||
path.join(gsc_tool.source, "include"),
|
||||
}
|
||||
|
||||
zlib.includes()
|
||||
|
||||
project "xsk-gsc-s1"
|
||||
kind "StaticLib"
|
||||
language "C++"
|
||||
|
||||
filter "action:vs*"
|
||||
buildoptions "/Zc:__cplusplus"
|
||||
filter {}
|
||||
|
||||
files {
|
||||
path.join(gsc_tool.source, "include/xsk/stdinc.hpp"),
|
||||
|
||||
path.join(gsc_tool.source, "include/xsk/gsc/engine/s1_pc.hpp"),
|
||||
path.join(gsc_tool.source, "src/gsc/engine/s1_pc.cpp"),
|
||||
|
||||
path.join(gsc_tool.source, "src/gsc/engine/s1_pc_code.cpp"),
|
||||
path.join(gsc_tool.source, "src/gsc/engine/s1_pc_func.cpp"),
|
||||
path.join(gsc_tool.source, "src/gsc/engine/s1_pc_meth.cpp"),
|
||||
path.join(gsc_tool.source, "src/gsc/engine/s1_pc_token.cpp"),
|
||||
|
||||
path.join(gsc_tool.source, "src/gsc/*.cpp"),
|
||||
|
||||
path.join(gsc_tool.source, "src/gsc/common/*.cpp"),
|
||||
path.join(gsc_tool.source, "include/xsk/gsc/common/*.hpp"),
|
||||
}
|
||||
|
||||
includedirs {
|
||||
path.join(gsc_tool.source, "include"),
|
||||
}
|
||||
end
|
||||
|
||||
table.insert(dependencies, gsc_tool)
|
19
deps/premake/gsl.lua
vendored
Normal file
19
deps/premake/gsl.lua
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
gsl = {
|
||||
source = path.join(dependencies.basePath, "GSL"),
|
||||
}
|
||||
|
||||
function gsl.import()
|
||||
gsl.includes()
|
||||
end
|
||||
|
||||
function gsl.includes()
|
||||
includedirs {
|
||||
path.join(gsl.source, "include")
|
||||
}
|
||||
end
|
||||
|
||||
function gsl.project()
|
||||
|
||||
end
|
||||
|
||||
table.insert(dependencies, gsl)
|
64
deps/premake/libtomcrypt.lua
vendored
Normal file
64
deps/premake/libtomcrypt.lua
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
libtomcrypt = {
|
||||
source = path.join(dependencies.basePath, "libtomcrypt"),
|
||||
}
|
||||
|
||||
function libtomcrypt.import()
|
||||
links {
|
||||
"libtomcrypt"
|
||||
}
|
||||
|
||||
libtomcrypt.includes()
|
||||
end
|
||||
|
||||
function libtomcrypt.includes()
|
||||
includedirs {
|
||||
path.join(libtomcrypt.source, "src/headers")
|
||||
}
|
||||
|
||||
defines {
|
||||
"LTC_NO_FAST",
|
||||
"LTC_NO_PROTOTYPES",
|
||||
"LTC_NO_RSA_BLINDING",
|
||||
"LTC_NO_FILE",
|
||||
"ARGTYPE=4",
|
||||
}
|
||||
end
|
||||
|
||||
function libtomcrypt.project()
|
||||
project "libtomcrypt"
|
||||
language "C"
|
||||
|
||||
libtomcrypt.includes()
|
||||
libtommath.import()
|
||||
|
||||
files {
|
||||
path.join(libtomcrypt.source, "src/**.c"),
|
||||
}
|
||||
|
||||
removefiles {
|
||||
path.join(libtomcrypt.source, "src/**/*_test.c"),
|
||||
path.join(libtomcrypt.source, "src/**/*tab.c"),
|
||||
path.join(libtomcrypt.source, "src/encauth/ocb3/**.c"),
|
||||
}
|
||||
|
||||
defines {
|
||||
"_CRT_SECURE_NO_WARNINGS",
|
||||
"LTC_SOURCE",
|
||||
"_LIB",
|
||||
"USE_LTM"
|
||||
}
|
||||
|
||||
removedefines {
|
||||
"_DLL",
|
||||
"_USRDLL"
|
||||
}
|
||||
|
||||
linkoptions {
|
||||
"-IGNORE:4221"
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, libtomcrypt)
|
52
deps/premake/libtommath.lua
vendored
Normal file
52
deps/premake/libtommath.lua
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
libtommath = {
|
||||
source = path.join(dependencies.basePath, "libtommath"),
|
||||
}
|
||||
|
||||
function libtommath.import()
|
||||
links {
|
||||
"libtommath"
|
||||
}
|
||||
|
||||
libtommath.includes()
|
||||
end
|
||||
|
||||
function libtommath.includes()
|
||||
includedirs {
|
||||
libtommath.source
|
||||
}
|
||||
|
||||
defines {
|
||||
"LTM_DESC",
|
||||
"__STDC_IEC_559__",
|
||||
"MP_NO_DEV_URANDOM",
|
||||
}
|
||||
end
|
||||
|
||||
function libtommath.project()
|
||||
project "libtommath"
|
||||
language "C"
|
||||
|
||||
libtommath.includes()
|
||||
|
||||
files {
|
||||
path.join(libtommath.source, "*.c"),
|
||||
}
|
||||
|
||||
defines {
|
||||
"_LIB"
|
||||
}
|
||||
|
||||
removedefines {
|
||||
"_DLL",
|
||||
"_USRDLL"
|
||||
}
|
||||
|
||||
linkoptions {
|
||||
"-IGNORE:4221"
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, libtommath)
|
31
deps/premake/minhook.lua
vendored
Normal file
31
deps/premake/minhook.lua
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
minhook = {
|
||||
source = path.join(dependencies.basePath, "minhook"),
|
||||
}
|
||||
|
||||
function minhook.import()
|
||||
links { "minhook" }
|
||||
minhook.includes()
|
||||
end
|
||||
|
||||
function minhook.includes()
|
||||
includedirs {
|
||||
path.join(minhook.source, "include")
|
||||
}
|
||||
end
|
||||
|
||||
function minhook.project()
|
||||
project "minhook"
|
||||
language "C"
|
||||
|
||||
minhook.includes()
|
||||
|
||||
files {
|
||||
path.join(minhook.source, "src/**.h"),
|
||||
path.join(minhook.source, "src/**.c"),
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, minhook)
|
43
deps/premake/minizip.lua
vendored
Normal file
43
deps/premake/minizip.lua
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
minizip = {
|
||||
source = path.join(dependencies.basePath, "zlib/contrib/minizip"),
|
||||
}
|
||||
|
||||
function minizip.import()
|
||||
links { "minizip" }
|
||||
zlib.import()
|
||||
minizip.includes()
|
||||
end
|
||||
|
||||
function minizip.includes()
|
||||
includedirs {
|
||||
minizip.source
|
||||
}
|
||||
|
||||
zlib.includes()
|
||||
end
|
||||
|
||||
function minizip.project()
|
||||
project "minizip"
|
||||
language "C"
|
||||
|
||||
minizip.includes()
|
||||
|
||||
files {
|
||||
path.join(minizip.source, "*.h"),
|
||||
path.join(minizip.source, "*.c"),
|
||||
}
|
||||
|
||||
removefiles {
|
||||
path.join(minizip.source, "miniunz.c"),
|
||||
path.join(minizip.source, "minizip.c"),
|
||||
}
|
||||
|
||||
defines {
|
||||
"_CRT_SECURE_NO_DEPRECATE",
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, minizip)
|
60
deps/premake/protobuf.lua
vendored
Normal file
60
deps/premake/protobuf.lua
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
protobuf = {
|
||||
source = path.join(dependencies.basePath, "protobuf"),
|
||||
}
|
||||
|
||||
function protobuf.import()
|
||||
links {
|
||||
"protobuf"
|
||||
}
|
||||
|
||||
protobuf.includes()
|
||||
end
|
||||
|
||||
function protobuf.includes()
|
||||
includedirs {
|
||||
path.join(protobuf.source, "src"),
|
||||
}
|
||||
end
|
||||
|
||||
function protobuf.project()
|
||||
project "protobuf"
|
||||
language "C++"
|
||||
|
||||
protobuf.includes()
|
||||
|
||||
files {
|
||||
path.join(protobuf.source, "src/**.cc"),
|
||||
"./src/**.proto",
|
||||
}
|
||||
|
||||
removefiles {
|
||||
path.join(protobuf.source, "src/**/*test.cc"),
|
||||
path.join(protobuf.source, "src/google/protobuf/*test*.cc"),
|
||||
|
||||
path.join(protobuf.source, "src/google/protobuf/testing/**.cc"),
|
||||
path.join(protobuf.source, "src/google/protobuf/compiler/**.cc"),
|
||||
|
||||
path.join(protobuf.source, "src/google/protobuf/arena_nc.cc"),
|
||||
path.join(protobuf.source, "src/google/protobuf/util/internal/error_listener.cc"),
|
||||
path.join(protobuf.source, "**/*_gcc.cc"),
|
||||
}
|
||||
|
||||
rules {
|
||||
"ProtobufCompiler"
|
||||
}
|
||||
|
||||
defines {
|
||||
"_SCL_SECURE_NO_WARNINGS",
|
||||
"_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS",
|
||||
"_SILENCE_ALL_CXX20_DEPRECATION_WARNINGS",
|
||||
}
|
||||
|
||||
linkoptions {
|
||||
"-IGNORE:4221"
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, protobuf)
|
23
deps/premake/rapidjson.lua
vendored
Normal file
23
deps/premake/rapidjson.lua
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
rapidjson = {
|
||||
source = path.join(dependencies.basePath, "rapidjson"),
|
||||
}
|
||||
|
||||
function rapidjson.import()
|
||||
defines {
|
||||
"RAPIDJSON_HAS_STDSTRING"
|
||||
}
|
||||
|
||||
rapidjson.includes()
|
||||
end
|
||||
|
||||
function rapidjson.includes()
|
||||
includedirs {
|
||||
path.join(rapidjson.source, "include"),
|
||||
}
|
||||
end
|
||||
|
||||
function rapidjson.project()
|
||||
|
||||
end
|
||||
|
||||
table.insert(dependencies, rapidjson)
|
37
deps/premake/udis86.lua
vendored
Normal file
37
deps/premake/udis86.lua
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
udis86 = {
|
||||
source = path.join(dependencies.basePath, "udis86"),
|
||||
}
|
||||
|
||||
function udis86.import()
|
||||
links {
|
||||
"udis86"
|
||||
}
|
||||
|
||||
udis86.includes()
|
||||
end
|
||||
|
||||
function udis86.includes()
|
||||
includedirs {
|
||||
udis86.source,
|
||||
path.join(udis86.source, "libudis86"),
|
||||
path.join(dependencies.basePath, "extra/udis86"),
|
||||
path.join(dependencies.basePath, "extra/udis86/libudis86"),
|
||||
}
|
||||
end
|
||||
|
||||
function udis86.project()
|
||||
project "udis86"
|
||||
language "C"
|
||||
|
||||
udis86.includes()
|
||||
|
||||
files {
|
||||
path.join(udis86.source, "libudis86/*.c"),
|
||||
path.join(dependencies.basePath, "extra/udis86/libudis86/*.c"),
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, udis86)
|
32
deps/premake/wintoast.lua
vendored
Normal file
32
deps/premake/wintoast.lua
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
wintoast = {
|
||||
source = path.join(dependencies.basePath, "WinToast"),
|
||||
}
|
||||
|
||||
function wintoast.import()
|
||||
links { "WinToast" }
|
||||
wintoast.includes()
|
||||
end
|
||||
|
||||
function wintoast.includes()
|
||||
includedirs {
|
||||
path.join(wintoast.source, "include"),
|
||||
}
|
||||
end
|
||||
|
||||
function wintoast.project()
|
||||
project "WinToast"
|
||||
language "C++"
|
||||
|
||||
wintoast.includes()
|
||||
rapidjson.import();
|
||||
|
||||
files {
|
||||
path.join(wintoast.source, "include/wintoastlib.h"),
|
||||
path.join(wintoast.source, "src/wintoastlib.cpp"),
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, wintoast)
|
39
deps/premake/zlib.lua
vendored
Normal file
39
deps/premake/zlib.lua
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
zlib = {
|
||||
source = path.join(dependencies.basePath, "zlib"),
|
||||
}
|
||||
|
||||
function zlib.import()
|
||||
links { "zlib" }
|
||||
zlib.includes()
|
||||
end
|
||||
|
||||
function zlib.includes()
|
||||
includedirs {
|
||||
zlib.source
|
||||
}
|
||||
|
||||
defines {
|
||||
"ZLIB_CONST",
|
||||
}
|
||||
end
|
||||
|
||||
function zlib.project()
|
||||
project "zlib"
|
||||
language "C"
|
||||
|
||||
zlib.includes()
|
||||
|
||||
files {
|
||||
path.join(zlib.source, "*.h"),
|
||||
path.join(zlib.source, "*.c"),
|
||||
}
|
||||
|
||||
defines {
|
||||
"_CRT_SECURE_NO_DEPRECATE",
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, zlib)
|
1
deps/protobuf
vendored
Submodule
1
deps/protobuf
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 5a3dac894157bf3618b2c906a8b9073b4cad62b6
|
1
deps/rapidjson
vendored
Submodule
1
deps/rapidjson
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 6089180ecb704cb2b136777798fa1be303618975
|
1
deps/udis86
vendored
Submodule
1
deps/udis86
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 56ff6c87c11de0ffa725b14339004820556e343d
|
1
deps/zlib
vendored
Submodule
1
deps/zlib
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 643e17b7498d12ab8d15565662880579692f769d
|
362
premake5.lua
Normal file
362
premake5.lua
Normal file
@ -0,0 +1,362 @@
|
||||
gitVersioningCommand = "git describe --tags --dirty --always"
|
||||
gitCurrentBranchCommand = "git symbolic-ref -q --short HEAD"
|
||||
|
||||
-- Quote the given string input as a C string
|
||||
function cstrquote(value)
|
||||
if value == nil then
|
||||
return "\"\""
|
||||
end
|
||||
result = value:gsub("\\", "\\\\")
|
||||
result = result:gsub("\"", "\\\"")
|
||||
result = result:gsub("\n", "\\n")
|
||||
result = result:gsub("\t", "\\t")
|
||||
result = result:gsub("\r", "\\r")
|
||||
result = result:gsub("\a", "\\a")
|
||||
result = result:gsub("\b", "\\b")
|
||||
result = "\"" .. result .. "\""
|
||||
return result
|
||||
end
|
||||
|
||||
-- Converts tags in "vX.X.X" format and given revision number Y to an array of numbers {X,X,X,Y}.
|
||||
-- In the case where the format does not work fall back to padding with zeroes and just ending with the revision number.
|
||||
-- partscount can be either 3 or 4.
|
||||
function vertonumarr(value, vernumber, partscount)
|
||||
vernum = {}
|
||||
for num in string.gmatch(value or "", "%d+") do
|
||||
if #vernum < 3 then
|
||||
table.insert(vernum, tonumber(num))
|
||||
end
|
||||
end
|
||||
while #vernum < 3 do
|
||||
table.insert(vernum, 0)
|
||||
end
|
||||
if #vernum < partscount then
|
||||
table.insert(vernum, tonumber(vernumber))
|
||||
end
|
||||
return vernum
|
||||
end
|
||||
|
||||
dependencies = {
|
||||
basePath = "./deps"
|
||||
}
|
||||
|
||||
function dependencies.load()
|
||||
dir = path.join(dependencies.basePath, "premake/*.lua")
|
||||
deps = os.matchfiles(dir)
|
||||
|
||||
for i, dep in pairs(deps) do
|
||||
dep = dep:gsub(".lua", "")
|
||||
require(dep)
|
||||
end
|
||||
end
|
||||
|
||||
function dependencies.imports()
|
||||
for i, proj in pairs(dependencies) do
|
||||
if type(i) == 'number' then
|
||||
proj.import()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function dependencies.projects()
|
||||
for i, proj in pairs(dependencies) do
|
||||
if type(i) == 'number' then
|
||||
proj.project()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
newoption {
|
||||
trigger = "copy-to",
|
||||
description = "Optional, copy the EXE to a custom folder after build, define the path here if wanted.",
|
||||
value = "PATH"
|
||||
}
|
||||
|
||||
newoption {
|
||||
trigger = "dev-build",
|
||||
description = "Enable development builds of the client."
|
||||
}
|
||||
|
||||
newaction {
|
||||
trigger = "version",
|
||||
description = "Returns the version string for the current commit of the source code.",
|
||||
onWorkspace = function(wks)
|
||||
-- get current version via git
|
||||
local proc = assert(io.popen(gitVersioningCommand, "r"))
|
||||
local gitDescribeOutput = assert(proc:read('*a')):gsub("%s+", "")
|
||||
proc:close()
|
||||
local version = gitDescribeOutput
|
||||
|
||||
proc = assert(io.popen(gitCurrentBranchCommand, "r"))
|
||||
local gitCurrentBranchOutput = assert(proc:read('*a')):gsub("%s+", "")
|
||||
local gitCurrentBranchSuccess = proc:close()
|
||||
if gitCurrentBranchSuccess then
|
||||
-- We got a branch name, check if it is a feature branch
|
||||
if gitCurrentBranchOutput ~= "develop" and gitCurrentBranchOutput ~= "master" then
|
||||
version = version .. "-" .. gitCurrentBranchOutput
|
||||
end
|
||||
end
|
||||
|
||||
print(version)
|
||||
os.exit(0)
|
||||
end
|
||||
}
|
||||
|
||||
newaction {
|
||||
trigger = "generate-buildinfo",
|
||||
description = "Sets up build information file like version.h.",
|
||||
onWorkspace = function(wks)
|
||||
-- get old version number from version.hpp if any
|
||||
local oldVersion = "(none)"
|
||||
local oldVersionHeader = io.open(wks.location .. "/src/version.h", "r")
|
||||
if oldVersionHeader ~= nil then
|
||||
local oldVersionHeaderContent = assert(oldVersionHeader:read('*l'))
|
||||
while oldVersionHeaderContent do
|
||||
m = string.match(oldVersionHeaderContent, "#define GIT_DESCRIBE (.+)%s*$")
|
||||
if m ~= nil then
|
||||
oldVersion = m
|
||||
end
|
||||
|
||||
oldVersionHeaderContent = oldVersionHeader:read('*l')
|
||||
end
|
||||
end
|
||||
|
||||
-- get current version via git
|
||||
local proc = assert(io.popen(gitVersioningCommand, "r"))
|
||||
local gitDescribeOutput = assert(proc:read('*a')):gsub("%s+", "")
|
||||
proc:close()
|
||||
|
||||
-- generate version.hpp with a revision number if not equal
|
||||
gitDescribeOutputQuoted = cstrquote(gitDescribeOutput)
|
||||
if oldVersion ~= gitDescribeOutputQuoted then
|
||||
-- get current git hash and write to version.txt (used by the preliminary updater)
|
||||
-- TODO - remove once proper updater and release versioning exists
|
||||
local proc = assert(io.popen("git rev-parse HEAD", "r"))
|
||||
local gitCommitHash = assert(proc:read('*a')):gsub("%s+", "")
|
||||
proc:close()
|
||||
|
||||
-- get whether this is a clean revision (no uncommitted changes)
|
||||
proc = assert(io.popen("git status --porcelain", "r"))
|
||||
local revDirty = (assert(proc:read('*a')) ~= "")
|
||||
if revDirty then revDirty = 1 else revDirty = 0 end
|
||||
proc:close()
|
||||
|
||||
-- get current tag name
|
||||
proc = assert(io.popen("git describe --tags --abbrev=0"))
|
||||
local tagName = proc:read('*l')
|
||||
|
||||
-- get current branch name
|
||||
proc = assert(io.popen("git branch --show-current"))
|
||||
local branchName = proc:read('*l')
|
||||
|
||||
-- branch for ci
|
||||
if branchName == nil or branchName == '' then
|
||||
proc = assert(io.popen("git show -s --pretty=%d HEAD"))
|
||||
local branchInfo = proc:read('*l')
|
||||
m = string.match(branchInfo, ".+,.+, ([^)]+)")
|
||||
if m ~= nil then
|
||||
branchName = m
|
||||
end
|
||||
end
|
||||
|
||||
if branchName == nil then
|
||||
branchName = "develop"
|
||||
end
|
||||
|
||||
print("Detected branch: " .. branchName)
|
||||
|
||||
-- get revision number via git
|
||||
local proc = assert(io.popen("git rev-list --count HEAD", "r"))
|
||||
local revNumber = assert(proc:read('*a')):gsub("%s+", "")
|
||||
|
||||
print ("Update " .. oldVersion .. " -> " .. gitDescribeOutputQuoted)
|
||||
|
||||
-- write to version.txt for preliminary updater
|
||||
-- NOTE - remove this once we have a proper updater and proper release versioning
|
||||
local versionFile = assert(io.open(wks.location .. "/version.txt", "w"))
|
||||
versionFile:write(gitCommitHash)
|
||||
versionFile:close()
|
||||
|
||||
-- write version header
|
||||
local versionHeader = assert(io.open(wks.location .. "/src/version.h", "w"))
|
||||
versionHeader:write("/*\n")
|
||||
versionHeader:write(" * Automatically generated by premake5.\n")
|
||||
versionHeader:write(" * Do not touch!\n")
|
||||
versionHeader:write(" */\n")
|
||||
versionHeader:write("\n")
|
||||
versionHeader:write("#define GIT_DESCRIBE " .. gitDescribeOutputQuoted .. "\n")
|
||||
versionHeader:write("#define GIT_DIRTY " .. revDirty .. "\n")
|
||||
versionHeader:write("#define GIT_HASH " .. cstrquote(gitCommitHash) .. "\n")
|
||||
versionHeader:write("#define GIT_TAG " .. cstrquote(tagName) .. "\n")
|
||||
versionHeader:write("#define GIT_BRANCH " .. cstrquote(branchName) .. "\n")
|
||||
versionHeader:write("\n")
|
||||
versionHeader:write("// Version transformed for RC files\n")
|
||||
versionHeader:write("#define VERSION_PRODUCT_RC " .. table.concat(vertonumarr(tagName, revNumber, 3), ",") .. "\n")
|
||||
versionHeader:write("#define VERSION_PRODUCT " .. cstrquote(table.concat(vertonumarr(tagName, revNumber, 3), ".")) .. "\n")
|
||||
versionHeader:write("#define VERSION_FILE_RC " .. table.concat(vertonumarr(tagName, revNumber, 4), ",") .. "\n")
|
||||
versionHeader:write("#define VERSION_FILE " .. cstrquote(table.concat(vertonumarr(tagName, revNumber, 4), ".")) .. "\n")
|
||||
versionHeader:write("\n")
|
||||
versionHeader:write("// Alias definitions\n")
|
||||
versionHeader:write("#define VERSION GIT_DESCRIBE\n")
|
||||
versionHeader:write("#define SHORTVERSION VERSION_PRODUCT\n")
|
||||
versionHeader:close()
|
||||
local versionHeader = assert(io.open(wks.location .. "/src/version.hpp", "w"))
|
||||
versionHeader:write("/*\n")
|
||||
versionHeader:write(" * Automatically generated by premake5.\n")
|
||||
versionHeader:write(" * Do not touch!\n")
|
||||
versionHeader:write(" *\n")
|
||||
versionHeader:write(" * This file exists for reasons of complying with our coding standards.\n")
|
||||
versionHeader:write(" *\n")
|
||||
versionHeader:write(" * The Resource Compiler will ignore any content from C++ header files if they're not from STDInclude.hpp.\n")
|
||||
versionHeader:write(" * That's the reason why we now place all version info in version.h instead.\n")
|
||||
versionHeader:write(" */\n")
|
||||
versionHeader:write("\n")
|
||||
versionHeader:write("#include \".\\version.h\"\n")
|
||||
versionHeader:close()
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
dependencies.load()
|
||||
|
||||
workspace "s1-mod"
|
||||
startproject "client"
|
||||
location "./build"
|
||||
objdir "%{wks.location}/obj"
|
||||
targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}"
|
||||
|
||||
configurations {"Debug", "Release"}
|
||||
|
||||
language "C++"
|
||||
cppdialect "C++20"
|
||||
|
||||
architecture "x86_64"
|
||||
platforms "x64"
|
||||
|
||||
systemversion "latest"
|
||||
symbols "On"
|
||||
staticruntime "On"
|
||||
editandcontinue "Off"
|
||||
warnings "Extra"
|
||||
characterset "ASCII"
|
||||
|
||||
if _OPTIONS["dev-build"] then
|
||||
defines {"DEV_BUILD"}
|
||||
end
|
||||
|
||||
if os.getenv("CI") then
|
||||
defines {"CI"}
|
||||
end
|
||||
|
||||
flags {"NoIncrementalLink", "NoMinimalRebuild", "MultiProcessorCompile", "No64BitChecks"}
|
||||
|
||||
filter "platforms:x64"
|
||||
defines {"_WINDOWS", "WIN32"}
|
||||
filter {}
|
||||
|
||||
filter "configurations:Release"
|
||||
optimize "Size"
|
||||
buildoptions {"/GL"}
|
||||
linkoptions {"/IGNORE:4702", "/LTCG"}
|
||||
defines {"NDEBUG"}
|
||||
flags {"FatalCompileWarnings"}
|
||||
filter {}
|
||||
|
||||
filter "configurations:Debug"
|
||||
optimize "Debug"
|
||||
defines {"DEBUG", "_DEBUG"}
|
||||
filter {}
|
||||
|
||||
project "common"
|
||||
kind "StaticLib"
|
||||
language "C++"
|
||||
|
||||
files {"./src/common/**.hpp", "./src/common/**.cpp"}
|
||||
|
||||
includedirs {"./src/common", "%{prj.location}/src"}
|
||||
|
||||
resincludedirs {"$(ProjectDir)src"}
|
||||
|
||||
dependencies.imports()
|
||||
|
||||
project "runner"
|
||||
kind "WindowedApp"
|
||||
language "C++"
|
||||
|
||||
files {"./src/runner/**.rc", "./src/runner/**.hpp", "./src/runner/**.cpp", "./src/runner/resources/**.*"}
|
||||
|
||||
includedirs {"./src/runner", "./src/common", "%{prj.location}/src"}
|
||||
|
||||
resincludedirs {"$(ProjectDir)src"}
|
||||
|
||||
links {"common"}
|
||||
|
||||
dependencies.imports()
|
||||
|
||||
project "client"
|
||||
kind "WindowedApp"
|
||||
language "C++"
|
||||
|
||||
targetname "s1-mod"
|
||||
|
||||
pchheader "std_include.hpp"
|
||||
pchsource "src/client/std_include.cpp"
|
||||
|
||||
linkoptions {"/IGNORE:4254", "/DYNAMICBASE:NO", "/SAFESEH:NO", "/LARGEADDRESSAWARE", "/LAST:.main", "/PDBCompress"}
|
||||
|
||||
files {"./src/client/**.rc", "./src/client/**.hpp", "./src/client/**.cpp", "./src/client/resources/**.*"}
|
||||
|
||||
includedirs {"./src/client", "./src/common", "%{prj.location}/src"}
|
||||
|
||||
resincludedirs {"$(ProjectDir)src"}
|
||||
|
||||
dependson {"tlsdll", "runner"}
|
||||
|
||||
links {"common"}
|
||||
|
||||
prebuildcommands {"pushd %{_MAIN_SCRIPT_DIR}", "tools\\premake5 generate-buildinfo", "popd"}
|
||||
|
||||
if _OPTIONS["copy-to"] then
|
||||
postbuildcommands {"copy /y \"$(TargetPath)\" \"" .. _OPTIONS["copy-to"] .. "\""}
|
||||
end
|
||||
|
||||
dependencies.imports()
|
||||
|
||||
project "tlsdll"
|
||||
kind "SharedLib"
|
||||
language "C++"
|
||||
|
||||
files {"./src/tlsdll/**.rc", "./src/tlsdll/**.hpp", "./src/tlsdll/**.cpp", "./src/tlsdll/resources/**.*"}
|
||||
|
||||
includedirs {"./src/tlsdll", "%{prj.location}/src"}
|
||||
|
||||
links {"common"}
|
||||
|
||||
resincludedirs {"$(ProjectDir)src"}
|
||||
|
||||
project "runner"
|
||||
kind "WindowedApp"
|
||||
language "C++"
|
||||
|
||||
files {"./src/runner/**.rc", "./src/runner/**.hpp", "./src/runner/**.cpp", "./src/runner/resources/**.*"}
|
||||
|
||||
includedirs {"./src/runner", "./src/common", "%{prj.location}/src"}
|
||||
|
||||
links {"common"}
|
||||
|
||||
resincludedirs {"$(ProjectDir)src"}
|
||||
|
||||
dependencies.imports()
|
||||
|
||||
group "Dependencies"
|
||||
dependencies.projects()
|
||||
|
||||
rule "ProtobufCompiler"
|
||||
display "Protobuf compiler"
|
||||
location "./build"
|
||||
fileExtension ".proto"
|
||||
buildmessage "Compiling %(Identity) with protoc..."
|
||||
buildcommands {'@echo off', 'path "$(SolutionDir)\\..\\tools"',
|
||||
'if not exist "$(ProjectDir)\\src\\proto" mkdir "$(ProjectDir)\\src\\proto"',
|
||||
'protoc --error_format=msvs -I=%(RelativeDir) --cpp_out=src\\proto %(Identity)'}
|
||||
buildoutputs {'$(ProjectDir)\\src\\proto\\%(Filename).pb.cc', '$(ProjectDir)\\src\\proto\\%(Filename).pb.h'}
|
163
src/client/component/arxan.cpp
Normal file
163
src/client/component/arxan.cpp
Normal file
@ -0,0 +1,163 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace arxan
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour nt_close_hook;
|
||||
utils::hook::detour nt_query_information_process_hook;
|
||||
|
||||
NTSTATUS WINAPI nt_query_information_process_stub(const HANDLE handle, const PROCESSINFOCLASS info_class,
|
||||
const PVOID info,
|
||||
const ULONG info_length, const PULONG ret_length)
|
||||
{
|
||||
auto* orig = static_cast<decltype(NtQueryInformationProcess)*>(nt_query_information_process_hook.
|
||||
get_original());
|
||||
const auto status = orig(handle, info_class, info, info_length, ret_length);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
if (info_class == ProcessBasicInformation)
|
||||
{
|
||||
static DWORD explorer_pid = 0;
|
||||
if (!explorer_pid)
|
||||
{
|
||||
auto* const shell_window = GetShellWindow();
|
||||
GetWindowThreadProcessId(shell_window, &explorer_pid);
|
||||
}
|
||||
|
||||
static_cast<PPROCESS_BASIC_INFORMATION>(info)->Reserved3 = PVOID(DWORD64(explorer_pid));
|
||||
}
|
||||
else if (info_class == 30) // ProcessDebugObjectHandle
|
||||
{
|
||||
*static_cast<HANDLE*>(info) = nullptr;
|
||||
|
||||
return 0xC0000353;
|
||||
}
|
||||
else if (info_class == 7) // ProcessDebugPort
|
||||
{
|
||||
*static_cast<HANDLE*>(info) = nullptr;
|
||||
}
|
||||
else if (info_class == 31)
|
||||
{
|
||||
*static_cast<ULONG*>(info) = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
NTSTATUS NTAPI nt_close_stub(const HANDLE handle)
|
||||
{
|
||||
char info[16];
|
||||
if (NtQueryObject(handle, OBJECT_INFORMATION_CLASS(4), &info, 2, nullptr) >= 0 && size_t(handle) != 0x12345)
|
||||
{
|
||||
auto* orig = static_cast<decltype(NtClose)*>(nt_close_hook.get_original());
|
||||
return orig(handle);
|
||||
}
|
||||
|
||||
return STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS info)
|
||||
{
|
||||
if (info->ExceptionRecord->ExceptionCode == STATUS_INVALID_HANDLE)
|
||||
{
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
void hide_being_debugged()
|
||||
{
|
||||
auto* const peb = PPEB(__readgsqword(0x60));
|
||||
peb->BeingDebugged = false;
|
||||
*reinterpret_cast<PDWORD>(LPSTR(peb) + 0xBC) &= ~0x70;
|
||||
}
|
||||
|
||||
void remove_hardware_breakpoints()
|
||||
{
|
||||
CONTEXT context;
|
||||
ZeroMemory(&context, sizeof(context));
|
||||
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||
|
||||
auto* const thread = GetCurrentThread();
|
||||
GetThreadContext(thread, &context);
|
||||
|
||||
context.Dr0 = 0;
|
||||
context.Dr1 = 0;
|
||||
context.Dr2 = 0;
|
||||
context.Dr3 = 0;
|
||||
context.Dr6 = 0;
|
||||
context.Dr7 = 0;
|
||||
|
||||
SetThreadContext(thread, &context);
|
||||
}
|
||||
|
||||
BOOL WINAPI set_thread_context_stub(const HANDLE thread, CONTEXT* context)
|
||||
{
|
||||
if (!game::environment::is_sp()
|
||||
&& game::dwGetLogOnStatus() == game::DW_LIVE_CONNECTED
|
||||
&& context->ContextFlags == CONTEXT_DEBUG_REGISTERS)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return SetThreadContext(thread, context);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
if (function == "SetThreadContext")
|
||||
{
|
||||
//return set_thread_context_stub;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void post_load() override
|
||||
{
|
||||
hide_being_debugged();
|
||||
scheduler::loop(hide_being_debugged, scheduler::pipeline::async);
|
||||
|
||||
const utils::nt::library ntdll("ntdll.dll");
|
||||
nt_close_hook.create(ntdll.get_proc<void*>("NtClose"), nt_close_stub);
|
||||
nt_query_information_process_hook.create(ntdll.get_proc<void*>("NtQueryInformationProcess"),
|
||||
nt_query_information_process_stub);
|
||||
|
||||
AddVectoredExceptionHandler(1, exception_filter);
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
// cba to implement sp, not sure if it's even needed
|
||||
if (game::environment::is_sp()) return;
|
||||
|
||||
// HW-BP Mapping
|
||||
// 1404D4F40 -> 1404D4F30
|
||||
// 140509500 -> 140509410
|
||||
// 140545F80 -> 140545EE0
|
||||
// 14053CCF0 -> 14053CCC0 // dwGetLogonStatus
|
||||
|
||||
utils::hook::call(0x14053B5FE, 0x140545EE0); // some pump
|
||||
utils::hook::call(0x1404D3D42, 0x140509410); // some other pump
|
||||
utils::hook::jump(0x14053CCB0, 0x14053CCC0); // dwGetLogonStatus
|
||||
utils::hook::call(0x14053CD04, 0x14053CCC0); // dwGetLogonStatus
|
||||
|
||||
//scheduler::on_game_initialized(remove_hardware_breakpoints, scheduler::pipeline::main);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(arxan::component)
|
229
src/client/component/auth.cpp
Normal file
229
src/client/component/auth.cpp
Normal file
@ -0,0 +1,229 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "steam/steam.hpp"
|
||||
|
||||
#include "auth.hpp"
|
||||
#include "command.hpp"
|
||||
#include "network.hpp"
|
||||
#include "console.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/smbios.hpp>
|
||||
#include <utils/info_string.hpp>
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
namespace auth
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string get_hdd_serial()
|
||||
{
|
||||
DWORD serial{};
|
||||
if (!GetVolumeInformationA("C:\\", nullptr, 0, &serial, nullptr, nullptr, nullptr, 0))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return utils::string::va("%08X", serial);
|
||||
}
|
||||
|
||||
std::string get_hw_profile_guid()
|
||||
{
|
||||
HW_PROFILE_INFO info;
|
||||
if (!GetCurrentHwProfileA(&info))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::string{info.szHwProfileGuid, sizeof(info.szHwProfileGuid)};
|
||||
}
|
||||
|
||||
std::string get_protected_data()
|
||||
{
|
||||
std::string input = "AlterWare-S1-Auth";
|
||||
|
||||
DATA_BLOB data_in{}, data_out{};
|
||||
data_in.pbData = reinterpret_cast<uint8_t*>(input.data());
|
||||
data_in.cbData = static_cast<DWORD>(input.size());
|
||||
if(CryptProtectData(&data_in, nullptr, nullptr, nullptr, nullptr, CRYPTPROTECT_LOCAL_MACHINE, &data_out) != TRUE)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto size = std::min(data_out.cbData, 52ul);
|
||||
std::string result{reinterpret_cast<char*>(data_out.pbData), size};
|
||||
LocalFree(data_out.pbData);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string get_key_entropy()
|
||||
{
|
||||
std::string entropy{};
|
||||
entropy.append(utils::smbios::get_uuid());
|
||||
entropy.append(get_hw_profile_guid());
|
||||
entropy.append(get_protected_data());
|
||||
entropy.append(get_hdd_serial());
|
||||
|
||||
if (entropy.empty())
|
||||
{
|
||||
entropy.resize(32);
|
||||
utils::cryptography::random::get_data(entropy.data(), entropy.size());
|
||||
}
|
||||
|
||||
return entropy;
|
||||
}
|
||||
|
||||
utils::cryptography::ecc::key& get_key()
|
||||
{
|
||||
static auto key = utils::cryptography::ecc::generate_key(512, get_key_entropy());
|
||||
return key;
|
||||
}
|
||||
|
||||
int send_connect_data_stub(game::netsrc_t sock, game::netadr_s* adr, const char* format, const int len)
|
||||
{
|
||||
std::string connect_string(format, len);
|
||||
game::SV_Cmd_TokenizeString(connect_string.data());
|
||||
const auto _ = gsl::finally([]()
|
||||
{
|
||||
game::SV_Cmd_EndTokenizedString();
|
||||
});
|
||||
|
||||
const command::params_sv params;
|
||||
if (params.size() < 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const utils::info_string info_string{std::string{params[2]}};
|
||||
const auto challenge = info_string.get("challenge");
|
||||
|
||||
connect_string.clear();
|
||||
connect_string.append(params[0]);
|
||||
connect_string.append(" ");
|
||||
connect_string.append(params[1]);
|
||||
connect_string.append(" ");
|
||||
connect_string.append("\"" + info_string.build() + "\"");
|
||||
|
||||
proto::network::connect_info info;
|
||||
info.set_publickey(get_key().get_public_key());
|
||||
info.set_signature(sign_message(get_key(), challenge));
|
||||
info.set_infostring(connect_string);
|
||||
|
||||
network::send(*adr, "connect", info.SerializeAsString());
|
||||
return true;
|
||||
}
|
||||
|
||||
void direct_connect(game::netadr_s* from, game::msg_t* msg)
|
||||
{
|
||||
const auto offset = sizeof("connect") + 4;
|
||||
|
||||
proto::network::connect_info info;
|
||||
if (msg->cursize < offset || !info.ParseFromArray(msg->data + offset, msg->cursize - offset))
|
||||
{
|
||||
network::send(*from, "error", "Invalid connect data!", '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
game::SV_Cmd_EndTokenizedString();
|
||||
game::SV_Cmd_TokenizeString(info.infostring().data());
|
||||
|
||||
const command::params_sv params;
|
||||
if (params.size() < 3)
|
||||
{
|
||||
network::send(*from, "error", "Invalid connect string!", '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
const utils::info_string info_string{std::string{params[2]}};
|
||||
|
||||
const auto steam_id = info_string.get("xuid");
|
||||
const auto challenge = info_string.get("challenge");
|
||||
|
||||
if (steam_id.empty() || challenge.empty())
|
||||
{
|
||||
network::send(*from, "error", "Invalid connect data!", '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
utils::cryptography::ecc::key key;
|
||||
key.set(info.publickey());
|
||||
|
||||
const auto xuid = strtoull(steam_id.data(), nullptr, 16);
|
||||
if (xuid != key.get_hash())
|
||||
{
|
||||
network::send(*from, "error", utils::string::va("XUID doesn't match the certificate: %llX != %llX", xuid, key.get_hash()), '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!key.is_valid() || !verify_message(key, challenge, info.signature()))
|
||||
{
|
||||
network::send(*from, "error", "Challenge signature was invalid!", '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
game::SV_DirectConnect(from);
|
||||
}
|
||||
|
||||
void* get_direct_connect_stub()
|
||||
{
|
||||
return utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.lea(rcx, qword_ptr(rsp, 0x20));
|
||||
a.movaps(xmmword_ptr(rsp, 0x20), xmm0);
|
||||
|
||||
a.pushad64();
|
||||
a.mov(rdx, rdi);
|
||||
a.call_aligned(direct_connect);
|
||||
a.popad64();
|
||||
|
||||
a.jmp(0x140442317);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t get_guid()
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return 0x110000100000000 | (::utils::cryptography::random::get_integer() & ~0x80000000);
|
||||
}
|
||||
|
||||
return get_key().get_hash();
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// Patch steam id bit check
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
utils::hook::jump(0x1404267F0, 0x140426846);
|
||||
utils::hook::jump(0x14042760F, 0x140427650);
|
||||
utils::hook::jump(0x140427AB4, 0x140427B02);
|
||||
}
|
||||
else
|
||||
{
|
||||
utils::hook::jump(0x140538920, 0x140538976);
|
||||
utils::hook::jump(0x140009801, 0x140009B48);
|
||||
utils::hook::jump(0x140009AEB, 0x140009B48);
|
||||
utils::hook::jump(0x14053995F, 0x1405399A0);
|
||||
utils::hook::jump(0x140539E70, 0x140539EB6);
|
||||
|
||||
utils::hook::jump(0x1404421F6, get_direct_connect_stub(), true);
|
||||
utils::hook::call(0x140208C54, send_connect_data_stub);
|
||||
}
|
||||
|
||||
command::add("guid", []
|
||||
{
|
||||
console::info("Your guid: %llX\n", steam::SteamUser()->GetSteamID().bits);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(auth::component)
|
6
src/client/component/auth.hpp
Normal file
6
src/client/component/auth.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace auth
|
||||
{
|
||||
uint64_t get_guid();
|
||||
}
|
138
src/client/component/binding.cpp
Normal file
138
src/client/component/binding.cpp
Normal file
@ -0,0 +1,138 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace binding
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::vector<std::string> custom_binds = {};
|
||||
|
||||
utils::hook::detour cl_execute_key_hook;
|
||||
|
||||
int get_num_keys()
|
||||
{
|
||||
return SELECT_VALUE(102, 103);
|
||||
}
|
||||
|
||||
int key_write_bindings_to_buffer_stub(int /*localClientNum*/, char* buffer, const int buffer_size)
|
||||
{
|
||||
auto bytes_used = 0;
|
||||
const auto buffer_size_align = static_cast<std::int32_t>(buffer_size) - 4;
|
||||
|
||||
for (auto key_index = 0; key_index < 256; ++key_index)
|
||||
{
|
||||
const auto* const key_button = game::Key_KeynumToString(key_index, 0, 1);
|
||||
auto value = game::playerKeys->keys[key_index].binding;
|
||||
|
||||
if (value && value < get_num_keys())
|
||||
{
|
||||
const auto len = sprintf_s(&buffer[bytes_used], (buffer_size_align - bytes_used),
|
||||
"bind %s \"%s\"\n", key_button, game::command_whitelist[value]);
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
return bytes_used;
|
||||
}
|
||||
|
||||
bytes_used += len;
|
||||
}
|
||||
else if (value >= get_num_keys())
|
||||
{
|
||||
value -= get_num_keys();
|
||||
if (static_cast<size_t>(value) < custom_binds.size() && !custom_binds[value].empty())
|
||||
{
|
||||
const auto len = sprintf_s(&buffer[bytes_used], (buffer_size_align - bytes_used),
|
||||
"bind %s \"%s\"\n", key_button, custom_binds[value].data());
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
return bytes_used;
|
||||
}
|
||||
|
||||
bytes_used += len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer[bytes_used] = 0;
|
||||
return bytes_used;
|
||||
}
|
||||
|
||||
int get_binding_for_custom_command(const char* command)
|
||||
{
|
||||
auto index = 0;
|
||||
for (auto& bind : custom_binds)
|
||||
{
|
||||
if (bind == command)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
custom_binds.emplace_back(command);
|
||||
index = static_cast<unsigned int>(custom_binds.size()) - 1;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
int key_get_binding_for_cmd_stub(const char* command)
|
||||
{
|
||||
// original binds
|
||||
for (auto i = 0; i <= get_num_keys(); i++)
|
||||
{
|
||||
if (game::command_whitelist[i] && !strcmp(command, game::command_whitelist[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// custom binds
|
||||
return get_num_keys() + get_binding_for_custom_command(command);
|
||||
}
|
||||
|
||||
void cl_execute_key_stub(const int local_client_num, int key, const int down, const int time)
|
||||
{
|
||||
if (key >= get_num_keys())
|
||||
{
|
||||
key -= get_num_keys();
|
||||
|
||||
if (static_cast<size_t>(key) < custom_binds.size() && !custom_binds[key].empty())
|
||||
{
|
||||
game::Cbuf_AddText(local_client_num, utils::string::va("%s\n", custom_binds[key].data()));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
cl_execute_key_hook.invoke<void>(local_client_num, key, down, time);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// write all bindings to config file
|
||||
utils::hook::call(SELECT_VALUE(0x14013F90B, 0x1402081CB), key_write_bindings_to_buffer_stub);
|
||||
|
||||
// links a custom command to an index
|
||||
utils::hook::jump(SELECT_VALUE(0x1402EE5A0, 0x1403AFB50), key_get_binding_for_cmd_stub);
|
||||
|
||||
// execute custom binds
|
||||
cl_execute_key_hook.create(SELECT_VALUE(0x14013B110, 0x140202BE0), &cl_execute_key_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(binding::component)
|
197
src/client/component/bots.cpp
Normal file
197
src/client/component/bots.cpp
Normal file
@ -0,0 +1,197 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "network.hpp"
|
||||
#include "party.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "server_list.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace bots
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr std::size_t MAX_NAME_LENGTH = 16;
|
||||
|
||||
bool can_add()
|
||||
{
|
||||
if (party::get_client_count() < *game::mp::svs_numclients)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void bot_team_join(const unsigned int entity_num)
|
||||
{
|
||||
scheduler::once([entity_num]()
|
||||
{
|
||||
game::Scr_AddInt(2);
|
||||
game::Scr_AddString("team_select");
|
||||
game::Scr_Notify(&game::mp::g_entities[entity_num], static_cast<std::uint16_t>(game::SL_GetString("luinotifyserver", 0)), 2);
|
||||
|
||||
scheduler::once([entity_num]()
|
||||
{
|
||||
game::Scr_AddString(utils::string::va("class%d", std::rand() % 5));
|
||||
game::Scr_AddString("class_select");
|
||||
game::Scr_Notify(&game::mp::g_entities[entity_num], static_cast<std::uint16_t>(game::SL_GetString("luinotifyserver", 0)), 2);
|
||||
}, scheduler::pipeline::server, 2s);
|
||||
}, scheduler::pipeline::server, 2s);
|
||||
}
|
||||
|
||||
void spawn_bot(const int entity_num)
|
||||
{
|
||||
game::SV_SpawnTestClient(&game::mp::g_entities[entity_num]);
|
||||
if (game::Com_GetCurrentCoDPlayMode() == game::CODPLAYMODE_CORE)
|
||||
{
|
||||
bot_team_join(entity_num);
|
||||
}
|
||||
}
|
||||
|
||||
void add_bot()
|
||||
{
|
||||
if (!can_add())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// SV_BotGetRandomName
|
||||
const auto* const bot_name = game::SV_BotGetRandomName();
|
||||
auto* bot_ent = game::SV_AddBot(bot_name);
|
||||
if (bot_ent)
|
||||
{
|
||||
spawn_bot(bot_ent->s.number);
|
||||
}
|
||||
else if (can_add()) // workaround since first bot won't ever spawn
|
||||
{
|
||||
add_bot();
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::detour get_bot_name_hook;
|
||||
volatile bool bot_names_received = false;
|
||||
std::vector<std::string> bot_names;
|
||||
|
||||
const char* get_random_bot_name()
|
||||
{
|
||||
if (bot_names.empty())
|
||||
{
|
||||
return get_bot_name_hook.invoke<const char*>();
|
||||
}
|
||||
|
||||
const auto index = std::rand() % bot_names.size();
|
||||
const auto& name = bot_names.at(index);
|
||||
|
||||
return utils::string::va("%.*s", static_cast<int>(name.size()), name.data());
|
||||
}
|
||||
|
||||
bool should_update_bot_names()
|
||||
{
|
||||
return !filesystem::exists("bots.txt");
|
||||
}
|
||||
|
||||
void parse_bot_names_from_file()
|
||||
{
|
||||
std::string data;
|
||||
filesystem::read_file("bots.txt", &data);
|
||||
if (data.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto name_list = utils::string::split(data, '\n');
|
||||
for (auto& entry : name_list)
|
||||
{
|
||||
// Take into account CR line endings
|
||||
entry = utils::string::replace(entry, "\r", "");
|
||||
|
||||
if (entry.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
entry = entry.substr(0, MAX_NAME_LENGTH - 1);
|
||||
bot_names.emplace_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
void update_bot_names()
|
||||
{
|
||||
bot_names_received = false;
|
||||
|
||||
game::netadr_s master;
|
||||
if (server_list::get_master_server(master))
|
||||
{
|
||||
console::info("Getting bots...\n");
|
||||
network::send(master, "getbots");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
get_bot_name_hook.create(game::SV_BotGetRandomName, get_random_bot_name);
|
||||
|
||||
command::add("spawnBot", [](const command::params& params)
|
||||
{
|
||||
if (!game::SV_Loaded() || game::VirtualLobby_Loaded()) return;
|
||||
|
||||
auto num_bots = 1;
|
||||
if (params.size() == 2)
|
||||
{
|
||||
num_bots = std::atoi(params.get(1));
|
||||
}
|
||||
|
||||
num_bots = std::min(num_bots, *game::mp::svs_numclients);
|
||||
|
||||
console::info("Spawning %i %s\n", num_bots, (num_bots == 1 ? "bot" : "bots"));
|
||||
|
||||
for (auto i = 0; i < num_bots; ++i)
|
||||
{
|
||||
scheduler::once(add_bot, scheduler::pipeline::server, 100ms * i);
|
||||
}
|
||||
});
|
||||
|
||||
if (should_update_bot_names())
|
||||
{
|
||||
scheduler::on_game_initialized([]() -> void
|
||||
{
|
||||
update_bot_names();
|
||||
scheduler::loop(update_bot_names, scheduler::main, 1h);
|
||||
}, scheduler::main);
|
||||
}
|
||||
else
|
||||
{
|
||||
parse_bot_names_from_file();
|
||||
}
|
||||
|
||||
network::on("getbotsResponse", [](const game::netadr_s& target, const std::string_view& data)
|
||||
{
|
||||
game::netadr_s master{};
|
||||
if (server_list::get_master_server(master) && !bot_names_received && target == master)
|
||||
{
|
||||
const std::string received_data{ data };
|
||||
bot_names = utils::string::split(received_data, '\n');
|
||||
bot_names_received = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(bots::component)
|
69
src/client/component/branding.cpp
Normal file
69
src/client/component/branding.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "localized_strings.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#include "version.hpp"
|
||||
|
||||
namespace branding
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour ui_get_formatted_build_number_hook;
|
||||
|
||||
const char* ui_get_formatted_build_number_stub()
|
||||
{
|
||||
const auto* const build_num = ui_get_formatted_build_number_hook.invoke<const char*>();
|
||||
|
||||
return utils::string::va("%s (%s)", VERSION, build_num);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
localized_strings::override("LUA_MENU_MULTIPLAYER_CAPS", "s1-mod: MULTIPLAYER\n");
|
||||
}
|
||||
localized_strings::override("LUA_MENU_LEGAL_COPYRIGHT", "s1-mod: " VERSION);
|
||||
|
||||
dvars::override::set_string("version", utils::string::va("s1-mod %s", VERSION));
|
||||
|
||||
ui_get_formatted_build_number_hook.create(
|
||||
SELECT_VALUE(0x14035B3F0, 0x1404A8950), ui_get_formatted_build_number_stub);
|
||||
|
||||
scheduler::loop([]()
|
||||
{
|
||||
const auto x = 4;
|
||||
const auto y = 4;
|
||||
const auto scale = 1.0f;
|
||||
float color[4] = {0.666f, 0.666f, 0.666f, 0.666f};
|
||||
const auto* text = "s1-mod: " VERSION;
|
||||
|
||||
auto* font = game::R_RegisterFont("fonts/consolefont");
|
||||
|
||||
if (!font) return;
|
||||
|
||||
game::R_AddCmdDrawText(text, std::numeric_limits<int>::max(), font, static_cast<float>(x),
|
||||
y + static_cast<float>(font->pixelHeight) * scale,
|
||||
scale, scale, 0.0f, color, 0);
|
||||
}, scheduler::pipeline::renderer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(branding::component)
|
44
src/client/component/bullet.cpp
Normal file
44
src/client/component/bullet.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace bullet
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour bg_get_surface_penetration_depth_hook;
|
||||
|
||||
float bg_get_surface_penetration_depth_stub(game::Weapon weapon, bool is_alternate, int surfaceType)
|
||||
{
|
||||
if (dvars::bg_surfacePenetration->current.value > 0.0f)
|
||||
{
|
||||
return dvars::bg_surfacePenetration->current.value;
|
||||
}
|
||||
|
||||
return bg_get_surface_penetration_depth_hook.invoke<float>(weapon, is_alternate, surfaceType);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dvars::bg_surfacePenetration = game::Dvar_RegisterFloat("bg_surfacePenetration", 0.0f,
|
||||
0.0f, std::numeric_limits<float>::max(), game::DVAR_FLAG_SAVED,
|
||||
"Set to a value greater than 0 to override the surface penetration depth");
|
||||
bg_get_surface_penetration_depth_hook.create(0x1401641A0, &bg_get_surface_penetration_depth_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(bullet::component)
|
183
src/client/component/colors.cpp
Normal file
183
src/client/component/colors.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace colors
|
||||
{
|
||||
struct hsv_color
|
||||
{
|
||||
unsigned char h;
|
||||
unsigned char s;
|
||||
unsigned char v;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
std::vector<DWORD> color_table;
|
||||
|
||||
DWORD hsv_to_rgb(const hsv_color hsv)
|
||||
{
|
||||
DWORD rgb;
|
||||
|
||||
if (hsv.s == 0)
|
||||
{
|
||||
return RGB(hsv.v, hsv.v, hsv.v);
|
||||
}
|
||||
|
||||
// converting to 16 bit to prevent overflow
|
||||
const unsigned int h = hsv.h;
|
||||
const unsigned int s = hsv.s;
|
||||
const unsigned int v = hsv.v;
|
||||
|
||||
const auto region = static_cast<uint8_t>(h / 43);
|
||||
const auto remainder = (h - (region * 43)) * 6;
|
||||
|
||||
const auto p = static_cast<uint8_t>((v * (255 - s)) >> 8);
|
||||
const auto q = static_cast<uint8_t>(
|
||||
(v * (255 - ((s * remainder) >> 8))) >> 8);
|
||||
const auto t = static_cast<uint8_t>(
|
||||
(v * (255 - ((s * (255 - remainder)) >> 8))) >> 8);
|
||||
|
||||
switch (region)
|
||||
{
|
||||
case 0:
|
||||
rgb = RGB(v, t, p);
|
||||
break;
|
||||
case 1:
|
||||
rgb = RGB(q, v, p);
|
||||
break;
|
||||
case 2:
|
||||
rgb = RGB(p, v, t);
|
||||
break;
|
||||
case 3:
|
||||
rgb = RGB(p, q, v);
|
||||
break;
|
||||
case 4:
|
||||
rgb = RGB(t, p, v);
|
||||
break;
|
||||
default:
|
||||
rgb = RGB(v, p, q);
|
||||
break;
|
||||
}
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
int color_index(const char c)
|
||||
{
|
||||
const auto index = c - 48;
|
||||
return index >= 0xC ? 7 : index;
|
||||
}
|
||||
|
||||
char add(const uint8_t r, const uint8_t g, const uint8_t b)
|
||||
{
|
||||
const char index = '0' + static_cast<char>(color_table.size());
|
||||
color_table.push_back(RGB(r, g, b));
|
||||
return index;
|
||||
}
|
||||
|
||||
void com_clean_name_stub(const char* in, char* out, const int out_size)
|
||||
{
|
||||
strncpy_s(out, out_size, in, _TRUNCATE);
|
||||
}
|
||||
|
||||
char* i_clean_str_stub(char* string)
|
||||
{
|
||||
utils::string::strip(string, string, std::strlen(string) + 1);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
size_t get_client_name_stub(const int local_client_num, const int index, char* buf, const int size,
|
||||
const size_t unk, const size_t unk2)
|
||||
{
|
||||
// CL_GetClientName (CL_GetClientNameAndClantag?)
|
||||
const auto result = reinterpret_cast<size_t(*)(int, int, char*, int, size_t, size_t)>(0x140213E60)(
|
||||
local_client_num, index, buf, size, unk, unk2);
|
||||
|
||||
utils::string::strip(buf, buf, static_cast<size_t>(size));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void rb_lookup_color_stub(const char index, DWORD* color)
|
||||
{
|
||||
*color = RGB(255, 255, 255);
|
||||
|
||||
if (index == '8')
|
||||
{
|
||||
*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x14CEDD978, 0x14D8594C4));
|
||||
}
|
||||
else if (index == '9')
|
||||
{
|
||||
*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x14CEDD97C, 0x14D8594C8));
|
||||
}
|
||||
else if (index == ':')
|
||||
{
|
||||
*color = hsv_to_rgb({static_cast<uint8_t>((game::Sys_Milliseconds() / 100) % 256), 255, 255});
|
||||
}
|
||||
else if (index == ';')
|
||||
{
|
||||
*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x14CEDD984, 0x14D8594D0));
|
||||
}
|
||||
else if (index == '<')
|
||||
{
|
||||
*color = 0xFFFCFF80;
|
||||
}
|
||||
else
|
||||
{
|
||||
*color = color_table[color_index(index)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!game::environment::is_sp())
|
||||
{
|
||||
// allows colored name in-game
|
||||
utils::hook::jump(0x1404C9510, com_clean_name_stub);
|
||||
|
||||
// don't apply colors to overhead names
|
||||
utils::hook::call(0x1401891B0, get_client_name_stub);
|
||||
|
||||
// patch I_CleanStr
|
||||
utils::hook::jump(0x1404C99A0, i_clean_str_stub);
|
||||
}
|
||||
|
||||
// force new colors
|
||||
utils::hook::jump(SELECT_VALUE(0x1404D3A20, 0x1405F2B90), rb_lookup_color_stub);
|
||||
|
||||
// add colors
|
||||
add(0, 0, 0); // 0 - Black
|
||||
add(255, 49, 49); // 1 - Red
|
||||
add(134, 192, 0); // 2 - Green
|
||||
add(255, 173, 34); // 3 - Yellow
|
||||
add(0, 135, 193); // 4 - Blue
|
||||
add(32, 197, 255); // 5 - Light Blue
|
||||
add(151, 80, 221); // 6 - Pink
|
||||
add(255, 255, 255); // 7 - White
|
||||
|
||||
add(0, 0, 0); // 8 - Team color (axis?)
|
||||
add(0, 0, 0); // 9 - Team color (allies?)
|
||||
|
||||
add(0, 0, 0); // 10 - Rainbow (:)
|
||||
add(0, 0, 0);
|
||||
// 11 - Server color (;) - using that color in infostrings (e.g. your name) fails, ';' is an illegal character!
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(colors::component)
|
677
src/client/component/command.cpp
Normal file
677
src/client/component/command.cpp
Normal file
@ -0,0 +1,677 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "game_console.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "fastfiles.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/memory.hpp>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace command
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr auto CMD_MAX_NESTING = 8;
|
||||
|
||||
utils::hook::detour client_command_hook;
|
||||
|
||||
std::unordered_map<std::string, std::function<void(params&)>> handlers;
|
||||
std::unordered_map<std::string, std::function<void(game::mp::gentity_s*, params_sv&)>> handlers_sv;
|
||||
|
||||
void main_handler()
|
||||
{
|
||||
params params = {};
|
||||
|
||||
const auto command = utils::string::to_lower(params[0]);
|
||||
if (const auto itr = handlers.find(command); itr != handlers.end())
|
||||
{
|
||||
itr->second(params);
|
||||
}
|
||||
}
|
||||
|
||||
void client_command(const char client_num)
|
||||
{
|
||||
if (game::mp::g_entities[client_num].client == nullptr)
|
||||
{
|
||||
// Client is not fully connected
|
||||
return;
|
||||
}
|
||||
|
||||
params_sv params = {};
|
||||
|
||||
const auto command = utils::string::to_lower(params[0]);
|
||||
if (const auto itr = handlers_sv.find(command); itr != handlers_sv.end())
|
||||
{
|
||||
itr->second(&game::mp::g_entities[client_num], params);
|
||||
}
|
||||
|
||||
client_command_hook.invoke<void>(client_num);
|
||||
}
|
||||
|
||||
// Shamelessly stolen from Quake3
|
||||
// https://github.com/id-Software/Quake-III-Arena/blob/dbe4ddb10315479fc00086f08e25d968b4b43c49/code/qcommon/common.c#L364
|
||||
void parse_command_line()
|
||||
{
|
||||
static auto parsed = false;
|
||||
if (parsed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static std::string comand_line_buffer = GetCommandLineA();
|
||||
auto* command_line = comand_line_buffer.data();
|
||||
|
||||
auto& com_num_console_lines = *reinterpret_cast<int*>(0x147B76504);
|
||||
auto* com_console_lines = reinterpret_cast<char**>(0x147B76510);
|
||||
|
||||
auto inq = false;
|
||||
com_console_lines[0] = command_line;
|
||||
com_num_console_lines = 0;
|
||||
|
||||
while (*command_line)
|
||||
{
|
||||
if (*command_line == '"')
|
||||
{
|
||||
inq = !inq;
|
||||
}
|
||||
// look for a + separating character
|
||||
// if commandLine came from a file, we might have real line seperators
|
||||
if ((*command_line == '+' && !inq) || *command_line == '\n' || *command_line == '\r')
|
||||
{
|
||||
if (com_num_console_lines == 0x20) // MAX_CONSOLE_LINES
|
||||
{
|
||||
break;
|
||||
}
|
||||
com_console_lines[com_num_console_lines] = command_line + 1;
|
||||
com_num_console_lines++;
|
||||
*command_line = '\0';
|
||||
}
|
||||
command_line++;
|
||||
}
|
||||
parsed = true;
|
||||
}
|
||||
|
||||
void parse_commandline_stub()
|
||||
{
|
||||
parse_command_line();
|
||||
reinterpret_cast<void(*)()>(0x1403CEE10)();
|
||||
}
|
||||
}
|
||||
|
||||
void read_startup_variable(const std::string& dvar)
|
||||
{
|
||||
// parse the commandline if it's not parsed
|
||||
parse_command_line();
|
||||
|
||||
auto& com_num_console_lines = *reinterpret_cast<int*>(0x147B76504);
|
||||
auto* com_console_lines = reinterpret_cast<char**>(0x147B76510);
|
||||
|
||||
for (int i = 0; i < com_num_console_lines; i++)
|
||||
{
|
||||
game::Cmd_TokenizeString(com_console_lines[i]);
|
||||
|
||||
// only +set dvar value
|
||||
if (game::Cmd_Argc() >= 3 && game::Cmd_Argv(0) == "set"s && game::Cmd_Argv(1) == dvar)
|
||||
{
|
||||
game::Dvar_SetCommand(game::Cmd_Argv(1), game::Cmd_Argv(2));
|
||||
}
|
||||
|
||||
game::Cmd_EndTokenizeString();
|
||||
}
|
||||
}
|
||||
|
||||
params::params()
|
||||
: nesting_(game::cmd_args->nesting)
|
||||
{
|
||||
assert(this->nesting_ < CMD_MAX_NESTING);
|
||||
}
|
||||
|
||||
int params::size() const
|
||||
{
|
||||
return game::cmd_args->argc[this->nesting_];
|
||||
}
|
||||
|
||||
const char* params::get(const int index) const
|
||||
{
|
||||
if (index >= this->size())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return game::cmd_args->argv[this->nesting_][index];
|
||||
}
|
||||
|
||||
std::string params::join(const int index) const
|
||||
{
|
||||
std::string result = {};
|
||||
|
||||
for (auto i = index; i < this->size(); i++)
|
||||
{
|
||||
if (i > index) result.append(" ");
|
||||
result.append(this->get(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
params_sv::params_sv()
|
||||
: nesting_(game::sv_cmd_args->nesting)
|
||||
{
|
||||
assert(this->nesting_ < CMD_MAX_NESTING);
|
||||
}
|
||||
|
||||
int params_sv::size() const
|
||||
{
|
||||
return game::sv_cmd_args->argc[this->nesting_];
|
||||
}
|
||||
|
||||
const char* params_sv::get(const int index) const
|
||||
{
|
||||
if (index >= this->size())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return game::sv_cmd_args->argv[this->nesting_][index];
|
||||
}
|
||||
|
||||
std::string params_sv::join(const int index) const
|
||||
{
|
||||
std::string result;
|
||||
|
||||
for (auto i = index; i < this->size(); i++)
|
||||
{
|
||||
if (i > index) result.append(" ");
|
||||
result.append(this->get(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void add_raw(const char* name, void (*callback)())
|
||||
{
|
||||
game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate<game::cmd_function_s>());
|
||||
}
|
||||
|
||||
void add(const char* name, const std::function<void(const params&)>& callback)
|
||||
{
|
||||
const auto command = utils::string::to_lower(name);
|
||||
|
||||
if (!handlers.contains(command))
|
||||
{
|
||||
add_raw(name, main_handler);
|
||||
}
|
||||
|
||||
handlers[command] = callback;
|
||||
}
|
||||
|
||||
void add(const char* name, const std::function<void()>& callback)
|
||||
{
|
||||
add(name, [callback](const params&)
|
||||
{
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
void add_sv(const char* name, const std::function<void(game::mp::gentity_s*, const params_sv&)>& callback)
|
||||
{
|
||||
// doing this so the sv command would show up in the console
|
||||
add_raw(name, nullptr);
|
||||
|
||||
const auto command = utils::string::to_lower(name);
|
||||
|
||||
if (!handlers_sv.contains(command))
|
||||
{
|
||||
handlers_sv[command] = callback;
|
||||
}
|
||||
}
|
||||
|
||||
bool cheats_ok(const game::mp::gentity_s* ent)
|
||||
{
|
||||
if (!dvars::sv_cheats->current.enabled)
|
||||
{
|
||||
game::SV_GameSendServerCommand(ent->s.number, game::SV_CMD_RELIABLE,
|
||||
"f \"Cheats are not enabled on this server\"");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ent->health < 1)
|
||||
{
|
||||
game::SV_GameSendServerCommand(ent->s.number, game::SV_CMD_RELIABLE,
|
||||
"f \"You must be alive to use this command\"");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void execute(std::string command, const bool sync)
|
||||
{
|
||||
command += "\n";
|
||||
|
||||
if (sync)
|
||||
{
|
||||
game::Cmd_ExecuteSingleCommand(0, 0, command.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
game::Cbuf_AddText(0, command.data());
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
add_commands_sp();
|
||||
}
|
||||
else
|
||||
{
|
||||
utils::hook::call(0x1403CDF1C, &parse_commandline_stub);
|
||||
add_commands_mp();
|
||||
}
|
||||
|
||||
add_commands_generic();
|
||||
}
|
||||
|
||||
private:
|
||||
static void add_commands_generic()
|
||||
{
|
||||
add("quit", game::Com_Quit_f);
|
||||
add("crash", []
|
||||
{
|
||||
*reinterpret_cast<int*>(1) = 0x12345678;
|
||||
});
|
||||
|
||||
add("consoleList", [](const params& params)
|
||||
{
|
||||
const std::string input = params.get(1);
|
||||
|
||||
std::vector<std::string> matches;
|
||||
game_console::find_matches(input, matches, false);
|
||||
|
||||
for(auto& match : matches)
|
||||
{
|
||||
auto* dvar = game::Dvar_FindVar(match.c_str());
|
||||
if (!dvar)
|
||||
{
|
||||
console::info("[CMD]\t %s\n", match.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::info("[DVAR]\t%s \"%s\"\n", match.c_str(), game::Dvar_ValueToString(dvar, dvar->current));
|
||||
}
|
||||
}
|
||||
|
||||
console::info("Total %i matches\n", matches.size());
|
||||
});
|
||||
|
||||
add("dvarDump", [](const params& argument)
|
||||
{
|
||||
console::info("================================ DVAR DUMP ========================================\n");
|
||||
std::string filename;
|
||||
if (argument.size() == 2)
|
||||
{
|
||||
filename = "s1/";
|
||||
filename.append(argument[1]);
|
||||
if (!filename.ends_with(".txt"))
|
||||
{
|
||||
filename.append(".txt");
|
||||
}
|
||||
}
|
||||
for (auto i = 0; i < *game::dvarCount; i++)
|
||||
{
|
||||
const auto dvar = game::sortedDvars[i];
|
||||
if (dvar)
|
||||
{
|
||||
if (!filename.empty())
|
||||
{
|
||||
const auto line = std::format("{} \"{}\"\r\n", dvar->name,
|
||||
game::Dvar_ValueToString(dvar, dvar->current));
|
||||
utils::io::write_file(filename, line, i != 0);
|
||||
}
|
||||
console::info("%s \"%s\"\n", dvar->name,
|
||||
game::Dvar_ValueToString(dvar, dvar->current));
|
||||
}
|
||||
}
|
||||
console::info("\n%i dvars\n", *game::dvarCount);
|
||||
console::info("================================ END DVAR DUMP ====================================\n");
|
||||
});
|
||||
|
||||
add("commandDump", [](const params& argument)
|
||||
{
|
||||
console::info("================================ COMMAND DUMP =====================================\n");
|
||||
game::cmd_function_s* cmd = (*game::cmd_functions);
|
||||
std::string filename;
|
||||
if (argument.size() == 2)
|
||||
{
|
||||
filename = "s1/";
|
||||
filename.append(argument[1]);
|
||||
if (!filename.ends_with(".txt"))
|
||||
{
|
||||
filename.append(".txt");
|
||||
}
|
||||
}
|
||||
int i = 0;
|
||||
while (cmd)
|
||||
{
|
||||
if (cmd->name)
|
||||
{
|
||||
if (!filename.empty())
|
||||
{
|
||||
const auto line = std::format("{}\r\n", cmd->name);
|
||||
utils::io::write_file(filename, line, i != 0);
|
||||
}
|
||||
console::info("%s\n", cmd->name);
|
||||
i++;
|
||||
}
|
||||
cmd = cmd->next;
|
||||
}
|
||||
console::info("\n%i commands\n", i);
|
||||
console::info("================================ END COMMAND DUMP =================================\n");
|
||||
});
|
||||
|
||||
add("listassetpool", [](const params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("listassetpool <poolnumber> [filter]: list all the assets in the specified pool\n");
|
||||
|
||||
for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++)
|
||||
{
|
||||
console::info("%d %s\n", i, game::g_assetNames[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto type = static_cast<game::XAssetType>(atoi(params.get(1)));
|
||||
|
||||
if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT)
|
||||
{
|
||||
console::error("Invalid pool passed must be between [%d, %d]\n", 0, game::XAssetType::ASSET_TYPE_COUNT - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
console::info("Listing assets in pool %s\n", game::g_assetNames[type]);
|
||||
|
||||
const std::string filter = params.get(2);
|
||||
fastfiles::enum_assets(type, [type, filter](const game::XAssetHeader header)
|
||||
{
|
||||
const auto asset = game::XAsset{ type, header };
|
||||
const auto* const asset_name = game::DB_GetXAssetName(&asset);
|
||||
//const auto entry = game::DB_FindXAssetEntry(type, asset_name);
|
||||
//TODO: display which zone the asset is from
|
||||
|
||||
if (!filter.empty() && !game_console::match_compare(filter, asset_name, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
console::info("%s\n", asset_name);
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
|
||||
add("vstr", [](const params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("vstr <variablename> : execute a variable command\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* dvarName = params.get(1);
|
||||
const auto* dvar = game::Dvar_FindVar(dvarName);
|
||||
|
||||
if (dvar == nullptr)
|
||||
{
|
||||
console::info("%s doesn't exist\n", dvarName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dvar->type != game::dvar_type::string
|
||||
&& dvar->type != game::dvar_type::enumeration)
|
||||
{
|
||||
console::info("%s is not a string-based dvar\n", dvar->name);
|
||||
return;
|
||||
}
|
||||
|
||||
execute(dvar->current.string);
|
||||
});
|
||||
}
|
||||
|
||||
static void add_commands_sp()
|
||||
{
|
||||
add("god", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::sp::g_entities[0].flags ^= 1;
|
||||
game::CG_GameMessage(0, utils::string::va("godmode %s",
|
||||
game::sp::g_entities[0].flags & 1
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add("demigod", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::sp::g_entities[0].flags ^= 2;
|
||||
game::CG_GameMessage(0, utils::string::va("demigod mode %s",
|
||||
game::sp::g_entities[0].flags & 2
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add("notarget", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::sp::g_entities[0].flags ^= 4;
|
||||
game::CG_GameMessage(0, utils::string::va("notarget %s",
|
||||
game::sp::g_entities[0].flags & 4
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add("noclip", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::sp::g_entities[0].client->flags ^= 1;
|
||||
game::CG_GameMessage(0, utils::string::va("noclip %s",
|
||||
game::sp::g_entities[0].client->flags & 1
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add("ufo", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::sp::g_entities[0].client->flags ^= 2;
|
||||
game::CG_GameMessage(0, utils::string::va("ufo %s",
|
||||
game::sp::g_entities[0].client->flags & 2
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add("give", [](const params& params)
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.size() < 2)
|
||||
{
|
||||
game::CG_GameMessage(0, "You did not specify a weapon name");
|
||||
return;
|
||||
}
|
||||
|
||||
auto ps = game::SV_GetPlayerstateForClientNum(0);
|
||||
const auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
if (wp)
|
||||
{
|
||||
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0, 0, 0))
|
||||
{
|
||||
game::G_InitializeAmmo(ps, wp, 0);
|
||||
game::G_SelectWeapon(0, wp);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add("take", [](const params& params)
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.size() < 2)
|
||||
{
|
||||
game::CG_GameMessage(0, "You did not specify a weapon name");
|
||||
return;
|
||||
}
|
||||
|
||||
auto ps = game::SV_GetPlayerstateForClientNum(0);
|
||||
const auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
if (wp)
|
||||
{
|
||||
game::G_TakePlayerWeapon(ps, wp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void add_commands_mp()
|
||||
{
|
||||
client_command_hook.create(0x1402E98F0, &client_command);
|
||||
|
||||
add_sv("god", [](game::mp::gentity_s* ent, const params_sv&)
|
||||
{
|
||||
if (!cheats_ok(ent))
|
||||
return;
|
||||
|
||||
ent->flags ^= game::FL_GODMODE;
|
||||
|
||||
game::SV_GameSendServerCommand(ent->s.number, game::SV_CMD_RELIABLE,
|
||||
utils::string::va("f \"godmode %s\"", (ent->flags & game::FL_GODMODE) ? "^2on" : "^1off"));
|
||||
});
|
||||
|
||||
add_sv("demigod", [](game::mp::gentity_s* ent, const params_sv&)
|
||||
{
|
||||
if (!cheats_ok(ent))
|
||||
return;
|
||||
|
||||
ent->flags ^= game::FL_DEMI_GODMODE;
|
||||
|
||||
game::SV_GameSendServerCommand(ent->s.number, game::SV_CMD_RELIABLE,
|
||||
utils::string::va("f \"demigod mode %s\"", (ent->flags & game::FL_DEMI_GODMODE) ? "^2on" : "^1off"));
|
||||
});
|
||||
|
||||
add_sv("notarget", [](game::mp::gentity_s* ent, const params_sv&)
|
||||
{
|
||||
if (!cheats_ok(ent))
|
||||
return;
|
||||
|
||||
ent->flags ^= game::FL_NOTARGET;
|
||||
|
||||
game::SV_GameSendServerCommand(ent->s.number, game::SV_CMD_RELIABLE,
|
||||
utils::string::va("f \"notarget %s\"", (ent->flags & game::FL_NOTARGET) ? "^2on" : "^1off"));
|
||||
});
|
||||
|
||||
add_sv("noclip", [](game::mp::gentity_s* ent, const params_sv&)
|
||||
{
|
||||
if (!cheats_ok(ent))
|
||||
return;
|
||||
|
||||
ent->client->flags ^= 1;
|
||||
|
||||
game::SV_GameSendServerCommand(ent->s.number, game::SV_CMD_RELIABLE,
|
||||
utils::string::va("f \"noclip %s\"", ent->client->flags & 1 ? "^2on" : "^1off"));
|
||||
});
|
||||
|
||||
add_sv("ufo", [](game::mp::gentity_s* ent, const params_sv&)
|
||||
{
|
||||
if (!cheats_ok(ent))
|
||||
return;
|
||||
|
||||
ent->client->flags ^= 2;
|
||||
|
||||
game::SV_GameSendServerCommand(ent->s.number, game::SV_CMD_RELIABLE,
|
||||
utils::string::va("f \"ufo %s\"", ent->client->flags & 2 ? "^2on" : "^1off"));
|
||||
});
|
||||
|
||||
add_sv("give", [](game::mp::gentity_s* ent, const params_sv& params)
|
||||
{
|
||||
if (!cheats_ok(ent))
|
||||
return;
|
||||
|
||||
if (params.size() < 2)
|
||||
{
|
||||
game::SV_GameSendServerCommand(ent->s.number, game::SV_CMD_RELIABLE,
|
||||
"f \"You did not specify a weapon name\"");
|
||||
return;
|
||||
}
|
||||
|
||||
auto ps = game::SV_GetPlayerstateForClientNum(ent->s.number);
|
||||
const auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
if (wp)
|
||||
{
|
||||
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0, 0, 0))
|
||||
{
|
||||
game::G_InitializeAmmo(ps, wp, 0);
|
||||
game::G_SelectWeapon(ent->s.number, wp);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_sv("take", [](game::mp::gentity_s* ent, const params_sv& params)
|
||||
{
|
||||
if (!cheats_ok(ent))
|
||||
return;
|
||||
|
||||
if (params.size() < 2)
|
||||
{
|
||||
game::SV_GameSendServerCommand(ent->s.number, game::SV_CMD_RELIABLE,
|
||||
"f \"You did not specify a weapon name\"");
|
||||
return;
|
||||
}
|
||||
|
||||
auto ps = game::SV_GetPlayerstateForClientNum(ent->s.number);
|
||||
const auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
if (wp)
|
||||
{
|
||||
game::G_TakePlayerWeapon(ps, wp);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(command::component)
|
50
src/client/component/command.hpp
Normal file
50
src/client/component/command.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
namespace command
|
||||
{
|
||||
class params
|
||||
{
|
||||
public:
|
||||
params();
|
||||
|
||||
int size() const;
|
||||
const char* get(int index) const;
|
||||
std::string join(int index) const;
|
||||
|
||||
const char* operator[](const int index) const
|
||||
{
|
||||
return this->get(index); //
|
||||
}
|
||||
|
||||
private:
|
||||
int nesting_;
|
||||
};
|
||||
|
||||
class params_sv
|
||||
{
|
||||
public:
|
||||
params_sv();
|
||||
|
||||
int size() const;
|
||||
const char* get(int index) const;
|
||||
std::string join(int index) const;
|
||||
|
||||
const char* operator[](const int index) const
|
||||
{
|
||||
return this->get(index); //
|
||||
}
|
||||
|
||||
private:
|
||||
int nesting_;
|
||||
};
|
||||
|
||||
void read_startup_variable(const std::string& dvar);
|
||||
|
||||
void add_raw(const char* name, void (*callback)());
|
||||
void add(const char* name, const std::function<void(const params&)>& callback);
|
||||
void add(const char* name, const std::function<void()>& callback);
|
||||
|
||||
void add_sv(const char* name, const std::function<void(game::mp::gentity_s*, const params_sv&)>& callback);
|
||||
|
||||
void execute(std::string command, bool sync = false);
|
||||
}
|
260
src/client/component/console.cpp
Normal file
260
src/client/component/console.cpp
Normal file
@ -0,0 +1,260 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "game_console.hpp"
|
||||
#include "rcon.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/thread.hpp>
|
||||
|
||||
namespace console
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using message_queue = std::queue<std::string>;
|
||||
utils::concurrency::container<message_queue> message_queue_;
|
||||
|
||||
std::atomic_bool started_{false};
|
||||
std::atomic_bool terminate_runner_{false};
|
||||
|
||||
void print_message(const char* message)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
OutputDebugStringA(message);
|
||||
#endif
|
||||
|
||||
if (game::is_headless())
|
||||
{
|
||||
std::fputs(message, stdout);
|
||||
}
|
||||
else
|
||||
{
|
||||
game::Conbuf_AppendText(message);
|
||||
}
|
||||
}
|
||||
|
||||
std::string format(va_list* ap, const char* message)
|
||||
{
|
||||
static thread_local char buffer[0x1000];
|
||||
|
||||
const auto count = vsnprintf_s(buffer, _TRUNCATE, message, *ap);
|
||||
|
||||
if (count < 0) return {};
|
||||
return {buffer, static_cast<size_t>(count)};
|
||||
}
|
||||
|
||||
void dispatch_message(const int type, const std::string& message)
|
||||
{
|
||||
if (rcon::message_redirect(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game_console::print(type, message);
|
||||
|
||||
if (game::is_headless())
|
||||
{
|
||||
std::fputs(message.data(), stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
message_queue_.access([&message](message_queue& queue)
|
||||
{
|
||||
queue.emplace(message);
|
||||
});
|
||||
}
|
||||
|
||||
message_queue empty_message_queue()
|
||||
{
|
||||
message_queue current_queue{};
|
||||
|
||||
message_queue_.access([&](message_queue& queue)
|
||||
{
|
||||
current_queue = std::move(queue);
|
||||
queue = {};
|
||||
});
|
||||
|
||||
return current_queue;
|
||||
}
|
||||
|
||||
void print_stub(const char* fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
|
||||
char buffer[4096]{};
|
||||
const auto res = vsnprintf_s(buffer, _TRUNCATE, fmt, ap);
|
||||
(void)res;
|
||||
print_message(buffer);
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void append_text(const char* text)
|
||||
{
|
||||
dispatch_message(con_type_info, text);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
component()
|
||||
{
|
||||
if (game::is_headless())
|
||||
{
|
||||
if (!AttachConsole(ATTACH_PARENT_PROCESS))
|
||||
{
|
||||
AllocConsole();
|
||||
AttachConsole(GetCurrentProcessId());
|
||||
}
|
||||
|
||||
ShowWindow(GetConsoleWindow(), SW_SHOW);
|
||||
|
||||
FILE* fp;
|
||||
freopen_s(&fp, "CONIN$", "r", stdin);
|
||||
freopen_s(&fp, "CONOUT$", "w", stdout);
|
||||
freopen_s(&fp, "CONOUT$", "w", stderr);
|
||||
}
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
// Redirect input (]command)
|
||||
utils::hook::jump(SELECT_VALUE(0x14038F3E0, 0x1404D9200), append_text);
|
||||
|
||||
utils::hook::jump(printf, print_stub);
|
||||
|
||||
if (game::is_headless())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
terminate_runner_ = false;
|
||||
|
||||
this->message_runner_ = utils::thread::create_named_thread("Console IO", []
|
||||
{
|
||||
while (!started_)
|
||||
{
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
|
||||
while (!terminate_runner_)
|
||||
{
|
||||
std::string message_buffer{};
|
||||
auto current_queue = empty_message_queue();
|
||||
|
||||
while (!current_queue.empty())
|
||||
{
|
||||
const auto& msg = current_queue.front();
|
||||
message_buffer.append(msg);
|
||||
current_queue.pop();
|
||||
}
|
||||
|
||||
if (!message_buffer.empty())
|
||||
{
|
||||
print_message(message_buffer.data());
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(5ms);
|
||||
}
|
||||
});
|
||||
|
||||
this->console_runner_ = utils::thread::create_named_thread("Console Window", [this]
|
||||
{
|
||||
game::Sys_ShowConsole();
|
||||
|
||||
MSG msg{};
|
||||
while (!terminate_runner_)
|
||||
{
|
||||
if (PeekMessageW(&msg, nullptr, NULL, NULL, PM_REMOVE))
|
||||
{
|
||||
if (msg.message == WM_QUIT)
|
||||
{
|
||||
command::execute("quit", false);
|
||||
break;
|
||||
}
|
||||
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::sleep_for(5ms);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Give the console a chance to open or we will lose some early messages
|
||||
// like the ones printed from the filesystem component
|
||||
scheduler::once([]() -> void
|
||||
{
|
||||
started_ = true;
|
||||
}, scheduler::pipeline::main);
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
terminate_runner_ = true;
|
||||
|
||||
if (this->message_runner_.joinable())
|
||||
{
|
||||
this->message_runner_.join();
|
||||
}
|
||||
|
||||
if (this->console_runner_.joinable())
|
||||
{
|
||||
this->console_runner_.join();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::thread console_runner_{};
|
||||
std::thread message_runner_{};
|
||||
};
|
||||
|
||||
HWND get_window()
|
||||
{
|
||||
return *reinterpret_cast<HWND*>((SELECT_VALUE(0x14A9F6070, 0x14B5B94C0)));
|
||||
}
|
||||
|
||||
void set_title(std::string title)
|
||||
{
|
||||
if (game::is_headless())
|
||||
{
|
||||
SetConsoleTitleA(title.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
SetWindowTextA(get_window(), title.data());
|
||||
}
|
||||
}
|
||||
|
||||
void set_size(const int width, const int height)
|
||||
{
|
||||
RECT rect;
|
||||
GetWindowRect(get_window(), &rect);
|
||||
|
||||
SetWindowPos(get_window(), nullptr, rect.left, rect.top, width, height, 0);
|
||||
|
||||
auto* const logo_window = *reinterpret_cast<HWND*>(SELECT_VALUE(0x14A9F6080, 0x14B5B94D0));
|
||||
SetWindowPos(logo_window, nullptr, 5, 5, width - 25, 60, 0);
|
||||
}
|
||||
|
||||
void print(const int type, const char* fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
const auto result = format(&ap, fmt);
|
||||
va_end(ap);
|
||||
|
||||
dispatch_message(type, result);
|
||||
}
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(console::component)
|
35
src/client/component/console.hpp
Normal file
35
src/client/component/console.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
namespace console
|
||||
{
|
||||
HWND get_window();
|
||||
void set_title(std::string title);
|
||||
void set_size(int width, int height);
|
||||
|
||||
enum console_type
|
||||
{
|
||||
con_type_error = 1,
|
||||
con_type_warning = 3,
|
||||
con_type_info = 7
|
||||
};
|
||||
|
||||
void print(int type, const char* fmt, ...);
|
||||
|
||||
template <typename... Args>
|
||||
void error(const char* fmt, Args&&... args)
|
||||
{
|
||||
print(con_type_error, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void warn(const char* fmt, Args&&... args)
|
||||
{
|
||||
print(con_type_warning, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void info(const char* fmt, Args&&... args)
|
||||
{
|
||||
print(con_type_info, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
326
src/client/component/dedicated.cpp
Normal file
326
src/client/component/dedicated.cpp
Normal file
@ -0,0 +1,326 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "console.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "server_list.hpp"
|
||||
#include "network.hpp"
|
||||
#include "command.hpp"
|
||||
#include "dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace dedicated
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour gscr_set_dynamic_dvar_hook;
|
||||
utils::hook::detour com_quit_f_hook;
|
||||
|
||||
const game::dvar_t* sv_lanOnly;
|
||||
|
||||
void init_dedicated_server()
|
||||
{
|
||||
static bool initialized = false;
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
// R_LoadGraphicsAssets
|
||||
reinterpret_cast<void(*)()>(0x1405A54F0)();
|
||||
}
|
||||
|
||||
void send_heartbeat()
|
||||
{
|
||||
if (sv_lanOnly->current.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::netadr_s target{};
|
||||
if (server_list::get_master_server(target))
|
||||
{
|
||||
network::send(target, "heartbeat", "S1");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string>& get_startup_command_queue()
|
||||
{
|
||||
static std::vector<std::string> startup_command_queue;
|
||||
return startup_command_queue;
|
||||
}
|
||||
|
||||
void execute_startup_command(int client, int /*controllerIndex*/, const char* command)
|
||||
{
|
||||
if (game::Live_SyncOnlineDataFlags(0) == 0)
|
||||
{
|
||||
game::Cbuf_ExecuteBufferInternal(0, 0, command, game::Cmd_ExecuteSingleCommand);
|
||||
}
|
||||
else
|
||||
{
|
||||
get_startup_command_queue().emplace_back(command);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_startup_command_queue()
|
||||
{
|
||||
const auto queue = get_startup_command_queue();
|
||||
get_startup_command_queue().clear();
|
||||
|
||||
for (const auto& command : queue)
|
||||
{
|
||||
game::Cbuf_ExecuteBufferInternal(0, 0, command.data(), game::Cmd_ExecuteSingleCommand);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string>& get_console_command_queue()
|
||||
{
|
||||
static std::vector<std::string> console_command_queue;
|
||||
return console_command_queue;
|
||||
}
|
||||
|
||||
void execute_console_command([[maybe_unused]] const int local_client_num, const char* command)
|
||||
{
|
||||
if (game::Live_SyncOnlineDataFlags(0) == 0)
|
||||
{
|
||||
command::execute(command);
|
||||
}
|
||||
else
|
||||
{
|
||||
get_console_command_queue().emplace_back(command);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_console_command_queue()
|
||||
{
|
||||
const auto queue = get_console_command_queue();
|
||||
get_console_command_queue().clear();
|
||||
|
||||
for (const auto& command : queue)
|
||||
{
|
||||
game::Cbuf_AddText(0, command.data());
|
||||
game::Cbuf_AddText(0, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void sync_gpu_stub()
|
||||
{
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
|
||||
void gscr_set_dynamic_dvar()
|
||||
{
|
||||
auto s = game::Scr_GetString(0);
|
||||
auto* dvar = game::Dvar_FindVar(s);
|
||||
if (dvar && !std::strncmp("scr_", dvar->name, 4))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gscr_set_dynamic_dvar_hook.invoke<void>();
|
||||
}
|
||||
|
||||
void kill_server()
|
||||
{
|
||||
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
|
||||
{
|
||||
if (game::mp::svs_clients[i].header.state >= 3)
|
||||
{
|
||||
game::SV_GameSendServerCommand(i, game::SV_CMD_CAN_IGNORE,
|
||||
utils::string::va("r \"%s\"", "EXE_ENDOFGAME"));
|
||||
}
|
||||
}
|
||||
|
||||
com_quit_f_hook.invoke<void>();
|
||||
}
|
||||
|
||||
void sys_error_stub(const char* msg, ...)
|
||||
{
|
||||
char buffer[2048]{};
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
|
||||
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
scheduler::once([]
|
||||
{
|
||||
command::execute("map_rotate");
|
||||
}, scheduler::main, 3s);
|
||||
|
||||
game::Com_Error(game::ERR_DROP, "%s", buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void initialize()
|
||||
{
|
||||
command::execute("exec default_xboxlive.cfg", true);
|
||||
command::execute("onlinegame 1", true);
|
||||
command::execute("xblive_privatematch 1", true);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::Dvar_RegisterBool("dedicated", true, game::DVAR_FLAG_READ, "Dedicated server");
|
||||
|
||||
sv_lanOnly = game::Dvar_RegisterBool("sv_lanOnly", false, game::DVAR_FLAG_NONE, "Don't send heartbeat");
|
||||
|
||||
// Disable VirtualLobby
|
||||
dvars::override::register_bool("virtualLobbyEnabled", false, game::DVAR_FLAG_NONE | game::DVAR_FLAG_READ);
|
||||
|
||||
// Disable r_preloadShaders
|
||||
dvars::override::register_bool("r_preloadShaders", false, game::DVAR_FLAG_NONE | game::DVAR_FLAG_READ);
|
||||
|
||||
// Don't allow sv_hostname to be changed by the game
|
||||
dvars::disable::set_string("sv_hostname");
|
||||
|
||||
// Stop crashing from sys_errors
|
||||
utils::hook::jump(0x1404D6260, sys_error_stub);
|
||||
|
||||
// Hook R_SyncGpu
|
||||
utils::hook::jump(0x1405A7630, sync_gpu_stub);
|
||||
|
||||
utils::hook::jump(0x14020C6B0, init_dedicated_server);
|
||||
|
||||
// delay startup commands until the initialization is done
|
||||
utils::hook::call(0x1403CDF63, execute_startup_command);
|
||||
|
||||
// delay console commands until the initialization is done
|
||||
utils::hook::call(0x1403CEC35, execute_console_command);
|
||||
utils::hook::nop(0x1403CEC4B, 5);
|
||||
|
||||
// patch GScr_SetDynamicDvar to behave better
|
||||
gscr_set_dynamic_dvar_hook.create(0x140312D00, &gscr_set_dynamic_dvar);
|
||||
|
||||
utils::hook::nop(0x1404AE6AE, 5); // don't load config file
|
||||
utils::hook::nop(0x1403AF719, 5); // ^
|
||||
utils::hook::set<uint8_t>(0x1403D2490, 0xC3); // don't save config file
|
||||
utils::hook::set<uint8_t>(0x14022AFC0, 0xC3); // disable self-registration
|
||||
utils::hook::set<uint8_t>(0x1404DA780, 0xC3); // init sound system (1)
|
||||
utils::hook::set<uint8_t>(0x14062BC10, 0xC3); // init sound system (2)
|
||||
utils::hook::set<uint8_t>(0x1405F31A0, 0xC3); // render thread
|
||||
utils::hook::set<uint8_t>(0x140213C20, 0xC3); // called from Com_Frame, seems to do renderer stuff
|
||||
utils::hook::set<uint8_t>(0x1402085C0, 0xC3);
|
||||
// CL_CheckForResend, which tries to connect to the local server constantly
|
||||
utils::hook::set<uint8_t>(0x14059B854, 0); // r_loadForRenderer default to 0
|
||||
utils::hook::set<uint8_t>(0x1404D6952, 0xC3); // recommended settings check - TODO: Check hook
|
||||
utils::hook::set<uint8_t>(0x1404D9BA0, 0xC3); // some mixer-related function called on shutdown
|
||||
utils::hook::set<uint8_t>(0x1403B2860, 0xC3); // dont load ui gametype stuff
|
||||
utils::hook::nop(0x14043ABB8, 6); // unknown check in SV_ExecuteClientMessage
|
||||
utils::hook::nop(0x140439F15, 4); // allow first slot to be occupied
|
||||
utils::hook::nop(0x14020E01C, 2); // properly shut down dedicated servers
|
||||
utils::hook::nop(0x14020DFE9, 2); // ^
|
||||
utils::hook::nop(0x14020E047, 5); // don't shutdown renderer
|
||||
|
||||
utils::hook::set<uint8_t>(0x140057D40, 0xC3); // something to do with blendShapeVertsView
|
||||
utils::hook::nop(0x14062EA17, 8); // sound thing
|
||||
|
||||
utils::hook::set<uint8_t>(0x1404D6960, 0xC3); // cpu detection stuff?
|
||||
utils::hook::set<uint8_t>(0x1405AEC00, 0xC3); // gfx stuff during fastfile loading
|
||||
utils::hook::set<uint8_t>(0x1405AEB10, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405AEBA0, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x140275640, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405AEB60, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x140572640, 0xC3); // directx stuff
|
||||
utils::hook::set<uint8_t>(0x1405A1340, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x140021D60, 0xC3); // ^ - mutex
|
||||
utils::hook::set<uint8_t>(0x1405A17E0, 0xC3); // ^
|
||||
|
||||
utils::hook::set<uint8_t>(0x1400534F0, 0xC3); // rendering stuff
|
||||
utils::hook::set<uint8_t>(0x1405A1AB0, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405A1BB0, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405A21F0, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405A2D60, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405A3400, 0xC3); // ^
|
||||
|
||||
// shaders
|
||||
utils::hook::set<uint8_t>(0x140057BC0, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x140057B40, 0xC3); // ^
|
||||
|
||||
utils::hook::set<uint8_t>(0x1405EE040, 0xC3); // ^ - mutex
|
||||
|
||||
utils::hook::set<uint8_t>(0x1404DAF30, 0xC3); // idk
|
||||
utils::hook::set<uint8_t>(0x1405736B0, 0xC3); // ^
|
||||
|
||||
utils::hook::set<uint8_t>(0x1405A6E70, 0xC3); // R_Shutdown
|
||||
utils::hook::set<uint8_t>(0x1405732D0, 0xC3); // shutdown stuff
|
||||
utils::hook::set<uint8_t>(0x1405A6F40, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405A61A0, 0xC3); // ^
|
||||
|
||||
utils::hook::set<uint8_t>(0x14062C550, 0xC3); // sound crashes
|
||||
|
||||
utils::hook::set<uint8_t>(0x140445070, 0xC3); // disable host migration
|
||||
|
||||
utils::hook::set<uint8_t>(0x1403E1A50, 0xC3); // render synchronization lock
|
||||
utils::hook::set<uint8_t>(0x1403E1990, 0xC3); // render synchronization unlock
|
||||
|
||||
utils::hook::set<uint8_t>(0x1400E517B, 0xEB);
|
||||
// LUI: Unable to start the LUI system due to errors in main.lua
|
||||
|
||||
utils::hook::nop(0x1404CC482, 5); // Disable sound pak file loading
|
||||
utils::hook::nop(0x1404CC471, 2); // ^
|
||||
utils::hook::set<uint8_t>(0x140279B80, 0xC3); // Disable image pak file loading
|
||||
|
||||
// Reduce min required memory
|
||||
utils::hook::set<uint64_t>(0x1404D140D, 0x80000000);
|
||||
utils::hook::set<uint64_t>(0x1404D14BF, 0x80000000);
|
||||
|
||||
// initialize the game after onlinedataflags is 32 (workaround)
|
||||
scheduler::schedule([=]()
|
||||
{
|
||||
if (game::Live_SyncOnlineDataFlags(0) == 32 && game::Sys_IsDatabaseReady2())
|
||||
{
|
||||
scheduler::once([]
|
||||
{
|
||||
command::execute("xstartprivateparty", true);
|
||||
command::execute("disconnect", true); // 32 -> 0
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
return scheduler::cond_end;
|
||||
}
|
||||
|
||||
return scheduler::cond_continue;
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
|
||||
scheduler::on_game_initialized([]
|
||||
{
|
||||
initialize();
|
||||
|
||||
console::info("==================================\n");
|
||||
console::info("Server started!\n");
|
||||
console::info("==================================\n");
|
||||
|
||||
// remove disconnect command
|
||||
game::Cmd_RemoveCommand(reinterpret_cast<const char*>(751));
|
||||
|
||||
execute_startup_command_queue();
|
||||
execute_console_command_queue();
|
||||
|
||||
// Send heartbeat to dpmaster
|
||||
scheduler::once(send_heartbeat, scheduler::pipeline::server);
|
||||
scheduler::loop(send_heartbeat, scheduler::pipeline::server, 10min);
|
||||
command::add("heartbeat", send_heartbeat);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
|
||||
command::add("killserver", kill_server);
|
||||
com_quit_f_hook.create(0x1403D08C0, &kill_server);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(dedicated::component)
|
6
src/client/component/dedicated.hpp
Normal file
6
src/client/component/dedicated.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace dedicated
|
||||
{
|
||||
void initialize();
|
||||
}
|
64
src/client/component/dedicated_info.cpp
Normal file
64
src/client/component/dedicated_info.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#include <std_include.hpp>
|
||||
#include "console.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include <utils\string.hpp>
|
||||
|
||||
namespace dedicated_info
|
||||
{
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler::loop([]()
|
||||
{
|
||||
auto* sv_running = game::Dvar_FindVar("sv_running");
|
||||
if (!sv_running || !sv_running->current.enabled)
|
||||
{
|
||||
console::set_title("s1-mod Dedicated Server");
|
||||
return;
|
||||
}
|
||||
|
||||
auto* const sv_hostname = game::Dvar_FindVar("sv_hostname");
|
||||
auto* const sv_maxclients = game::Dvar_FindVar("sv_maxclients");
|
||||
auto* const mapname = game::Dvar_FindVar("mapname");
|
||||
|
||||
auto bot_count = 0;
|
||||
auto client_count = 0;
|
||||
|
||||
for (auto i = 0; i < sv_maxclients->current.integer; i++)
|
||||
{
|
||||
auto* client = &game::mp::svs_clients[i];
|
||||
auto* self = &game::mp::g_entities[i];
|
||||
|
||||
if (client->header.state >= 1 && self && self->client)
|
||||
{
|
||||
client_count++;
|
||||
if (game::SV_BotIsBot(i))
|
||||
{
|
||||
++bot_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string cleaned_hostname = sv_hostname->current.string;
|
||||
|
||||
utils::string::strip(sv_hostname->current.string, cleaned_hostname.data(),
|
||||
cleaned_hostname.size() + 1);
|
||||
|
||||
console::set_title(utils::string::va("%s on %s [%d/%d] (%d)", cleaned_hostname.data(),
|
||||
mapname->current.string, client_count,
|
||||
sv_maxclients->current.integer, bot_count));
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(dedicated_info::component)
|
448
src/client/component/demonware.cpp
Normal file
448
src/client/component/demonware.cpp
Normal file
@ -0,0 +1,448 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/thread.hpp>
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/demonware/servers/lobby_server.hpp"
|
||||
#include "game/demonware/servers/auth3_server.hpp"
|
||||
#include "game/demonware/servers/stun_server.hpp"
|
||||
#include "game/demonware/servers/umbrella_server.hpp"
|
||||
#include "game/demonware/server_registry.hpp"
|
||||
|
||||
#define TCP_BLOCKING true
|
||||
#define UDP_BLOCKING false
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
namespace
|
||||
{
|
||||
volatile bool exit_server;
|
||||
std::thread server_thread;
|
||||
utils::concurrency::container<std::unordered_map<SOCKET, bool>> blocking_sockets;
|
||||
utils::concurrency::container<std::unordered_map<SOCKET, tcp_server*>> socket_map;
|
||||
server_registry<tcp_server> tcp_servers;
|
||||
server_registry<udp_server> udp_servers;
|
||||
|
||||
tcp_server* find_server(const SOCKET socket)
|
||||
{
|
||||
return socket_map.access<tcp_server*>([&](const std::unordered_map<SOCKET, tcp_server*>& map) -> tcp_server*
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry == map.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return entry->second;
|
||||
});
|
||||
}
|
||||
|
||||
bool socket_link(const SOCKET socket, const uint32_t address)
|
||||
{
|
||||
auto* server = tcp_servers.find(address);
|
||||
if (!server)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
socket_map.access([&](std::unordered_map<SOCKET, tcp_server*>& map)
|
||||
{
|
||||
map[socket] = server;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void socket_unlink(const SOCKET socket)
|
||||
{
|
||||
socket_map.access([&](std::unordered_map<SOCKET, tcp_server*>& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry != map.end())
|
||||
{
|
||||
map.erase(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool is_socket_blocking(const SOCKET socket, const bool def)
|
||||
{
|
||||
return blocking_sockets.access<bool>([&](std::unordered_map<SOCKET, bool>& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry == map.end())
|
||||
{
|
||||
return def;
|
||||
}
|
||||
|
||||
return entry->second;
|
||||
});
|
||||
}
|
||||
|
||||
void remove_blocking_socket(const SOCKET socket)
|
||||
{
|
||||
blocking_sockets.access([&](std::unordered_map<SOCKET, bool>& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry != map.end())
|
||||
{
|
||||
map.erase(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void add_blocking_socket(const SOCKET socket, const bool block)
|
||||
{
|
||||
blocking_sockets.access([&](std::unordered_map<SOCKET, bool>& map)
|
||||
{
|
||||
map[socket] = block;
|
||||
});
|
||||
}
|
||||
|
||||
void server_main()
|
||||
{
|
||||
exit_server = false;
|
||||
|
||||
while (!exit_server)
|
||||
{
|
||||
tcp_servers.frame();
|
||||
udp_servers.frame();
|
||||
std::this_thread::sleep_for(50ms);
|
||||
}
|
||||
}
|
||||
|
||||
namespace io
|
||||
{
|
||||
hostent* WINAPI gethostbyname_stub(const char* name)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[ network ]: [gethostbyname]: \"%s\"\n", name);
|
||||
#endif
|
||||
|
||||
base_server* server = tcp_servers.find(name);
|
||||
if (!server)
|
||||
{
|
||||
server = udp_servers.find(name);
|
||||
}
|
||||
|
||||
if (!server)
|
||||
{
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4996)
|
||||
return gethostbyname(name);
|
||||
#pragma warning(pop)
|
||||
}
|
||||
|
||||
static thread_local in_addr address{};
|
||||
address.s_addr = server->get_address();
|
||||
|
||||
static thread_local in_addr* addr_list[2]{};
|
||||
addr_list[0] = &address;
|
||||
addr_list[1] = nullptr;
|
||||
|
||||
static thread_local hostent host{};
|
||||
host.h_name = const_cast<char*>(name);
|
||||
host.h_aliases = nullptr;
|
||||
host.h_addrtype = AF_INET;
|
||||
host.h_length = sizeof(in_addr);
|
||||
host.h_addr_list = reinterpret_cast<char**>(addr_list);
|
||||
|
||||
return &host;
|
||||
}
|
||||
|
||||
int WINAPI connect_stub(const SOCKET s, const sockaddr* addr, const int len)
|
||||
{
|
||||
if (len == sizeof(sockaddr_in))
|
||||
{
|
||||
const auto* in_addr = reinterpret_cast<const sockaddr_in*>(addr);
|
||||
if (socket_link(s, in_addr->sin_addr.s_addr)) return 0;
|
||||
}
|
||||
|
||||
return connect(s, addr, len);
|
||||
}
|
||||
|
||||
int WINAPI closesocket_stub(const SOCKET s)
|
||||
{
|
||||
remove_blocking_socket(s);
|
||||
socket_unlink(s);
|
||||
|
||||
return closesocket(s);
|
||||
}
|
||||
|
||||
int WINAPI send_stub(const SOCKET s, const char* buf, const int len, const int flags)
|
||||
{
|
||||
auto* server = find_server(s);
|
||||
|
||||
if (server)
|
||||
{
|
||||
server->handle_input(buf, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
return send(s, buf, len, flags);
|
||||
}
|
||||
|
||||
int WINAPI recv_stub(const SOCKET s, char* buf, const int len, const int flags)
|
||||
{
|
||||
auto* server = find_server(s);
|
||||
|
||||
if (server)
|
||||
{
|
||||
if (server->pending_data())
|
||||
{
|
||||
return static_cast<int>(server->handle_output(buf, len));
|
||||
}
|
||||
else
|
||||
{
|
||||
WSASetLastError(WSAEWOULDBLOCK);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return recv(s, buf, len, flags);
|
||||
}
|
||||
|
||||
int WINAPI sendto_stub(const SOCKET s, const char* buf, const int len, const int flags, const sockaddr* to,
|
||||
const int tolen)
|
||||
{
|
||||
const auto* in_addr = reinterpret_cast<const sockaddr_in*>(to);
|
||||
auto* server = udp_servers.find(in_addr->sin_addr.s_addr);
|
||||
|
||||
if (server)
|
||||
{
|
||||
server->handle_input(buf, len, {s, to, tolen});
|
||||
return len;
|
||||
}
|
||||
|
||||
return sendto(s, buf, len, flags, to, tolen);
|
||||
}
|
||||
|
||||
int WINAPI recvfrom_stub(const SOCKET s, char* buf, const int len, const int flags, sockaddr* from,
|
||||
int* fromlen)
|
||||
{
|
||||
// Not supported yet
|
||||
if (is_socket_blocking(s, UDP_BLOCKING))
|
||||
{
|
||||
return recvfrom(s, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
size_t result = 0;
|
||||
udp_servers.for_each([&](udp_server& server)
|
||||
{
|
||||
if (server.pending_data(s))
|
||||
{
|
||||
result = server.handle_output(
|
||||
s, buf, static_cast<size_t>(len), from, fromlen);
|
||||
}
|
||||
});
|
||||
|
||||
if (result)
|
||||
{
|
||||
return static_cast<int>(result);
|
||||
}
|
||||
|
||||
return recvfrom(s, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
int WINAPI select_stub(const int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
|
||||
timeval* timeout)
|
||||
{
|
||||
if (exit_server)
|
||||
{
|
||||
return select(nfds, readfds, writefds, exceptfds, timeout);
|
||||
}
|
||||
|
||||
auto result = 0;
|
||||
std::vector<SOCKET> read_sockets;
|
||||
std::vector<SOCKET> write_sockets;
|
||||
|
||||
socket_map.access([&](std::unordered_map<SOCKET, tcp_server*>& sockets)
|
||||
{
|
||||
for (auto& s : sockets)
|
||||
{
|
||||
if (readfds)
|
||||
{
|
||||
if (FD_ISSET(s.first, readfds))
|
||||
{
|
||||
if (s.second->pending_data())
|
||||
{
|
||||
read_sockets.push_back(s.first);
|
||||
FD_CLR(s.first, readfds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (writefds)
|
||||
{
|
||||
if (FD_ISSET(s.first, writefds))
|
||||
{
|
||||
write_sockets.push_back(s.first);
|
||||
FD_CLR(s.first, writefds);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptfds)
|
||||
{
|
||||
if (FD_ISSET(s.first, exceptfds))
|
||||
{
|
||||
FD_CLR(s.first, exceptfds);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ((!readfds || readfds->fd_count == 0) && (!writefds || writefds->fd_count == 0))
|
||||
{
|
||||
timeout->tv_sec = 0;
|
||||
timeout->tv_usec = 0;
|
||||
}
|
||||
|
||||
result = select(nfds, readfds, writefds, exceptfds, timeout);
|
||||
if (result < 0) result = 0;
|
||||
|
||||
for (const auto& socket : read_sockets)
|
||||
{
|
||||
if (readfds)
|
||||
{
|
||||
FD_SET(socket, readfds);
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& socket : write_sockets)
|
||||
{
|
||||
if (writefds)
|
||||
{
|
||||
FD_SET(socket, writefds);
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int WINAPI ioctlsocket_stub(const SOCKET s, const long cmd, u_long* argp)
|
||||
{
|
||||
if (static_cast<unsigned long>(cmd) == (FIONBIO))
|
||||
{
|
||||
add_blocking_socket(s, *argp == 0);
|
||||
}
|
||||
|
||||
return ioctlsocket(s, cmd, argp);
|
||||
}
|
||||
|
||||
BOOL internet_get_connected_state_stub(LPDWORD, DWORD)
|
||||
{
|
||||
// Allow offline play
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void bd_logger_stub(const char* const function, const char* const msg, ...)
|
||||
{
|
||||
static const auto* bd_logger_enabled = game::Dvar_RegisterBool("bd_logger_enabled", false, game::DVAR_FLAG_NONE, "Enable bdLogger");
|
||||
if (!bd_logger_enabled->current.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[2048];
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
|
||||
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
|
||||
printf("%s: %s\n", function, buffer);
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void startup_dw()
|
||||
{
|
||||
udp_servers.create<stun_server>("s1-stun.us.demonware.net");
|
||||
udp_servers.create<stun_server>("s1-stun.eu.demonware.net");
|
||||
udp_servers.create<stun_server>("s1-stun.jp.demonware.net");
|
||||
udp_servers.create<stun_server>("s1-stun.au.demonware.net");
|
||||
|
||||
udp_servers.create<stun_server>("stun.us.demonware.net");
|
||||
udp_servers.create<stun_server>("stun.eu.demonware.net");
|
||||
udp_servers.create<stun_server>("stun.jp.demonware.net");
|
||||
udp_servers.create<stun_server>("stun.au.demonware.net");
|
||||
|
||||
tcp_servers.create<auth3_server>("aw-pc-auth3.prod.demonware.net");
|
||||
tcp_servers.create<lobby_server>("aw-pc-lobby.prod.demonware.net");
|
||||
tcp_servers.create<umbrella_server>("prod.umbrella.demonware.net");
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_load() override
|
||||
{
|
||||
startup_dw();
|
||||
|
||||
server_thread = utils::thread::create_named_thread("Demonware", server_main);
|
||||
}
|
||||
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
if (library == "WS2_32.dll")
|
||||
{
|
||||
if (function == "#3") return io::closesocket_stub;
|
||||
if (function == "#4") return io::connect_stub;
|
||||
if (function == "#10") return io::ioctlsocket_stub;
|
||||
if (function == "#16") return io::recv_stub;
|
||||
if (function == "#17") return io::recvfrom_stub;
|
||||
if (function == "#18") return io::select_stub;
|
||||
if (function == "#19") return io::send_stub;
|
||||
if (function == "#20") return io::sendto_stub;
|
||||
if (function == "#52") return io::gethostbyname_stub;
|
||||
}
|
||||
|
||||
if (function == "InternetGetConnectedState")
|
||||
{
|
||||
return io::internet_get_connected_state_stub;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
utils::hook::jump(SELECT_VALUE(0x140575880, 0x1406C0080), bd_logger_stub);
|
||||
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
utils::hook::set<uint8_t>(0x1405632E0, 0xC3); // bdAuthSteam
|
||||
utils::hook::set<uint8_t>(0x1402DF2C0, 0xC3); // dwNet
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::set<uint8_t>(0x140698BB2, 0x0); // CURLOPT_SSL_VERIFYPEER
|
||||
utils::hook::set<uint8_t>(0x140698B69, 0xAF); // CURLOPT_SSL_VERIFYHOST
|
||||
utils::hook::set<uint8_t>(0x14088D0E8, 0x0); // HTTPS -> HTTP
|
||||
|
||||
// HTTPS -> HTTP
|
||||
utils::hook::inject(0x14003852E, "http://prod.umbrella.demonware.net/v1.0/");
|
||||
utils::hook::inject(0x14003884F, "http://prod.umbrella.demonware.net/v1.0/");
|
||||
utils::hook::inject(0x140038A07, "http://prod.umbrella.demonware.net/v1.0/");
|
||||
|
||||
utils::hook::set<uint8_t>(0x140437CC0, 0xC3); // SV_SendMatchData
|
||||
utils::hook::set<uint8_t>(0x140560D70, 0xC3); // Live_CheckForFullDisconnect
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
exit_server = true;
|
||||
if (server_thread.joinable())
|
||||
{
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(demonware::component)
|
167
src/client/component/discord.cpp
Normal file
167
src/client/component/discord.cpp
Normal file
@ -0,0 +1,167 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "console.hpp"
|
||||
#include "network.hpp"
|
||||
#include "party.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
#include <discord_rpc.h>
|
||||
|
||||
namespace discord
|
||||
{
|
||||
namespace
|
||||
{
|
||||
DiscordRichPresence discord_presence;
|
||||
|
||||
void join_game(const char* join_secret)
|
||||
{
|
||||
game::Cbuf_AddText(0, utils::string::va("connect %s\n", join_secret));
|
||||
}
|
||||
|
||||
void join_request(const DiscordUser* request)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
console::info("Discord: Join request from %s (%s)\n", request->username, request->userId);
|
||||
#endif
|
||||
Discord_Respond(request->userId, DISCORD_REPLY_IGNORE);
|
||||
}
|
||||
|
||||
void update_discord()
|
||||
{
|
||||
Discord_RunCallbacks();
|
||||
|
||||
auto* dvar = game::Dvar_FindVar("virtualLobbyActive");
|
||||
if (!game::CL_IsCgameInitialized() || (dvar && dvar->current.enabled == 1))
|
||||
{
|
||||
discord_presence.details = game::environment::is_sp() ? "Singleplayer" : "Multiplayer";
|
||||
|
||||
dvar = game::Dvar_FindVar("virtualLobbyInFiringRange");
|
||||
if (dvar && dvar->current.enabled == 1)
|
||||
{
|
||||
discord_presence.state = "Firing Range";
|
||||
}
|
||||
else
|
||||
{
|
||||
discord_presence.state = "Main Menu";
|
||||
}
|
||||
|
||||
discord_presence.partySize = 0;
|
||||
discord_presence.partyMax = 0;
|
||||
discord_presence.startTimestamp = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (game::environment::is_sp()) return;
|
||||
|
||||
const auto* gametype = game::UI_GetGameTypeDisplayName(game::Dvar_FindVar("ui_gametype")->current.string);
|
||||
const auto* map = game::UI_GetMapDisplayName(game::Dvar_FindVar("ui_mapname")->current.string);
|
||||
|
||||
discord_presence.details = utils::string::va("%s on %s", gametype, map);
|
||||
|
||||
// get server host name
|
||||
auto* const host_name = reinterpret_cast<char*>(0x141646CC4);
|
||||
utils::string::strip(host_name, host_name, std::strlen(host_name) + 1);
|
||||
|
||||
// get number of clients in game
|
||||
auto clients = reinterpret_cast<int*>(0x1414CC290);
|
||||
int clientsNum = *clients;
|
||||
discord_presence.partySize = clientsNum;
|
||||
|
||||
if (game::Dvar_FindVar("name") && !strcmp(host_name, game::Dvar_FindVar("name")->current.string)) // host_name == name, most likely private match
|
||||
{
|
||||
discord_presence.state = "Private Match";
|
||||
discord_presence.partyMax = game::Dvar_FindVar("sv_maxclients")->current.integer;
|
||||
}
|
||||
else
|
||||
{
|
||||
discord_presence.state = host_name;
|
||||
discord_presence.partyMax = party::server_client_count();
|
||||
|
||||
std::hash<game::netadr_s> hash_fn;
|
||||
static const auto nonce = utils::cryptography::random::get_integer();
|
||||
|
||||
const auto& address = party::get_target();
|
||||
discord_presence.partyId = utils::string::va("%zu", hash_fn(address) ^ nonce);
|
||||
discord_presence.joinSecret = network::net_adr_to_string(address);
|
||||
}
|
||||
|
||||
if (!discord_presence.startTimestamp)
|
||||
{
|
||||
discord_presence.startTimestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
}
|
||||
|
||||
Discord_UpdatePresence(&discord_presence);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_load() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DiscordEventHandlers handlers;
|
||||
ZeroMemory(&handlers, sizeof(handlers));
|
||||
handlers.ready = ready;
|
||||
handlers.errored = errored;
|
||||
handlers.disconnected = errored;
|
||||
handlers.joinGame = join_game;
|
||||
handlers.spectateGame = nullptr;
|
||||
handlers.joinRequest = join_request;
|
||||
|
||||
Discord_Initialize("1117777088301240350", &handlers, 1, nullptr);
|
||||
|
||||
scheduler::once([]
|
||||
{
|
||||
scheduler::once(update_discord, scheduler::pipeline::main);
|
||||
scheduler::loop(update_discord, scheduler::pipeline::main, 15s);
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
if (!initialized_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Discord_Shutdown();
|
||||
}
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
|
||||
static void ready(const DiscordUser* /*request*/)
|
||||
{
|
||||
ZeroMemory(&discord_presence, sizeof(discord_presence));
|
||||
|
||||
discord_presence.instance = 1;
|
||||
|
||||
console::info("Discord: Ready\n");
|
||||
|
||||
Discord_UpdatePresence(&discord_presence);
|
||||
}
|
||||
|
||||
static void errored(const int error_code, const char* message)
|
||||
{
|
||||
console::error("Discord: Error (%i): %s\n", error_code, message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#ifndef DEV_BUILD
|
||||
REGISTER_COMPONENT(discord::component)
|
||||
#endif
|
189
src/client/component/dvar_cheats.cpp
Normal file
189
src/client/component/dvar_cheats.cpp
Normal file
@ -0,0 +1,189 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "console.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace dvar_cheats
|
||||
{
|
||||
void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::dvar_value* value)
|
||||
{
|
||||
if (dvar && dvar->name == "sv_cheats"s)
|
||||
{
|
||||
// if dedi, do not allow internal to change value so servers can allow cheats if they want to
|
||||
if (game::environment::is_dedi() && source == game::DvarSetSource::DVAR_SOURCE_INTERNAL)
|
||||
{
|
||||
value->enabled = dvar->current.enabled;
|
||||
}
|
||||
|
||||
// if sv_cheats was enabled and it changes to disabled, we need to reset all cheat dvars
|
||||
else if (dvar->current.enabled && !value->enabled)
|
||||
{
|
||||
for (auto i = 0; i < *game::dvarCount; ++i)
|
||||
{
|
||||
const auto var = game::sortedDvars[i];
|
||||
if (var && (var->flags & game::DvarFlags::DVAR_FLAG_CHEAT))
|
||||
{
|
||||
game::Dvar_Reset(var, game::DvarSetSource::DVAR_SOURCE_INTERNAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool dvar_flag_checks(const game::dvar_t* dvar, const game::DvarSetSource source)
|
||||
{
|
||||
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_WRITE))
|
||||
{
|
||||
console::error("%s is write protected\n", dvar->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_READ))
|
||||
{
|
||||
console::error("%s is read only\n", dvar->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// only check cheat/replicated values when the source is external
|
||||
if (source == game::DvarSetSource::DVAR_SOURCE_EXTERNAL)
|
||||
{
|
||||
const auto cl_ingame = game::Dvar_FindVar("cl_ingame");
|
||||
const auto sv_running = game::Dvar_FindVar("sv_running");
|
||||
|
||||
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_REPLICATED) && (cl_ingame && cl_ingame->current.enabled) && (
|
||||
sv_running && !sv_running->current.enabled))
|
||||
{
|
||||
console::error("%s can only be changed by the server\n", dvar->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_CHEAT) && (!dvars::sv_cheats->current.enabled))
|
||||
{
|
||||
console::error("%s is cheat protected\n", dvar->name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// pass all the flag checks, allow dvar to be changed
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto dvar_flag_checks_stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
const auto can_set_value = a.newLabel();
|
||||
const auto zero_source = a.newLabel();
|
||||
|
||||
a.pushad64();
|
||||
a.mov(r8, rdi);
|
||||
a.mov(edx, esi);
|
||||
a.mov(rcx, rbx);
|
||||
a.call_aligned(apply_sv_cheats); //check if we are setting sv_cheats
|
||||
a.popad64();
|
||||
a.cmp(esi, 0);
|
||||
a.jz(zero_source); //if the SetSource is 0 (INTERNAL) ignore flag checks
|
||||
|
||||
a.pushad64();
|
||||
a.mov(edx, esi); //source
|
||||
a.mov(rcx, rbx); //dvar
|
||||
a.call_aligned(dvar_flag_checks); //protect read/write/cheat/replicated dvars
|
||||
a.cmp(al, 1);
|
||||
a.jz(can_set_value);
|
||||
|
||||
// if we get here, we are non-zero source and CANNOT set values
|
||||
a.popad64(); // if I do this before the jz it won't work. for some reason the popad64 is affecting the ZR flag
|
||||
a.jmp(0x1404C3D45);
|
||||
|
||||
// if we get here, we are non-zero source and CAN set values
|
||||
a.bind(can_set_value);
|
||||
a.popad64(); // if I do this before the jz it won't work. for some reason the popad64 is affecting the ZR flag
|
||||
a.jmp(0x1404C3AAE);
|
||||
|
||||
// if we get here, we are zero source and ignore flags
|
||||
a.bind(zero_source);
|
||||
a.jmp(0x1404C3AF4);
|
||||
});
|
||||
|
||||
void cg_set_client_dvar_from_server(const int local_client_num, void* cg, const char* dvar_id, const char* value)
|
||||
{
|
||||
const auto* dvar = game::Dvar_FindVar(dvar_id);
|
||||
if (dvar)
|
||||
{
|
||||
// If we send as string, it can't be set with source SERVERCMD because the game only allows that source on real server cmd dvars.
|
||||
// Just use external instead as if it was being set by the console
|
||||
game::Dvar_SetFromStringByNameFromSource(dvar_id, value, game::DvarSetSource::DVAR_SOURCE_EXTERNAL);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a dvar name, assume it is an id and the game will handle normally
|
||||
game::CG_SetClientDvarFromServer(local_client_num, cg, dvar_id, value);
|
||||
}
|
||||
}
|
||||
|
||||
void set_client_dvar_by_string(const int entity_num, const char* value)
|
||||
{
|
||||
const auto* dvar = game::Scr_GetString(0); // grab the original dvar again since it's never stored on stack
|
||||
const auto* command = utils::string::va("q %s \"%s\"", dvar, value);
|
||||
|
||||
game::SV_GameSendServerCommand(entity_num, game::SV_CMD_RELIABLE, command);
|
||||
}
|
||||
|
||||
const auto player_cmd_set_client_dvar = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
const auto set_by_string = a.newLabel();
|
||||
|
||||
a.pushad64();
|
||||
|
||||
// check if we didn't find a network dvar index
|
||||
a.mov(ecx, dword_ptr(rsp, 0x8C8));
|
||||
a.cmp(ecx, 0);
|
||||
a.je(set_by_string);
|
||||
|
||||
// we found an index, handle normally
|
||||
a.popad64();
|
||||
a.mov(r8d, ptr(rsp, 0x848));
|
||||
a.lea(r9, ptr(rsp, 0x30));
|
||||
a.jmp(0x1402E2E57);
|
||||
|
||||
// no index, let's send the dvar as a string
|
||||
a.bind(set_by_string);
|
||||
a.movzx(ecx, word_ptr(rsp, 0x8C0)); //entity_num
|
||||
a.lea(rdx, ptr(rsp, 0xB0)); //value
|
||||
a.call_aligned(set_client_dvar_by_string);
|
||||
a.popad64();
|
||||
a.jmp(0x1402E2E7D);
|
||||
});
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp()) return;
|
||||
|
||||
utils::hook::nop(0x1404C3A93, 4); // let our stub handle zero-source sets
|
||||
utils::hook::jump(0x1404C3A9A, dvar_flag_checks_stub, true); // check extra dvar flags when setting values
|
||||
|
||||
utils::hook::nop(0x1402E2E03, 5); // remove error in PlayerCmd_SetClientDvar if setting a non-network dvar
|
||||
// don't check flags on the dvars, send any existing dvar instead
|
||||
utils::hook::jump(0x1402E2E4A, player_cmd_set_client_dvar, true); // send non-network dvars as string
|
||||
utils::hook::call(0x1401BB782, cg_set_client_dvar_from_server);
|
||||
// check for dvars being sent as string before parsing ids
|
||||
|
||||
#ifdef _DEBUG
|
||||
constexpr auto value = true;
|
||||
#else
|
||||
constexpr auto value = false;
|
||||
#endif
|
||||
|
||||
dvars::sv_cheats = game::Dvar_RegisterBool("sv_cheats", value, game::DVAR_FLAG_REPLICATED, "Allow cheat commands and dvars on this server");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(dvar_cheats::component)
|
387
src/client/component/dvars.cpp
Normal file
387
src/client/component/dvars.cpp
Normal file
@ -0,0 +1,387 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "dvars.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace dvars
|
||||
{
|
||||
struct dvar_base
|
||||
{
|
||||
unsigned int flags{};
|
||||
};
|
||||
|
||||
struct dvar_bool : dvar_base
|
||||
{
|
||||
bool value{};
|
||||
};
|
||||
|
||||
struct dvar_float : dvar_base
|
||||
{
|
||||
float value{};
|
||||
float min{};
|
||||
float max{};
|
||||
};
|
||||
|
||||
struct dvar_vector2 : dvar_base
|
||||
{
|
||||
float x{};
|
||||
float y{};
|
||||
float min{};
|
||||
float max{};
|
||||
};
|
||||
|
||||
struct dvar_vector3 : dvar_base
|
||||
{
|
||||
|
||||
float x{};
|
||||
float y{};
|
||||
float z{};
|
||||
float min{};
|
||||
float max{};
|
||||
};
|
||||
|
||||
struct dvar_int : dvar_base
|
||||
{
|
||||
int value{};
|
||||
int min{};
|
||||
int max{};
|
||||
};
|
||||
|
||||
struct dvar_string : dvar_base
|
||||
{
|
||||
std::string value{};
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
T* find_dvar(std::unordered_map<std::string, T>& map, const std::string& name)
|
||||
{
|
||||
auto i = map.find(name);
|
||||
if (i != map.end())
|
||||
{
|
||||
return &i->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool find_dvar(std::unordered_set<std::string>& set, const std::string& name)
|
||||
{
|
||||
return set.find(name) != set.end();
|
||||
}
|
||||
}
|
||||
|
||||
namespace disable
|
||||
{
|
||||
static std::unordered_set<std::string> set_bool_disables;
|
||||
static std::unordered_set<std::string> set_float_disables;
|
||||
static std::unordered_set<std::string> set_int_disables;
|
||||
static std::unordered_set<std::string> set_string_disables;
|
||||
|
||||
void set_bool(const std::string& name)
|
||||
{
|
||||
set_bool_disables.emplace(name);
|
||||
}
|
||||
|
||||
void set_float(const std::string& name)
|
||||
{
|
||||
set_float_disables.emplace(name);
|
||||
}
|
||||
|
||||
void set_int(const std::string& name)
|
||||
{
|
||||
set_int_disables.emplace(name);
|
||||
}
|
||||
|
||||
void set_string(const std::string& name)
|
||||
{
|
||||
set_string_disables.emplace(name);
|
||||
}
|
||||
}
|
||||
|
||||
namespace override
|
||||
{
|
||||
static std::unordered_map<std::string, dvar_bool> register_bool_overrides;
|
||||
static std::unordered_map<std::string, dvar_float> register_float_overrides;
|
||||
static std::unordered_map<std::string, dvar_int> register_int_overrides;
|
||||
static std::unordered_map<std::string, dvar_string> register_string_overrides;
|
||||
static std::unordered_map<std::string, dvar_vector2> register_vector2_overrides;
|
||||
static std::unordered_map<std::string, dvar_vector3> register_vector3_overrides;
|
||||
|
||||
static std::unordered_map<std::string, bool> set_bool_overrides;
|
||||
static std::unordered_map<std::string, float> set_float_overrides;
|
||||
static std::unordered_map<std::string, int> set_int_overrides;
|
||||
static std::unordered_map<std::string, std::string> set_string_overrides;
|
||||
|
||||
void register_bool(const std::string& name, const bool value, const unsigned int flags)
|
||||
{
|
||||
dvar_bool values;
|
||||
values.value = value;
|
||||
values.flags = flags;
|
||||
register_bool_overrides[name] = std::move(values);
|
||||
}
|
||||
|
||||
void register_float(const std::string& name, const float value, const float min, const float max, const unsigned int flags)
|
||||
{
|
||||
dvar_float values;
|
||||
values.value = value;
|
||||
values.min = min;
|
||||
values.max = max;
|
||||
values.flags = flags;
|
||||
register_float_overrides[name] = std::move(values);
|
||||
}
|
||||
|
||||
void register_int(const std::string& name, const int value, const int min, const int max, const unsigned int flags)
|
||||
{
|
||||
dvar_int values;
|
||||
values.value = value;
|
||||
values.min = min;
|
||||
values.max = max;
|
||||
values.flags = flags;
|
||||
register_int_overrides[name] = std::move(values);
|
||||
}
|
||||
|
||||
void register_string(const std::string& name, const std::string& value, const unsigned int flags)
|
||||
{
|
||||
dvar_string values;
|
||||
values.value = value;
|
||||
values.flags = flags;
|
||||
register_string_overrides[name] = std::move(values);
|
||||
}
|
||||
|
||||
void register_vector2(const std::string& name, float x, float y, float min, float max, const unsigned int flags)
|
||||
{
|
||||
dvar_vector2 values;
|
||||
values.x = x;
|
||||
values.y = y;
|
||||
values.min = min;
|
||||
values.max = max;
|
||||
values.flags = flags;
|
||||
register_vector2_overrides[name] = std::move(values);
|
||||
}
|
||||
|
||||
void register_vector3(const std::string& name, float x, float y, float z, float min, float max, const unsigned int flags)
|
||||
{
|
||||
dvar_vector3 values;
|
||||
values.x = x;
|
||||
values.y = y;
|
||||
values.z = z;
|
||||
values.min = min;
|
||||
values.max = max;
|
||||
values.flags = flags;
|
||||
register_vector3_overrides[name] = std::move(values);
|
||||
}
|
||||
|
||||
void set_bool(const std::string& name, const bool value)
|
||||
{
|
||||
set_bool_overrides[name] = value;
|
||||
}
|
||||
|
||||
void set_float(const std::string& name, const float value)
|
||||
{
|
||||
set_float_overrides[name] = value;
|
||||
}
|
||||
|
||||
void set_int(const std::string& name, const int value)
|
||||
{
|
||||
set_int_overrides[name] = value;
|
||||
}
|
||||
|
||||
void set_string(const std::string& name, const std::string& value)
|
||||
{
|
||||
set_string_overrides[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::detour dvar_register_bool_hook;
|
||||
utils::hook::detour dvar_register_float_hook;
|
||||
utils::hook::detour dvar_register_int_hook;
|
||||
utils::hook::detour dvar_register_string_hook;
|
||||
utils::hook::detour dvar_register_vector2_hook;
|
||||
utils::hook::detour dvar_register_vector3_hook;
|
||||
|
||||
utils::hook::detour dvar_set_bool_hook;
|
||||
utils::hook::detour dvar_set_float_hook;
|
||||
utils::hook::detour dvar_set_int_hook;
|
||||
utils::hook::detour dvar_set_string_hook;
|
||||
|
||||
game::dvar_t* dvar_register_bool_stub(const char* name, bool value, unsigned int flags, const char* description)
|
||||
{
|
||||
auto* var = find_dvar(override::register_bool_overrides, name);
|
||||
if (var)
|
||||
{
|
||||
value = var->value;
|
||||
flags = var->flags;
|
||||
}
|
||||
|
||||
return dvar_register_bool_hook.invoke<game::dvar_t*>(name, value, flags, description);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_register_float_stub(const char* name, float value, float min, float max, unsigned int flags,
|
||||
const char* description)
|
||||
{
|
||||
auto* var = find_dvar(override::register_float_overrides, name);
|
||||
if (var)
|
||||
{
|
||||
value = var->value;
|
||||
min = var->min;
|
||||
max = var->max;
|
||||
flags = var->flags;
|
||||
}
|
||||
|
||||
return dvar_register_float_hook.invoke<game::dvar_t*>(name, value, min, max, flags, description);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_register_int_stub(const char* name, int value, int min, int max, unsigned int flags,
|
||||
const char* description)
|
||||
{
|
||||
auto* var = find_dvar(override::register_int_overrides, name);
|
||||
if (var)
|
||||
{
|
||||
value = var->value;
|
||||
min = var->min;
|
||||
max = var->max;
|
||||
flags = var->flags;
|
||||
}
|
||||
|
||||
return dvar_register_int_hook.invoke<game::dvar_t*>(name, value, min, max, flags, description);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_register_string_stub(const char* name, const char* value, unsigned int flags, const char* description)
|
||||
{
|
||||
auto* var = find_dvar(override::register_string_overrides, name);
|
||||
if (var)
|
||||
{
|
||||
value = var->value.data();
|
||||
flags = var->flags;
|
||||
}
|
||||
|
||||
return dvar_register_string_hook.invoke<game::dvar_t*>(name, value, flags, description);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_register_vector2_stub(const char* name, float x, float y, float min, float max,
|
||||
unsigned int flags, const char* description)
|
||||
{
|
||||
auto* var = find_dvar(override::register_vector2_overrides, name);
|
||||
if (var)
|
||||
{
|
||||
x = var->x;
|
||||
y = var->y;
|
||||
min = var->min;
|
||||
max = var->max;
|
||||
flags = var->flags;
|
||||
}
|
||||
|
||||
return dvar_register_vector2_hook.invoke<game::dvar_t*>(name, x, y, min, max, flags, description);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_register_vector3_stub(const char* name, float x, float y, float z, float min,
|
||||
float max, unsigned int flags, const char* description)
|
||||
{
|
||||
auto* var = find_dvar(override::register_vector3_overrides, name);
|
||||
if (var)
|
||||
{
|
||||
x = var->x;
|
||||
y = var->y;
|
||||
z = var->z;
|
||||
min = var->min;
|
||||
max = var->max;
|
||||
flags = var->flags;
|
||||
}
|
||||
|
||||
return dvar_register_vector3_hook.invoke<game::dvar_t*>(name, x, y, z, min, max, flags, description);
|
||||
}
|
||||
|
||||
void dvar_set_bool_stub(game::dvar_t* dvar, bool boolean)
|
||||
{
|
||||
const auto disabled = find_dvar(disable::set_bool_disables, dvar->name);
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* var = find_dvar(override::set_bool_overrides, dvar->name);
|
||||
if (var)
|
||||
{
|
||||
boolean = *var;
|
||||
}
|
||||
|
||||
return dvar_set_bool_hook.invoke<void>(dvar, boolean);
|
||||
}
|
||||
|
||||
void dvar_set_float_stub(game::dvar_t* dvar, float fl)
|
||||
{
|
||||
const auto disabled = find_dvar(disable::set_float_disables, dvar->name);
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* var = find_dvar(override::set_float_overrides, dvar->name);
|
||||
if (var)
|
||||
{
|
||||
fl = *var;
|
||||
}
|
||||
|
||||
return dvar_set_float_hook.invoke<void>(dvar, fl);
|
||||
}
|
||||
|
||||
void dvar_set_int_stub(game::dvar_t* dvar, int integer)
|
||||
{
|
||||
const auto disabled = find_dvar(disable::set_int_disables, dvar->name);
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* var = find_dvar(override::set_int_overrides, dvar->name);
|
||||
if (var)
|
||||
{
|
||||
integer = *var;
|
||||
}
|
||||
|
||||
return dvar_set_int_hook.invoke<void>(dvar, integer);
|
||||
}
|
||||
|
||||
void dvar_set_string_stub(game::dvar_t* dvar, const char* string)
|
||||
{
|
||||
const auto disabled = find_dvar(disable::set_string_disables, dvar->name);
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* var = find_dvar(override::set_string_overrides, dvar->name);
|
||||
if (var)
|
||||
{
|
||||
string = var->data();
|
||||
}
|
||||
|
||||
return dvar_set_string_hook.invoke<void>(dvar, string);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
dvar_register_bool_hook.create(SELECT_VALUE(0x140371850, 0x1404C0BE0), &dvar_register_bool_stub);
|
||||
dvar_register_float_hook.create(SELECT_VALUE(0x140371C20, 0x1404C0FB0), &dvar_register_float_stub);
|
||||
dvar_register_int_hook.create(SELECT_VALUE(0x140371CF0, 0x1404C1080), &dvar_register_int_stub);
|
||||
dvar_register_string_hook.create(SELECT_VALUE(0x140372050, 0x1404C1450), &dvar_register_string_stub);
|
||||
dvar_register_vector2_hook.create(SELECT_VALUE(0x140372120, 0x1404C1520), &dvar_register_vector2_stub);
|
||||
dvar_register_vector3_hook.create(SELECT_VALUE(0x140372230, 0x1404C1600), &dvar_register_vector3_stub);
|
||||
|
||||
dvar_set_bool_hook.create(SELECT_VALUE(0x140372B70, 0x1404C1F30), &dvar_set_bool_stub);
|
||||
dvar_set_float_hook.create(SELECT_VALUE(0x140373420, 0x1404C2A10), &dvar_set_float_stub);
|
||||
dvar_set_int_hook.create(SELECT_VALUE(0x1403738D0, 0x1404C2F40), &dvar_set_int_stub);
|
||||
dvar_set_string_hook.create(SELECT_VALUE(0x140373DE0, 0x1404C3610), &dvar_set_string_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(dvars::component)
|
27
src/client/component/dvars.hpp
Normal file
27
src/client/component/dvars.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
namespace dvars
|
||||
{
|
||||
namespace disable
|
||||
{
|
||||
void set_bool(const std::string& name);
|
||||
void set_float(const std::string& name);
|
||||
void set_int(const std::string& name);
|
||||
void set_string(const std::string& name);
|
||||
}
|
||||
|
||||
namespace override
|
||||
{
|
||||
void register_bool(const std::string& name, bool value, unsigned int flags);
|
||||
void register_float(const std::string& name, float value, float min, float max, unsigned int flags);
|
||||
void register_int(const std::string& name, int value, int min, int max, unsigned int flags);
|
||||
void register_string(const std::string& name, const std::string& value, unsigned int flags);
|
||||
void register_vector2(const std::string& name, float x, float y, float min, float max, unsigned int flags);
|
||||
void register_vector3(const std::string& name, float x, float y, float z, float min, float max, unsigned int flags);
|
||||
|
||||
void set_bool(const std::string& name, bool boolean);
|
||||
void set_float(const std::string& name, float fl);
|
||||
void set_int(const std::string& name, int integer);
|
||||
void set_string(const std::string& name, const std::string& string);
|
||||
}
|
||||
}
|
178
src/client/component/fastfiles.cpp
Normal file
178
src/client/component/fastfiles.cpp
Normal file
@ -0,0 +1,178 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "fastfiles.hpp"
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
namespace fastfiles
|
||||
{
|
||||
static utils::concurrency::container<std::string> current_fastfile;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour db_try_load_x_file_internal_hook;
|
||||
utils::hook::detour db_find_x_asset_header_hook;
|
||||
|
||||
void db_try_load_x_file_internal(const char* zone_name, const int flags)
|
||||
{
|
||||
console::info("Loading fastfile %s\n", zone_name);
|
||||
current_fastfile.access([&](std::string& fastfile)
|
||||
{
|
||||
fastfile = zone_name;
|
||||
});
|
||||
return db_try_load_x_file_internal_hook.invoke<void>(zone_name, flags);
|
||||
}
|
||||
|
||||
void dump_gsc_script(const std::string& name, game::XAssetHeader header)
|
||||
{
|
||||
if (!dvars::g_dump_scripts->current.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string buffer;
|
||||
buffer.append(header.scriptfile->name, std::strlen(header.scriptfile->name) + 1);
|
||||
buffer.append(reinterpret_cast<char*>(&header.scriptfile->compressedLen), sizeof(int));
|
||||
buffer.append(reinterpret_cast<char*>(&header.scriptfile->len), sizeof(int));
|
||||
buffer.append(reinterpret_cast<char*>(&header.scriptfile->bytecodeLen), sizeof(int));
|
||||
buffer.append(header.scriptfile->buffer, header.scriptfile->compressedLen);
|
||||
buffer.append(reinterpret_cast<char*>(header.scriptfile->bytecode), header.scriptfile->bytecodeLen);
|
||||
|
||||
const auto out_name = std::format("gsc_dump/{}.gscbin", name);
|
||||
utils::io::write_file(out_name, buffer);
|
||||
|
||||
console::info("Dumped %s\n", out_name.data());
|
||||
}
|
||||
|
||||
game::XAssetHeader db_find_x_asset_header_stub(game::XAssetType type, const char* name, int allow_create_default)
|
||||
{
|
||||
const auto start = game::Sys_Milliseconds();
|
||||
const auto result = db_find_x_asset_header_hook.invoke<game::XAssetHeader>(type, name, allow_create_default);
|
||||
const auto diff = game::Sys_Milliseconds() - start;
|
||||
|
||||
if (type == game::ASSET_TYPE_SCRIPTFILE)
|
||||
{
|
||||
dump_gsc_script(name, result);
|
||||
}
|
||||
|
||||
if (diff > 100)
|
||||
{
|
||||
console::print(
|
||||
result.data == nullptr ? console::con_type_error : console::con_type_warning, "Waited %i msec for asset '%s' of type '%s'.\n",
|
||||
diff,
|
||||
name,
|
||||
game::g_assetNames[type]
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_current_fastfile()
|
||||
{
|
||||
auto fastfile_copy = current_fastfile.access<std::string>([&](std::string& fastfile)
|
||||
{
|
||||
return fastfile;
|
||||
});
|
||||
|
||||
return fastfile_copy;
|
||||
}
|
||||
|
||||
constexpr int get_asset_type_size(const game::XAssetType type)
|
||||
{
|
||||
constexpr int asset_type_sizes[] =
|
||||
{
|
||||
96, 88, 128, 56, 40, 216, 56, 680,
|
||||
480, 32, 32, 32, 32, 32, 352, 1456,
|
||||
104, 32, 24, 152, 152, 152, 16, 64,
|
||||
640, 40, 16, 408, 24, 288, 176, 2800,
|
||||
48, -1, 40, 24, 200, 88, 16, 120,
|
||||
3560, 32, 64, 16, 16, -1, -1, -1,
|
||||
-1, 24, 40, 24, 40, 24, 128, 2256,
|
||||
136, 32, 72, 24, 64, 88, 48, 32,
|
||||
96, 152, 64, 32,
|
||||
};
|
||||
|
||||
return asset_type_sizes[type];
|
||||
}
|
||||
|
||||
template <game::XAssetType Type, size_t Size>
|
||||
char* reallocate_asset_pool()
|
||||
{
|
||||
constexpr auto element_size = get_asset_type_size(Type);
|
||||
static char new_pool[element_size * Size] = {0};
|
||||
assert(get_asset_type_size(Type) == game::DB_GetXAssetTypeSize(Type));
|
||||
|
||||
std::memmove(new_pool, game::DB_XAssetPool[Type], game::g_poolSize[Type] * element_size);
|
||||
|
||||
game::DB_XAssetPool[Type] = new_pool;
|
||||
game::g_poolSize[Type] = Size;
|
||||
|
||||
return new_pool;
|
||||
}
|
||||
|
||||
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool include_override)
|
||||
{
|
||||
game::DB_EnumXAssets_Internal(type, static_cast<void(*)(game::XAssetHeader, void*)>([](game::XAssetHeader header, void* data)
|
||||
{
|
||||
const auto& cb = *static_cast<const std::function<void(game::XAssetHeader)>*>(data);
|
||||
cb(header);
|
||||
}), &callback, include_override);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
db_try_load_x_file_internal_hook.create(SELECT_VALUE(0x1401816F0, 0x1402741C0), &db_try_load_x_file_internal);
|
||||
|
||||
db_find_x_asset_header_hook.create(game::DB_FindXAssetHeader, db_find_x_asset_header_stub);
|
||||
dvars::g_dump_scripts = game::Dvar_RegisterBool("g_dumpScripts", false, game::DVAR_FLAG_NONE, "Dump GSC scripts to binary format");
|
||||
|
||||
command::add("loadzone", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("usage: loadzone <zone>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
game::XZoneInfo info{};
|
||||
info.name = params.get(1);
|
||||
info.allocFlags = 1;
|
||||
info.freeFlags = 0;
|
||||
game::DB_LoadXAssets(&info, 1u, game::DBSyncMode::DB_LOAD_SYNC);
|
||||
});
|
||||
|
||||
command::add("g_poolSizes", []()
|
||||
{
|
||||
for (auto i = 0; i < game::ASSET_TYPE_COUNT; i++)
|
||||
{
|
||||
console::info("g_poolSize[%i]: %i // %s\n", i, game::g_poolSize[i], game::g_assetNames[i]);
|
||||
}
|
||||
});
|
||||
|
||||
reallocate_asset_pool<game::ASSET_TYPE_FONT, 48>();
|
||||
|
||||
if (!game::environment::is_sp())
|
||||
{
|
||||
const auto* xmodel_pool = reallocate_asset_pool<game::ASSET_TYPE_XMODEL, 8832>();
|
||||
utils::hook::inject(0x14026FD63, xmodel_pool + 8);
|
||||
utils::hook::inject(0x14026FDB3, xmodel_pool + 8);
|
||||
utils::hook::inject(0x14026FFAC, xmodel_pool + 8);
|
||||
utils::hook::inject(0x14027463C, xmodel_pool + 8);
|
||||
utils::hook::inject(0x140274689, xmodel_pool + 8);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(fastfiles::component)
|
10
src/client/component/fastfiles.hpp
Normal file
10
src/client/component/fastfiles.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace fastfiles
|
||||
{
|
||||
std::string get_current_fastfile();
|
||||
|
||||
void enum_assets(game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, bool include_override);
|
||||
}
|
272
src/client/component/filesystem.cpp
Normal file
272
src/client/component/filesystem.cpp
Normal file
@ -0,0 +1,272 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "dvars.hpp"
|
||||
|
||||
#include "filesystem.hpp"
|
||||
#include "game_module.hpp"
|
||||
#include "console.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace filesystem
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool initialized = false;
|
||||
|
||||
bool custom_path_registered = false;
|
||||
|
||||
std::deque<std::filesystem::path>& get_search_paths_internal()
|
||||
{
|
||||
static std::deque<std::filesystem::path> search_paths{};
|
||||
return search_paths;
|
||||
}
|
||||
|
||||
std::string get_binary_directory()
|
||||
{
|
||||
const auto dir = game_module::get_host_module().get_folder();
|
||||
return utils::string::replace(dir, "/", "\\");
|
||||
}
|
||||
|
||||
void register_custom_path_stub(const char* path, const char* dir)
|
||||
{
|
||||
if (!custom_path_registered)
|
||||
{
|
||||
custom_path_registered = true;
|
||||
|
||||
const auto launcher_dir = get_binary_directory();
|
||||
game::FS_AddLocalizedGameDirectory(launcher_dir.data(), "data");
|
||||
}
|
||||
|
||||
game::FS_AddLocalizedGameDirectory(path, dir);
|
||||
}
|
||||
|
||||
void fs_startup_stub(const char* gamename)
|
||||
{
|
||||
console::info("[FS] Startup\n");
|
||||
|
||||
custom_path_registered = false;
|
||||
|
||||
game::FS_Startup(gamename);
|
||||
}
|
||||
|
||||
bool can_insert_path(const std::filesystem::path& path)
|
||||
{
|
||||
const auto& paths = get_search_paths_internal();
|
||||
return std::ranges::none_of(paths.cbegin(), paths.cend(), [path](const auto& elem)
|
||||
{
|
||||
return elem == path;
|
||||
});
|
||||
}
|
||||
|
||||
void startup()
|
||||
{
|
||||
register_path("s1");
|
||||
register_path(get_binary_directory() + "\\data");
|
||||
|
||||
// game's search paths
|
||||
register_path("devraw");
|
||||
register_path("devraw_shared");
|
||||
register_path("raw_shared");
|
||||
register_path("raw");
|
||||
register_path("main");
|
||||
}
|
||||
|
||||
void check_for_startup()
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
initialized = true;
|
||||
startup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file::file(std::string name)
|
||||
: name_(std::move(name))
|
||||
{
|
||||
char* buffer{};
|
||||
const auto size = game::FS_ReadFile(this->name_.data(), &buffer);
|
||||
|
||||
if (size >= 0 && buffer)
|
||||
{
|
||||
this->valid_ = true;
|
||||
this->buffer_.append(buffer, size);
|
||||
game::FS_FreeFile(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
bool file::exists() const
|
||||
{
|
||||
return this->valid_;
|
||||
}
|
||||
|
||||
const std::string& file::get_buffer() const
|
||||
{
|
||||
return this->buffer_;
|
||||
}
|
||||
|
||||
const std::string& file::get_name() const
|
||||
{
|
||||
return this->name_;
|
||||
}
|
||||
|
||||
std::string read_file(const std::string& path)
|
||||
{
|
||||
check_for_startup();
|
||||
|
||||
for (const auto& search_path : get_search_paths_internal())
|
||||
{
|
||||
const auto path_ = search_path / path;
|
||||
if (utils::io::file_exists(path_.generic_string()))
|
||||
{
|
||||
return utils::io::read_file(path_.generic_string());
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool read_file(const std::string& path, std::string* data, std::string* real_path)
|
||||
{
|
||||
check_for_startup();
|
||||
|
||||
for (const auto& search_path : get_search_paths_internal())
|
||||
{
|
||||
const auto path_ = search_path / path;
|
||||
if (utils::io::read_file(path_.generic_string(), data))
|
||||
{
|
||||
if (real_path != nullptr)
|
||||
{
|
||||
*real_path = path_.generic_string();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool find_file(const std::string& path, std::string* real_path)
|
||||
{
|
||||
check_for_startup();
|
||||
|
||||
for (const auto& search_path : get_search_paths_internal())
|
||||
{
|
||||
const auto path_ = search_path / path;
|
||||
if (utils::io::file_exists(path_.generic_string()))
|
||||
{
|
||||
*real_path = path_.generic_string();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool exists(const std::string& path)
|
||||
{
|
||||
check_for_startup();
|
||||
|
||||
for (const auto& search_path : get_search_paths_internal())
|
||||
{
|
||||
const auto path_ = search_path / path;
|
||||
if (utils::io::file_exists(path_.generic_string()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void register_path(const std::filesystem::path& path)
|
||||
{
|
||||
if (can_insert_path(path))
|
||||
{
|
||||
console::info("[FS] Registering path '%s'\n", path.generic_string().data());
|
||||
get_search_paths_internal().push_front(path);
|
||||
}
|
||||
}
|
||||
|
||||
void unregister_path(const std::filesystem::path& path)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto& search_paths = get_search_paths_internal();
|
||||
for (auto i = search_paths.begin(); i != search_paths.end();)
|
||||
{
|
||||
if (*i == path)
|
||||
{
|
||||
console::info("[FS] Unregistering path '%s'\n", path.generic_string().data());
|
||||
i = search_paths.erase(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> get_search_paths()
|
||||
{
|
||||
std::vector<std::string> paths{};
|
||||
|
||||
for (const auto& path : get_search_paths_internal())
|
||||
{
|
||||
paths.push_back(path.generic_string());
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_search_paths_rev()
|
||||
{
|
||||
std::vector<std::string> paths{};
|
||||
const auto& search_paths = get_search_paths_internal();
|
||||
|
||||
for (auto i = search_paths.rbegin(); i != search_paths.rend(); ++i)
|
||||
{
|
||||
paths.push_back(i->generic_string());
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// Set fs_basegame
|
||||
dvars::override::register_string("fs_basegame", "s1", game::DVAR_FLAG_WRITE);
|
||||
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
utils::hook::call(0x140360A74, fs_startup_stub);
|
||||
|
||||
utils::hook::call(0x140361FE0, register_custom_path_stub);
|
||||
utils::hook::call(0x140362000, register_custom_path_stub);
|
||||
utils::hook::call(0x14036203F, register_custom_path_stub);
|
||||
}
|
||||
else
|
||||
{
|
||||
utils::hook::call(0x1404AE192, fs_startup_stub);
|
||||
utils::hook::call(0x1404AE5C3, fs_startup_stub);
|
||||
|
||||
utils::hook::call(0x1404AEFD0, register_custom_path_stub);
|
||||
utils::hook::call(0x1404AEFF0, register_custom_path_stub);
|
||||
utils::hook::call(0x1404AF02F, register_custom_path_stub);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(filesystem::component)
|
30
src/client/component/filesystem.hpp
Normal file
30
src/client/component/filesystem.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
namespace filesystem
|
||||
{
|
||||
class file
|
||||
{
|
||||
public:
|
||||
file(std::string name);
|
||||
|
||||
[[nodiscard]] bool exists() const;
|
||||
[[nodiscard]] const std::string& get_buffer() const;
|
||||
[[nodiscard]] const std::string& get_name() const;
|
||||
|
||||
private:
|
||||
bool valid_ = false;
|
||||
std::string name_;
|
||||
std::string buffer_;
|
||||
};
|
||||
|
||||
std::string read_file(const std::string& path);
|
||||
bool read_file(const std::string& path, std::string* data, std::string* real_path = nullptr);
|
||||
bool find_file(const std::string& path, std::string* real_path);
|
||||
bool exists(const std::string& path);
|
||||
|
||||
void register_path(const std::filesystem::path& path);
|
||||
void unregister_path(const std::filesystem::path& path);
|
||||
|
||||
std::vector<std::string> get_search_paths();
|
||||
std::vector<std::string> get_search_paths_rev();
|
||||
}
|
186
src/client/component/fps.cpp
Normal file
186
src/client/component/fps.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "fps.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace fps
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const game::dvar_t* cg_drawFPS;
|
||||
const game::dvar_t* cg_drawPing;
|
||||
|
||||
float fps_color_good[4] = {0.6f, 1.0f, 0.0f, 1.0f};
|
||||
float fps_color_ok[4] = {1.0f, 0.7f, 0.3f, 1.0f};
|
||||
float fps_color_bad[4] = {1.0f, 0.3f, 0.3f, 1.0f};
|
||||
|
||||
//float origin_color[4] = { 1.0f, 0.67f, 0.13f, 1.0f };
|
||||
float ping_color[4] = {1.0f, 1.0f, 1.0f, 0.65f};
|
||||
|
||||
struct cg_perf_data
|
||||
{
|
||||
std::chrono::time_point<std::chrono::steady_clock> perf_start;
|
||||
std::int32_t current_ms{};
|
||||
std::int32_t previous_ms{};
|
||||
std::int32_t frame_ms{};
|
||||
std::int32_t history[32]{};
|
||||
std::int32_t count{};
|
||||
std::int32_t index{};
|
||||
std::int32_t instant{};
|
||||
std::int32_t total{};
|
||||
float average{};
|
||||
float variance{};
|
||||
std::int32_t min{};
|
||||
std::int32_t max{};
|
||||
};
|
||||
|
||||
cg_perf_data cg_perf = cg_perf_data();
|
||||
|
||||
void perf_calc_fps(cg_perf_data* data, const std::int32_t value)
|
||||
{
|
||||
data->history[data->index % 32] = value;
|
||||
data->instant = value;
|
||||
data->min = std::numeric_limits<int>::max();
|
||||
data->max = 0;
|
||||
data->average = 0.0f;
|
||||
data->variance = 0.0f;
|
||||
data->total = 0;
|
||||
|
||||
for (auto i = 0; i < data->count; ++i)
|
||||
{
|
||||
const std::int32_t idx = (data->index - i) % 32;
|
||||
|
||||
if (idx < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
data->total += data->history[idx];
|
||||
|
||||
if (data->min > data->history[idx])
|
||||
{
|
||||
data->min = data->history[idx];
|
||||
}
|
||||
|
||||
if (data->max < data->history[idx])
|
||||
{
|
||||
data->max = data->history[idx];
|
||||
}
|
||||
}
|
||||
|
||||
data->average = static_cast<float>(data->total) / static_cast<float>(data->count);
|
||||
++data->index;
|
||||
}
|
||||
|
||||
void perf_update()
|
||||
{
|
||||
cg_perf.count = 32;
|
||||
|
||||
cg_perf.current_ms = static_cast<std::int32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::high_resolution_clock::now() - cg_perf.perf_start).count());
|
||||
cg_perf.frame_ms = cg_perf.current_ms - cg_perf.previous_ms;
|
||||
cg_perf.previous_ms = cg_perf.current_ms;
|
||||
|
||||
perf_calc_fps(&cg_perf, cg_perf.frame_ms);
|
||||
|
||||
utils::hook::invoke<void>(SELECT_VALUE(0x1404F6A90, 0x14062C540));
|
||||
}
|
||||
|
||||
void cg_draw_fps()
|
||||
{
|
||||
if (cg_drawFPS && cg_drawFPS->current.integer > 0)
|
||||
{
|
||||
const auto fps = get_fps();
|
||||
|
||||
auto* font = game::R_RegisterFont("fonts/consolefont");
|
||||
if (!font) return;
|
||||
|
||||
const auto* const fps_string = utils::string::va("%i", fps);
|
||||
|
||||
const auto scale = 1.0f;
|
||||
|
||||
const auto x = (game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 10.0f) - game::R_TextWidth(
|
||||
fps_string, std::numeric_limits<int>::max(), font) * scale;
|
||||
|
||||
const auto y = font->pixelHeight * 1.2f;
|
||||
|
||||
const auto fps_color = fps >= 60 ? fps_color_good : (fps >= 30 ? fps_color_ok : fps_color_bad);
|
||||
game::R_AddCmdDrawText(fps_string, std::numeric_limits<int>::max(), font, x, y, scale, scale, 0.0f, fps_color, 6);
|
||||
}
|
||||
}
|
||||
|
||||
void cg_draw_ping()
|
||||
{
|
||||
if (cg_drawPing->current.integer > 0 && game::CL_IsCgameInitialized())
|
||||
{
|
||||
auto* font = game::R_RegisterFont("fonts/consolefont");
|
||||
if (!font) return;
|
||||
|
||||
auto* const ping_string = utils::string::va("Ping: %i", *game::mp::ping);
|
||||
|
||||
const auto scale = 1.0f;
|
||||
|
||||
const auto x = (game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 375.0f) - game::R_TextWidth(
|
||||
ping_string, std::numeric_limits<int>::max(), font) * scale;
|
||||
|
||||
const auto y = font->pixelHeight * 1.2f;
|
||||
|
||||
game::R_AddCmdDrawText(ping_string, std::numeric_limits<int>::max(), font, x, y, scale, scale, 0.0f, ping_color, 6);
|
||||
}
|
||||
}
|
||||
|
||||
const game::dvar_t* cg_draw_fps_register_stub(const char* dvar_name, const char** value_list, const int default_index, unsigned int /*flags*/, const char* description)
|
||||
{
|
||||
cg_drawFPS = game::Dvar_RegisterEnum(dvar_name, value_list, default_index, game::DVAR_FLAG_SAVED, description);
|
||||
return cg_drawFPS;
|
||||
}
|
||||
}
|
||||
|
||||
int get_fps()
|
||||
{
|
||||
return static_cast<std::int32_t>(static_cast<float>(1000.0f /
|
||||
static_cast<float>(cg_perf.average)) + 9.313225746154785e-10);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// fps setup
|
||||
cg_perf.perf_start = std::chrono::high_resolution_clock::now();
|
||||
utils::hook::call(SELECT_VALUE(0x140144D41, 0x140213B27), &perf_update);
|
||||
|
||||
// change cg_drawfps flags to saved
|
||||
utils::hook::call(SELECT_VALUE(0x1400EF951, 0x1401A4B8E), &cg_draw_fps_register_stub);
|
||||
|
||||
// fix ping value
|
||||
utils::hook::nop(0x140213031, 2);
|
||||
|
||||
scheduler::loop(cg_draw_fps, scheduler::pipeline::renderer);
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
cg_drawPing = game::Dvar_RegisterInt("cg_drawPing", 0, 0, 1, game::DVAR_FLAG_SAVED, "Draw ping");
|
||||
scheduler::loop(cg_draw_ping, scheduler::pipeline::renderer);
|
||||
}
|
||||
|
||||
game::Dvar_RegisterBool("cg_infobar_ping", false, game::DVAR_FLAG_SAVED, "Show server latency");
|
||||
game::Dvar_RegisterBool("cg_infobar_fps", false, game::DVAR_FLAG_SAVED, "Show FPS counter");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(fps::component)
|
6
src/client/component/fps.hpp
Normal file
6
src/client/component/fps.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace fps
|
||||
{
|
||||
int get_fps();
|
||||
}
|
780
src/client/component/game_console.cpp
Normal file
780
src/client/component/game_console.cpp
Normal file
@ -0,0 +1,780 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "game_console.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
#include "version.hpp"
|
||||
|
||||
#define console_font game::R_RegisterFont("fonts/consolefont")
|
||||
#define material_white game::Material_RegisterHandle("white")
|
||||
|
||||
namespace game_console
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct console_globals
|
||||
{
|
||||
float x{};
|
||||
float y{};
|
||||
float left_x{};
|
||||
float font_height{};
|
||||
bool may_auto_complete{};
|
||||
char auto_complete_choice[64]{};
|
||||
int info_line_count{};
|
||||
};
|
||||
|
||||
using output_queue = std::deque<std::string>;
|
||||
|
||||
struct ingame_console
|
||||
{
|
||||
char buffer[256]{};
|
||||
int cursor{};
|
||||
int font_height{};
|
||||
int visible_line_count{};
|
||||
int visible_pixel_width{};
|
||||
float screen_min[2]{}; //left & top
|
||||
float screen_max[2]{}; //right & bottom
|
||||
console_globals globals{};
|
||||
bool output_visible{};
|
||||
int display_line_offset{};
|
||||
int line_count{};
|
||||
utils::concurrency::container<output_queue, std::recursive_mutex> output{};
|
||||
};
|
||||
|
||||
ingame_console con{};
|
||||
|
||||
std::int32_t history_index = -1;
|
||||
std::deque<std::string> history{};
|
||||
|
||||
std::string fixed_input{};
|
||||
std::vector<std::string> matches{};
|
||||
|
||||
float color_white[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
float color_s1[4] = {1.0f, 0.90f, 0.0f, 1.0f};
|
||||
|
||||
void clear()
|
||||
{
|
||||
strncpy_s(con.buffer, "", sizeof(con.buffer));
|
||||
con.cursor = 0;
|
||||
|
||||
fixed_input = "";
|
||||
matches.clear();
|
||||
}
|
||||
|
||||
void print_internal(const std::string& data)
|
||||
{
|
||||
con.output.access([&](output_queue& output)
|
||||
{
|
||||
if (con.visible_line_count > 0
|
||||
&& con.display_line_offset == (output.size() - con.visible_line_count))
|
||||
{
|
||||
con.display_line_offset++;
|
||||
}
|
||||
output.push_back(data);
|
||||
if (output.size() > 512)
|
||||
{
|
||||
output.pop_front();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void toggle_console()
|
||||
{
|
||||
clear();
|
||||
|
||||
con.output_visible = false;
|
||||
*game::keyCatchers ^= 1;
|
||||
}
|
||||
|
||||
void toggle_console_output()
|
||||
{
|
||||
con.output_visible = con.output_visible == 0;
|
||||
}
|
||||
|
||||
void check_resize()
|
||||
{
|
||||
con.screen_min[0] = 6.0f;
|
||||
con.screen_min[1] = 6.0f;
|
||||
con.screen_max[0] = game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 6.0f;
|
||||
con.screen_max[1] = game::ScrPlace_GetViewPlacement()->realViewportSize[1] - 6.0f;
|
||||
|
||||
if (console_font)
|
||||
{
|
||||
con.font_height = console_font->pixelHeight;
|
||||
con.visible_line_count = static_cast<int>((con.screen_max[1] - con.screen_min[1] - (con.font_height * 2)
|
||||
) -
|
||||
24.0f) / con.font_height;
|
||||
con.visible_pixel_width = static_cast<int>(((con.screen_max[0] - con.screen_min[0]) - 10.0f) - 18.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
con.font_height = 0;
|
||||
con.visible_line_count = 0;
|
||||
con.visible_pixel_width = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_box(const float x, const float y, const float w, const float h, float* color)
|
||||
{
|
||||
game::vec4_t dark_color;
|
||||
|
||||
dark_color[0] = color[0] * 0.5f;
|
||||
dark_color[1] = color[1] * 0.5f;
|
||||
dark_color[2] = color[2] * 0.5f;
|
||||
dark_color[3] = color[3];
|
||||
|
||||
game::R_AddCmdDrawStretchPic(x, y, w, h, 0.0f, 0.0f, 0.0f, 0.0f, color, material_white);
|
||||
game::R_AddCmdDrawStretchPic(x, y, 2.0f, h, 0.0f, 0.0f, 0.0f, 0.0f, dark_color, material_white);
|
||||
game::R_AddCmdDrawStretchPic((x + w) - 2.0f, y, 2.0f, h, 0.0f, 0.0f, 0.0f, 0.0f, dark_color,
|
||||
material_white);
|
||||
game::R_AddCmdDrawStretchPic(x, y, w, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, dark_color, material_white);
|
||||
game::R_AddCmdDrawStretchPic(x, (y + h) - 2.0f, w, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, dark_color,
|
||||
material_white);
|
||||
}
|
||||
|
||||
void draw_input_box(const int lines, float* color)
|
||||
{
|
||||
draw_box(
|
||||
con.globals.x - 6.0f,
|
||||
con.globals.y - 6.0f,
|
||||
(con.screen_max[0] - con.screen_min[0]) - ((con.globals.x - 6.0f) - con.screen_min[0]),
|
||||
(lines * con.globals.font_height) + 12.0f,
|
||||
color);
|
||||
}
|
||||
|
||||
void draw_input_text_and_over(const char* str, float* color)
|
||||
{
|
||||
game::R_AddCmdDrawText(str, 0x7FFFFFFF, console_font, con.globals.x,
|
||||
con.globals.y + con.globals.font_height, 1,
|
||||
1, 0, color, 0);
|
||||
con.globals.x = game::R_TextWidth(str, 0, console_font) + con.globals.x + 6.0f;
|
||||
}
|
||||
|
||||
void draw_hint_box(const int lines, float* color, [[maybe_unused]] float offset_x = 0.0f,
|
||||
[[maybe_unused]] float offset_y = 0.0f)
|
||||
{
|
||||
const auto _h = lines * con.globals.font_height + 12.0f;
|
||||
const auto _y = con.globals.y - 3.0f + con.globals.font_height + 12.0f;
|
||||
const auto _w = (con.screen_max[0] - con.screen_min[0]) - ((con.globals.x - 6.0f) - con.screen_min[0]);
|
||||
|
||||
draw_box(con.globals.x - 6.0f, _y, _w, _h, color);
|
||||
}
|
||||
|
||||
void draw_hint_text(const int line, const char* text, float* color, const float offset = 0.0f)
|
||||
{
|
||||
const auto _y = con.globals.font_height + con.globals.y + (con.globals.font_height * (line + 1)) + 15.0f;
|
||||
|
||||
game::R_AddCmdDrawText(text, 0x7FFFFFFF, console_font, con.globals.x + offset, _y, 1.0f, 1.0f, 0.0f, color,
|
||||
0);
|
||||
}
|
||||
|
||||
void draw_input()
|
||||
{
|
||||
con.globals.font_height = static_cast<float>(console_font->pixelHeight);
|
||||
con.globals.x = con.screen_min[0] + 6.0f;
|
||||
con.globals.y = con.screen_min[1] + 6.0f;
|
||||
con.globals.left_x = con.screen_min[0] + 6.0f;
|
||||
|
||||
draw_input_box(1, dvars::con_inputBoxColor->current.vector);
|
||||
draw_input_text_and_over("s1-mod: " VERSION ">", color_s1);
|
||||
|
||||
con.globals.left_x = con.globals.x;
|
||||
con.globals.auto_complete_choice[0] = 0;
|
||||
|
||||
game::R_AddCmdDrawTextWithCursor(con.buffer, 0x7FFFFFFF, console_font, con.globals.x,
|
||||
con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0.0f, color_white, 0,
|
||||
con.cursor, '|');
|
||||
|
||||
// check if using a prefixed '/' or not
|
||||
const auto input = con.buffer[1] && (con.buffer[0] == '/' || con.buffer[0] == '\\')
|
||||
? std::string(con.buffer).substr(1)
|
||||
: std::string(con.buffer);
|
||||
|
||||
if (!input.length())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (input != fixed_input)
|
||||
{
|
||||
matches.clear();
|
||||
|
||||
if (input.find(" ") != std::string::npos)
|
||||
{
|
||||
find_matches(input.substr(0, input.find(" ")), matches, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
find_matches(input, matches, false);
|
||||
}
|
||||
|
||||
fixed_input = input;
|
||||
}
|
||||
|
||||
con.globals.may_auto_complete = false;
|
||||
if (matches.size() > 24)
|
||||
{
|
||||
draw_hint_box(1, dvars::con_inputHintBoxColor->current.vector);
|
||||
draw_hint_text(0, utils::string::va("%i matches (too many to show here)", matches.size()),
|
||||
dvars::con_inputDvarMatchColor->current.vector);
|
||||
}
|
||||
else if (matches.size() == 1)
|
||||
{
|
||||
auto* const dvar = game::Dvar_FindVar(matches[0].data());
|
||||
const auto line_count = dvar ? 2 : 1;
|
||||
|
||||
draw_hint_box(line_count, dvars::con_inputHintBoxColor->current.vector);
|
||||
draw_hint_text(0, matches[0].data(),
|
||||
dvar
|
||||
? dvars::con_inputDvarMatchColor->current.vector
|
||||
: dvars::con_inputCmdMatchColor->current.vector);
|
||||
|
||||
if (dvar)
|
||||
{
|
||||
const auto offset = (con.screen_max[0] - con.globals.x) / 2.5f;
|
||||
|
||||
draw_hint_text(0, game::Dvar_ValueToString(dvar, dvar->current),
|
||||
dvars::con_inputDvarValueColor->current.vector, offset);
|
||||
draw_hint_text(1, " default", dvars::con_inputDvarInactiveValueColor->current.vector);
|
||||
draw_hint_text(1, game::Dvar_ValueToString(dvar, dvar->reset),
|
||||
dvars::con_inputDvarInactiveValueColor->current.vector, offset);
|
||||
}
|
||||
|
||||
strncpy_s(con.globals.auto_complete_choice, matches[0].data(), 64);
|
||||
con.globals.may_auto_complete = true;
|
||||
}
|
||||
else if (matches.size() > 1)
|
||||
{
|
||||
draw_hint_box(static_cast<int>(matches.size()), dvars::con_inputHintBoxColor->current.vector);
|
||||
|
||||
const auto offset = (con.screen_max[0] - con.globals.x) / 2.5f;
|
||||
|
||||
for (size_t i = 0; i < matches.size(); i++)
|
||||
{
|
||||
auto* const dvar = game::Dvar_FindVar(matches[i].data());
|
||||
|
||||
draw_hint_text(static_cast<int>(i), matches[i].data(),
|
||||
dvar
|
||||
? dvars::con_inputDvarMatchColor->current.vector
|
||||
: dvars::con_inputCmdMatchColor->current.vector);
|
||||
|
||||
if (dvar)
|
||||
{
|
||||
draw_hint_text(static_cast<int>(i), game::Dvar_ValueToString(dvar, dvar->current),
|
||||
dvars::con_inputDvarValueColor->current.vector, offset);
|
||||
}
|
||||
}
|
||||
|
||||
strncpy_s(con.globals.auto_complete_choice, matches[0].data(), 64);
|
||||
con.globals.may_auto_complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_output_scrollbar(const float x, float y, const float width, const float height, output_queue& output)
|
||||
{
|
||||
const auto _x = (x + width) - 10.0f;
|
||||
draw_box(_x, y, 10.0f, height, dvars::con_outputBarColor->current.vector);
|
||||
|
||||
auto _height = height;
|
||||
if (output.size() > con.visible_line_count)
|
||||
{
|
||||
const auto percentage = static_cast<float>(con.visible_line_count) / output.size();
|
||||
_height *= percentage;
|
||||
|
||||
const auto remainingSpace = height - _height;
|
||||
const auto percentageAbove = static_cast<float>(con.display_line_offset) / (output.size() - con.
|
||||
visible_line_count);
|
||||
|
||||
y = y + (remainingSpace * percentageAbove);
|
||||
}
|
||||
|
||||
draw_box(_x, y, 10.0f, _height, dvars::con_outputSliderColor->current.vector);
|
||||
}
|
||||
|
||||
void draw_output_text(const float x, float y, output_queue& output)
|
||||
{
|
||||
const auto offset = output.size() >= con.visible_line_count
|
||||
? 0.0f
|
||||
: (con.font_height * (con.visible_line_count - output.size()));
|
||||
|
||||
for (auto i = 0; i < con.visible_line_count; i++)
|
||||
{
|
||||
y = console_font->pixelHeight + y;
|
||||
|
||||
const auto index = i + con.display_line_offset;
|
||||
if (index >= output.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
game::R_AddCmdDrawText(output.at(index).data(), 0x7FFF, console_font, x, y + offset, 1.0f, 1.0f,
|
||||
0.0f, color_white, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void draw_output_window()
|
||||
{
|
||||
con.output.access([](output_queue& output)
|
||||
{
|
||||
draw_box(con.screen_min[0], con.screen_min[1] + 32.0f, con.screen_max[0] - con.screen_min[0],
|
||||
(con.screen_max[1] - con.screen_min[1]) - 32.0f, dvars::con_outputWindowColor->current.vector);
|
||||
|
||||
const auto x = con.screen_min[0] + 6.0f;
|
||||
const auto y = (con.screen_min[1] + 32.0f) + 6.0f;
|
||||
const auto width = (con.screen_max[0] - con.screen_min[0]) - 12.0f;
|
||||
const auto height = ((con.screen_max[1] - con.screen_min[1]) - 32.0f) - 12.0f;
|
||||
|
||||
game::R_AddCmdDrawText(game::Dvar_FindVar("version")->current.string, 0x7FFFFFFF, console_font, x,
|
||||
((height - 12.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_s1,
|
||||
0);
|
||||
|
||||
draw_output_scrollbar(x, y, width, height, output);
|
||||
draw_output_text(x, y, output);
|
||||
});
|
||||
}
|
||||
|
||||
void draw_console()
|
||||
{
|
||||
check_resize();
|
||||
|
||||
if (*game::keyCatchers & 1)
|
||||
{
|
||||
if (!(*game::keyCatchers & 1))
|
||||
{
|
||||
con.output_visible = false;
|
||||
}
|
||||
|
||||
if (con.output_visible)
|
||||
{
|
||||
draw_output_window();
|
||||
}
|
||||
|
||||
draw_input();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void print_internal(const char* fmt, ...)
|
||||
{
|
||||
char va_buffer[0x200]{};
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsprintf_s(va_buffer, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
const auto formatted = std::string(va_buffer);
|
||||
const auto lines = utils::string::split(formatted, '\n');
|
||||
|
||||
for (const auto& line : lines)
|
||||
{
|
||||
print_internal(line);
|
||||
}
|
||||
}
|
||||
|
||||
void print(const int type, const std::string& data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto lines = utils::string::split(data, '\n');
|
||||
for (const auto& line : lines)
|
||||
{
|
||||
print_internal(type == console::con_type_info ? line : "^"s.append(std::to_string(type)).append(line));
|
||||
}
|
||||
}
|
||||
|
||||
bool console_char_event(const int local_client_num, const int key)
|
||||
{
|
||||
if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*game::keyCatchers & 1)
|
||||
{
|
||||
if (key == game::keyNum_t::K_TAB) // tab (auto complete)
|
||||
{
|
||||
if (con.globals.may_auto_complete)
|
||||
{
|
||||
const auto first_char = con.buffer[0];
|
||||
|
||||
clear();
|
||||
|
||||
if (first_char == '\\' || first_char == '/')
|
||||
{
|
||||
con.buffer[0] = first_char;
|
||||
con.buffer[1] = '\0';
|
||||
}
|
||||
|
||||
strncat_s(con.buffer, con.globals.auto_complete_choice, 64);
|
||||
con.cursor = static_cast<int>(std::string(con.buffer).length());
|
||||
|
||||
if (con.cursor != 254)
|
||||
{
|
||||
con.buffer[con.cursor++] = ' ';
|
||||
con.buffer[con.cursor] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key == 'v' - 'a' + 1) // paste
|
||||
{
|
||||
const auto clipboard = utils::string::get_clipboard_data();
|
||||
if (clipboard.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < clipboard.length(); i++)
|
||||
{
|
||||
console_char_event(local_client_num, clipboard[i]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key == 'c' - 'a' + 1) // clear
|
||||
{
|
||||
clear();
|
||||
con.line_count = 0;
|
||||
con.display_line_offset = 0;
|
||||
con.output.access([](output_queue& output)
|
||||
{
|
||||
output.clear();
|
||||
});
|
||||
history_index = -1;
|
||||
history.clear();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key == 'h' - 'a' + 1) // backspace
|
||||
{
|
||||
if (con.cursor > 0)
|
||||
{
|
||||
memmove(con.buffer + con.cursor - 1, con.buffer + con.cursor,
|
||||
strlen(con.buffer) + 1 - con.cursor);
|
||||
con.cursor--;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key < 32)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (con.cursor == 256 - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
memmove(con.buffer + con.cursor + 1, con.buffer + con.cursor, strlen(con.buffer) + 1 - con.cursor);
|
||||
con.buffer[con.cursor] = static_cast<char>(key);
|
||||
con.cursor++;
|
||||
|
||||
if (con.cursor == strlen(con.buffer) + 1)
|
||||
{
|
||||
con.buffer[con.cursor] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool console_key_event(const int local_client_num, const int key, const int down)
|
||||
{
|
||||
if (key == game::keyNum_t::K_F10)
|
||||
{
|
||||
if (game::mp::svs_clients[local_client_num].header.state >= 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
game::Cmd_ExecuteSingleCommand(local_client_num, 0, "lui_open menu_systemlink_join\n");
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE)
|
||||
{
|
||||
if (!down)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (game::playerKeys[local_client_num].keys[game::keyNum_t::K_SHIFT].down)
|
||||
{
|
||||
if (!(*game::keyCatchers & 1))
|
||||
toggle_console();
|
||||
|
||||
toggle_console_output();
|
||||
return false;
|
||||
}
|
||||
|
||||
toggle_console();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*game::keyCatchers & 1)
|
||||
{
|
||||
if (down)
|
||||
{
|
||||
if (key == game::keyNum_t::K_UPARROW)
|
||||
{
|
||||
if (++history_index >= history.size())
|
||||
{
|
||||
history_index = static_cast<int>(history.size()) - 1;
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
if (history_index != -1)
|
||||
{
|
||||
strncpy_s(con.buffer, history.at(history_index).c_str(), sizeof(con.buffer));
|
||||
con.cursor = static_cast<int>(strlen(con.buffer));
|
||||
}
|
||||
}
|
||||
else if (key == game::keyNum_t::K_DOWNARROW)
|
||||
{
|
||||
if (--history_index < -1)
|
||||
{
|
||||
history_index = -1;
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
if (history_index != -1)
|
||||
{
|
||||
strncpy_s(con.buffer, history.at(history_index).c_str(), sizeof(con.buffer));
|
||||
con.cursor = static_cast<int>(strlen(con.buffer));
|
||||
}
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_RIGHTARROW)
|
||||
{
|
||||
if (con.cursor < strlen(con.buffer))
|
||||
{
|
||||
con.cursor++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_LEFTARROW)
|
||||
{
|
||||
if (con.cursor > 0)
|
||||
{
|
||||
con.cursor--;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//scroll through output
|
||||
if (key == game::keyNum_t::K_MWHEELUP || key == game::keyNum_t::K_PGUP)
|
||||
{
|
||||
con.output.access([](output_queue& output)
|
||||
{
|
||||
if (output.size() > con.visible_line_count && con.display_line_offset > 0)
|
||||
{
|
||||
con.display_line_offset--;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (key == game::keyNum_t::K_MWHEELDOWN || key == game::keyNum_t::K_PGDN)
|
||||
{
|
||||
con.output.access([](output_queue& output)
|
||||
{
|
||||
if (output.size() > con.visible_line_count
|
||||
&& con.display_line_offset < (output.size() - con.visible_line_count))
|
||||
{
|
||||
con.display_line_offset++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_ENTER)
|
||||
{
|
||||
game::Cbuf_AddText(0, utils::string::va("%s \n", fixed_input.data()));
|
||||
|
||||
if (history_index != -1)
|
||||
{
|
||||
const auto itr = history.begin() + history_index;
|
||||
|
||||
if (*itr == con.buffer)
|
||||
{
|
||||
history.erase(history.begin() + history_index);
|
||||
}
|
||||
}
|
||||
|
||||
history.push_front(con.buffer);
|
||||
|
||||
console::info("]%s\n", con.buffer);
|
||||
|
||||
if (history.size() > 10)
|
||||
{
|
||||
history.erase(history.begin() + 10);
|
||||
}
|
||||
|
||||
history_index = -1;
|
||||
|
||||
clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool match_compare(const std::string& input, const std::string& text, const bool exact)
|
||||
{
|
||||
if (exact && text == input) return true;
|
||||
if (!exact && text.find(input) != std::string::npos) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact)
|
||||
{
|
||||
input = utils::string::to_lower(input);
|
||||
|
||||
for (auto i = 0; i < *game::dvarCount; i++)
|
||||
{
|
||||
if (game::sortedDvars[i] && game::sortedDvars[i]->name)
|
||||
{
|
||||
auto name = utils::string::to_lower(game::sortedDvars[i]->name);
|
||||
if (game_console::match_compare(input, name, exact))
|
||||
{
|
||||
suggestions.emplace_back(game::sortedDvars[i]->name);
|
||||
}
|
||||
|
||||
if (exact && suggestions.size() > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto* cmd = *game::cmd_functions;
|
||||
while (cmd)
|
||||
{
|
||||
if (cmd->name)
|
||||
{
|
||||
auto name = utils::string::to_lower(cmd->name);
|
||||
if (game_console::match_compare(input, name, exact))
|
||||
{
|
||||
suggestions.emplace_back(cmd->name);
|
||||
}
|
||||
|
||||
if (exact && suggestions.size() > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
cmd = cmd->next;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_load() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler::loop(draw_console, scheduler::pipeline::renderer);
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize our structs
|
||||
con.cursor = 0;
|
||||
con.visible_line_count = 0;
|
||||
con.output_visible = false;
|
||||
con.display_line_offset = 0;
|
||||
con.line_count = 0;
|
||||
strncpy_s(con.buffer, "", sizeof(con.buffer));
|
||||
|
||||
con.globals.x = 0.0f;
|
||||
con.globals.y = 0.0f;
|
||||
con.globals.left_x = 0.0f;
|
||||
con.globals.font_height = 0.0f;
|
||||
con.globals.may_auto_complete = false;
|
||||
con.globals.info_line_count = 0;
|
||||
strncpy_s(con.globals.auto_complete_choice, "", 64);
|
||||
|
||||
// add clear command
|
||||
command::add("clear", [&]()
|
||||
{
|
||||
clear();
|
||||
con.line_count = 0;
|
||||
con.display_line_offset = 0;
|
||||
con.output.access([](output_queue& output)
|
||||
{
|
||||
output.clear();
|
||||
});
|
||||
history_index = -1;
|
||||
history.clear();
|
||||
});
|
||||
|
||||
// add our dvars
|
||||
dvars::con_inputBoxColor = game::Dvar_RegisterVec4("con_inputBoxColor", 0.2f, 0.2f, 0.2f, 0.9f, 0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED,
|
||||
"color of console input box");
|
||||
dvars::con_inputHintBoxColor = game::Dvar_RegisterVec4("con_inputHintBoxColor", 0.3f, 0.3f, 0.3f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED, "color of console input hint box");
|
||||
dvars::con_outputBarColor = game::Dvar_RegisterVec4("con_outputBarColor", 0.5f, 0.5f, 0.5f, 0.6f, 0.0f,
|
||||
1.0f, game::DVAR_FLAG_SAVED,
|
||||
"color of console output bar");
|
||||
dvars::con_outputSliderColor = game::Dvar_RegisterVec4("con_outputSliderColor", 1.0f, 0.8f, 0.0f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED, "color of console output slider");
|
||||
dvars::con_outputWindowColor = game::Dvar_RegisterVec4("con_outputWindowColor", 0.25f, 0.25f, 0.25f, 0.85f,
|
||||
0.0f,
|
||||
1.0f, game::DVAR_FLAG_SAVED, "color of console output window");
|
||||
dvars::con_inputDvarMatchColor = game::Dvar_RegisterVec4("con_inputDvarMatchColor", 1.0f, 1.0f, 0.8f, 1.0f,
|
||||
0.0f,
|
||||
1.0f, game::DVAR_FLAG_SAVED, "color of console matched dvar");
|
||||
dvars::con_inputDvarValueColor = game::Dvar_RegisterVec4("con_inputDvarValueColor", 1.0f, 1.0f, 0.8f, 1.0f,
|
||||
0.0f,
|
||||
1.0f, game::DVAR_FLAG_SAVED, "color of console matched dvar value");
|
||||
dvars::con_inputDvarInactiveValueColor = game::Dvar_RegisterVec4(
|
||||
"con_inputDvarInactiveValueColor", 0.8f, 0.8f,
|
||||
0.8f, 1.0f, 0.0f, 1.0f, game::DVAR_FLAG_SAVED,
|
||||
"color of console inactive dvar value");
|
||||
dvars::con_inputCmdMatchColor = game::Dvar_RegisterVec4("con_inputCmdMatchColor", 0.80f, 0.80f, 1.0f, 1.0f,
|
||||
0.0f,
|
||||
1.0f, game::DVAR_FLAG_SAVED, "color of console matched command");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(game_console::component)
|
12
src/client/component/game_console.hpp
Normal file
12
src/client/component/game_console.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace game_console
|
||||
{
|
||||
void print(int type, const std::string& data);
|
||||
|
||||
bool console_char_event(int local_client_num, int key);
|
||||
bool console_key_event(int local_client_num, int key, int down);
|
||||
|
||||
bool match_compare(const std::string& input, const std::string& text, const bool exact);
|
||||
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact);
|
||||
}
|
116
src/client/component/game_log.cpp
Normal file
116
src/client/component/game_log.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
#include "console.hpp"
|
||||
#include "game_log.hpp"
|
||||
|
||||
#include "gsc/script_extension.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace game_log
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void gscr_log_print()
|
||||
{
|
||||
char buf[1024]{};
|
||||
std::size_t out_chars = 0;
|
||||
|
||||
for (auto i = 0u; i < game::Scr_GetNumParam(); ++i)
|
||||
{
|
||||
const auto* value = game::Scr_GetString(i);
|
||||
const auto len = std::strlen(value);
|
||||
|
||||
out_chars += len;
|
||||
if (out_chars >= sizeof(buf))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
strncat_s(buf, value, _TRUNCATE);
|
||||
}
|
||||
|
||||
g_log_printf("%s", buf);
|
||||
}
|
||||
}
|
||||
|
||||
void g_log_printf(const char* fmt, ...)
|
||||
{
|
||||
const auto* log = dvars::g_log->current.string;
|
||||
if (*log == '\0')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[0x400]{};
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
|
||||
vsnprintf_s(buffer, _TRUNCATE, fmt, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
const auto time = *game::level_time / 1000;
|
||||
utils::io::write_file(log, utils::string::va("%3i:%i%i %s",
|
||||
time / 60,
|
||||
time % 60 / 10,
|
||||
time % 60 % 10,
|
||||
buffer
|
||||
), true);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gsc::override_function("logprint", &gscr_log_print);
|
||||
|
||||
scheduler::once([]
|
||||
{
|
||||
dvars::g_log = game::Dvar_RegisterString("g_log", "logs/games_mp.log", game::DVAR_FLAG_NONE, "Log file name");
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
scripting::on_init([]
|
||||
{
|
||||
console::info("------- Game Initialization -------\n");
|
||||
console::info("gamename: S1\n");
|
||||
console::info("gamedate: " __DATE__ "\n");
|
||||
|
||||
const auto* log = dvars::g_log->current.string;
|
||||
if (*log == '\0')
|
||||
{
|
||||
console::info("Not logging to disk.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
console::info("Logging to disk: '%s'.\n", log);
|
||||
g_log_printf("------------------------------------------------------------\n");
|
||||
g_log_printf("InitGame\n");
|
||||
});
|
||||
|
||||
scripting::on_shutdown([](int free_scripts)
|
||||
{
|
||||
console::info("==== ShutdownGame (%d) ====\n", free_scripts);
|
||||
|
||||
g_log_printf("ShutdownGame:\n");
|
||||
g_log_printf("------------------------------------------------------------\n");
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(game_log::component)
|
6
src/client/component/game_log.hpp
Normal file
6
src/client/component/game_log.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace game_log
|
||||
{
|
||||
void g_log_printf(const char* fmt, ...);
|
||||
}
|
118
src/client/component/game_module.cpp
Normal file
118
src/client/component/game_module.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game_module.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace game_module
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour handle_a_hook;
|
||||
utils::hook::detour handle_w_hook;
|
||||
utils::hook::detour handle_ex_a_hook;
|
||||
utils::hook::detour handle_ex_w_hook;
|
||||
utils::hook::detour file_name_a_hook;
|
||||
utils::hook::detour file_name_w_hook;
|
||||
|
||||
HMODULE __stdcall get_module_handle_a(const LPCSTR module_name)
|
||||
{
|
||||
if (!module_name)
|
||||
{
|
||||
return get_game_module();
|
||||
}
|
||||
|
||||
return handle_a_hook.invoke<HMODULE>(module_name);
|
||||
}
|
||||
|
||||
HMODULE __stdcall get_module_handle_w(const LPWSTR module_name)
|
||||
{
|
||||
if (!module_name)
|
||||
{
|
||||
return get_game_module();
|
||||
}
|
||||
|
||||
return handle_w_hook.invoke<HMODULE>(module_name);
|
||||
}
|
||||
|
||||
BOOL __stdcall get_module_handle_ex_a(const DWORD flags, const LPCSTR module_name, HMODULE* hmodule)
|
||||
{
|
||||
if (!module_name)
|
||||
{
|
||||
*hmodule = get_game_module();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return handle_ex_a_hook.invoke<BOOL>(flags, module_name, hmodule);
|
||||
}
|
||||
|
||||
BOOL __stdcall get_module_handle_ex_w(const DWORD flags, const LPCWSTR module_name, HMODULE* hmodule)
|
||||
{
|
||||
if (!module_name)
|
||||
{
|
||||
*hmodule = get_game_module();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return handle_ex_w_hook.invoke<BOOL>(flags, module_name, hmodule);
|
||||
}
|
||||
|
||||
DWORD __stdcall get_module_file_name_a(HMODULE hmodule, const LPSTR filename, const DWORD size)
|
||||
{
|
||||
if (!hmodule)
|
||||
{
|
||||
hmodule = get_game_module();
|
||||
}
|
||||
|
||||
return file_name_a_hook.invoke<DWORD>(hmodule, filename, size);
|
||||
}
|
||||
|
||||
DWORD __stdcall get_module_file_name_w(HMODULE hmodule, const LPWSTR filename, const DWORD size)
|
||||
{
|
||||
if (!hmodule)
|
||||
{
|
||||
hmodule = get_game_module();
|
||||
}
|
||||
|
||||
return file_name_w_hook.invoke<DWORD>(hmodule, filename, size);
|
||||
}
|
||||
|
||||
void hook_module_resolving()
|
||||
{
|
||||
handle_a_hook.create(&GetModuleHandleA, &get_module_handle_a);
|
||||
handle_w_hook.create(&GetModuleHandleW, &get_module_handle_w);
|
||||
handle_ex_w_hook.create(&GetModuleHandleExA, &get_module_handle_ex_a);
|
||||
handle_ex_w_hook.create(&GetModuleHandleExW, &get_module_handle_ex_w);
|
||||
file_name_a_hook.create(&GetModuleFileNameA, &get_module_file_name_a);
|
||||
file_name_w_hook.create(&GetModuleFileNameW, &get_module_file_name_w);
|
||||
}
|
||||
}
|
||||
|
||||
utils::nt::library get_game_module()
|
||||
{
|
||||
static utils::nt::library game{HMODULE(0x140000000)};
|
||||
return game;
|
||||
}
|
||||
|
||||
utils::nt::library get_host_module()
|
||||
{
|
||||
static utils::nt::library host{};
|
||||
return host;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_start() override
|
||||
{
|
||||
get_host_module();
|
||||
}
|
||||
|
||||
void post_load() override
|
||||
{
|
||||
hook_module_resolving();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(game_module::component)
|
9
src/client/component/game_module.hpp
Normal file
9
src/client/component/game_module.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/nt.hpp>
|
||||
|
||||
namespace game_module
|
||||
{
|
||||
utils::nt::library get_game_module();
|
||||
utils::nt::library get_host_module();
|
||||
}
|
206
src/client/component/gameplay.cpp
Normal file
206
src/client/component/gameplay.cpp
Normal file
@ -0,0 +1,206 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace gameplay
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour pm_weapon_use_ammo_hook;
|
||||
|
||||
int stuck_in_client_stub(game::mp::gentity_s* entity)
|
||||
{
|
||||
if (dvars::g_playerEjection->current.enabled)
|
||||
{
|
||||
return utils::hook::invoke<int>(0x1402DA310, entity); // StuckInClient
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cm_transformed_capsule_trace_stub(game::trace_t* results, const float* start, const float* end,
|
||||
game::Bounds* bounds, game::Bounds* capsule, int contents, const float* origin, const float* angles)
|
||||
{
|
||||
if (dvars::g_playerCollision->current.enabled)
|
||||
{
|
||||
utils::hook::invoke<void>(0x1403AB1C0,
|
||||
results, start, end, bounds, capsule, contents, origin, angles); // CM_TransformedCapsuleTrace
|
||||
}
|
||||
}
|
||||
|
||||
const auto pm_bouncing_stub_mp = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
const auto no_bounce = a.newLabel();
|
||||
const auto loc_14014DF48 = a.newLabel();
|
||||
|
||||
a.push(rax);
|
||||
|
||||
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&dvars::pm_bouncing)));
|
||||
a.mov(al, byte_ptr(rax, 0x10));
|
||||
a.cmp(byte_ptr(rbp, -0x2D), al);
|
||||
|
||||
a.pop(rax);
|
||||
a.jz(no_bounce);
|
||||
a.jmp(0x14014DFB0);
|
||||
|
||||
a.bind(no_bounce);
|
||||
a.cmp(dword_ptr(rsp, 0x70), 0);
|
||||
a.jnz(loc_14014DF48);
|
||||
a.jmp(0x14014DFA2);
|
||||
|
||||
a.bind(loc_14014DF48);
|
||||
a.jmp(0x14014DF48);
|
||||
});
|
||||
|
||||
void pm_weapon_use_ammo_stub(game::playerState_s* ps, game::Weapon weapon,
|
||||
bool is_alternate, int amount, game::PlayerHandIndex hand)
|
||||
{
|
||||
if (!dvars::player_sustainAmmo->current.enabled)
|
||||
{
|
||||
pm_weapon_use_ammo_hook.invoke<void>(ps, weapon, is_alternate, amount, hand);
|
||||
}
|
||||
}
|
||||
|
||||
const auto client_end_frame_stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.push(rax);
|
||||
|
||||
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&dvars::g_gravity)));
|
||||
a.mov(eax, dword_ptr(rax, 0x10));
|
||||
a.mov(word_ptr(rbx, 0x36), ax);
|
||||
|
||||
a.pop(rax);
|
||||
|
||||
// Game code hook skipped
|
||||
a.mov(eax, dword_ptr(rbx, 0x5084));
|
||||
a.mov(rdi, rcx);
|
||||
|
||||
a.jmp(0x1402D5A6A);
|
||||
});
|
||||
|
||||
const auto client_think_real_stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.push(rax);
|
||||
|
||||
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&dvars::g_speed)));
|
||||
a.mov(eax, dword_ptr(rax, 0x10));
|
||||
a.mov(word_ptr(rbx, 0x38), ax);
|
||||
|
||||
a.pop(rax);
|
||||
|
||||
// Game code hook skipped
|
||||
a.movzx(eax, word_ptr(rbx, 0x3C));
|
||||
a.add(eax, dword_ptr(rbx, 0x48));
|
||||
|
||||
a.jmp(0x1402D6A9C);
|
||||
});
|
||||
|
||||
const auto jump_push_off_ladder = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.push(rax);
|
||||
|
||||
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&dvars::jump_ladderPushVel)));
|
||||
a.mulss(xmm7, dword_ptr(rax, 0x10));
|
||||
a.mulss(xmm6, dword_ptr(rax, 0x10));
|
||||
|
||||
a.pop(rax);
|
||||
|
||||
a.jmp(0x1401358C3);
|
||||
});
|
||||
|
||||
void jump_start_stub(game::pmove_t* pm, game::pml_t* pml, float /*height*/)
|
||||
{
|
||||
utils::hook::invoke<void>(0x140135A90, pm, pml, dvars::jump_height->current.value);
|
||||
}
|
||||
|
||||
void pm_player_trace_stub(game::pmove_t* pm, game::trace_t* results, const float* start,
|
||||
const float* end, const game::Bounds* bounds, int pass_entity_num, int content_mask)
|
||||
{
|
||||
utils::hook::invoke<void>(0x14014A420, pm, results, start, end, bounds, pass_entity_num, content_mask);
|
||||
|
||||
if (dvars::g_elevators->current.enabled)
|
||||
{
|
||||
results->startsolid = false;
|
||||
}
|
||||
}
|
||||
|
||||
void pm_trace_stub(const game::pmove_t* pm, game::trace_t* results, const float* start,
|
||||
const float* end, const game::Bounds* bounds, int pass_entity_num, int content_mask)
|
||||
{
|
||||
utils::hook::invoke<void>(0x14014A610, pm, results, start, end, bounds, pass_entity_num, content_mask);
|
||||
|
||||
if (dvars::g_elevators->current.enabled)
|
||||
{
|
||||
results->allsolid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
dvars::player_sustainAmmo = game::Dvar_RegisterBool("player_sustainAmmo", false,
|
||||
game::DVAR_FLAG_REPLICATED, "Firing weapon will not decrease clip ammo");
|
||||
pm_weapon_use_ammo_hook.create(SELECT_VALUE(0x1403DD050, 0x140162B20), &pm_weapon_use_ammo_stub);
|
||||
|
||||
if (game::environment::is_sp()) return;
|
||||
|
||||
// Implement player ejection dvar
|
||||
dvars::g_playerEjection = game::Dvar_RegisterBool("g_playerEjection", true, game::DVAR_FLAG_REPLICATED,
|
||||
"Flag whether player ejection is on or off");
|
||||
utils::hook::call(0x1402D5E4A, stuck_in_client_stub);
|
||||
|
||||
// Implement player collision dvar
|
||||
dvars::g_playerCollision = game::Dvar_RegisterBool("g_playerCollision", true, game::DVAR_FLAG_REPLICATED,
|
||||
"Flag whether player collision is on or off");
|
||||
utils::hook::call(0x1404563DA, cm_transformed_capsule_trace_stub); // SV_ClipMoveToEntity
|
||||
utils::hook::call(0x1401F7F8F, cm_transformed_capsule_trace_stub); // CG_ClipMoveToEntity
|
||||
|
||||
// Implement bouncing dvar
|
||||
utils::hook::jump(0x14014DF91, pm_bouncing_stub_mp, true);
|
||||
dvars::pm_bouncing = game::Dvar_RegisterBool("pm_bouncing", false,
|
||||
game::DVAR_FLAG_REPLICATED, "Enable bouncing");
|
||||
|
||||
// Change jump_slowdownEnable dvar flags to just "replicated"
|
||||
utils::hook::set<uint8_t>(0x140135992, game::DVAR_FLAG_REPLICATED);
|
||||
|
||||
// Choosing the following min/max because the game would truncate larger values
|
||||
dvars::g_gravity = game::Dvar_RegisterInt("g_gravity", 800, std::numeric_limits<short>::min(),
|
||||
std::numeric_limits<short>::max(), game::DVAR_FLAG_REPLICATED, "Gravity in inches per second per second");
|
||||
utils::hook::jump(0x1402D5A5D, client_end_frame_stub, true);
|
||||
utils::hook::nop(0x1402D5A69, 1); // Nop skipped opcode
|
||||
|
||||
// Choosing the following min/max because the game would truncate larger values
|
||||
dvars::g_speed = game::Dvar_RegisterInt("g_speed", 190,
|
||||
std::numeric_limits<short>::min(), std::numeric_limits<short>::max(), game::DVAR_FLAG_REPLICATED, "Player speed");
|
||||
utils::hook::jump(0x1402D6A8C, client_think_real_stub, true);
|
||||
utils::hook::nop(0x1402D6A98, 4); // Nop skipped opcodes
|
||||
|
||||
dvars::jump_height = game::Dvar_RegisterFloat("jump_height", 39.0f,
|
||||
0.0f, 1000.0f, game::DVAR_FLAG_REPLICATED, "The maximum height of a player's jump");
|
||||
utils::hook::call(0x1401352FF, jump_start_stub);
|
||||
|
||||
dvars::jump_ladderPushVel = game::Dvar_RegisterFloat("jump_ladderPushVel", 128.0f,
|
||||
0.0f, 1024.0f, game::DVAR_FLAG_REPLICATED, "The velocity of a jump off of a ladder");
|
||||
utils::hook::jump(0x1401358B3, jump_push_off_ladder, true);
|
||||
utils::hook::nop(0x1401358BF, 4); // Nop skipped opcodes
|
||||
|
||||
dvars::g_elevators = game::Dvar_RegisterBool("g_elevators", false,
|
||||
game::DVAR_FLAG_REPLICATED, "Enable elevators");
|
||||
utils::hook::call(0x140146134, pm_player_trace_stub);
|
||||
utils::hook::call(0x14014619B, pm_player_trace_stub);
|
||||
|
||||
// Allow player to stand from prone/ducked
|
||||
utils::hook::call(0x140142AF6, pm_trace_stub);
|
||||
utils::hook::call(0x140142A1B, pm_trace_stub);
|
||||
utils::hook::call(0x14014298D, pm_trace_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gameplay::component)
|
339
src/client/component/gsc/script_error.cpp
Normal file
339
src/client/component/gsc/script_error.cpp
Normal file
@ -0,0 +1,339 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "script_extension.hpp"
|
||||
#include "script_error.hpp"
|
||||
|
||||
#include "component/scripting.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
using namespace utils::string;
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour scr_emit_function_hook;
|
||||
|
||||
unsigned int current_filename = 0;
|
||||
|
||||
std::string unknown_function_error;
|
||||
|
||||
// Array count confirmed at 0x1409BE0D0
|
||||
std::array<const char*, 27> var_typename =
|
||||
{
|
||||
"undefined",
|
||||
"object",
|
||||
"string",
|
||||
"localized string",
|
||||
"vector",
|
||||
"float",
|
||||
"int",
|
||||
"codepos",
|
||||
"precodepos",
|
||||
"function",
|
||||
"builtin function",
|
||||
"builtin method",
|
||||
"stack",
|
||||
"animation",
|
||||
"pre animation",
|
||||
"thread",
|
||||
"thread",
|
||||
"thread",
|
||||
"thread",
|
||||
"struct",
|
||||
"removed entity",
|
||||
"entity",
|
||||
"array",
|
||||
"removed thread",
|
||||
"<free>",
|
||||
"thread list",
|
||||
"endon list",
|
||||
};
|
||||
|
||||
void scr_emit_function_stub(unsigned int filename, unsigned int thread_name, char* code_pos)
|
||||
{
|
||||
current_filename = filename;
|
||||
scr_emit_function_hook.invoke<void>(filename, thread_name, code_pos);
|
||||
}
|
||||
|
||||
std::string get_filename_name()
|
||||
{
|
||||
const auto filename_str = game::SL_ConvertToString(static_cast<game::scr_string_t>(current_filename));
|
||||
const auto id = std::atoi(filename_str);
|
||||
if (!id)
|
||||
{
|
||||
return filename_str;
|
||||
}
|
||||
|
||||
return scripting::get_token(id);
|
||||
}
|
||||
|
||||
void get_unknown_function_error(const char* code_pos)
|
||||
{
|
||||
const auto function = find_function(code_pos);
|
||||
if (function.has_value())
|
||||
{
|
||||
const auto& pos = function.value();
|
||||
unknown_function_error = std::format(
|
||||
"while processing function '{}' in script '{}':\nunknown script '{}'", pos.first, pos.second, scripting::current_file
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
unknown_function_error = std::format("unknown script '{}'", scripting::current_file);
|
||||
}
|
||||
}
|
||||
|
||||
void get_unknown_function_error(unsigned int thread_name)
|
||||
{
|
||||
const auto filename = get_filename_name();
|
||||
const auto name = scripting::get_token(thread_name);
|
||||
|
||||
unknown_function_error = std::format(
|
||||
"while processing script '{}':\nunknown function '{}::{}'", scripting::current_file, filename, name
|
||||
);
|
||||
}
|
||||
|
||||
void compile_error_stub(const char* code_pos, [[maybe_unused]] const char* msg)
|
||||
{
|
||||
get_unknown_function_error(code_pos);
|
||||
game::Com_Error(game::ERR_DROP, "script link error\n%s", unknown_function_error.data());
|
||||
}
|
||||
|
||||
unsigned int find_variable_stub(unsigned int parent_id, unsigned int thread_name)
|
||||
{
|
||||
const auto res = game::FindVariable(parent_id, thread_name);
|
||||
if (!res)
|
||||
{
|
||||
get_unknown_function_error(thread_name);
|
||||
game::Com_Error(game::ERR_DROP, "script link error\n%s", unknown_function_error.data());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
unsigned int scr_get_object(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_POINTER)
|
||||
{
|
||||
return value->u.pointerValue;
|
||||
}
|
||||
|
||||
scr_error(va("Type %s is not an object", var_typename[value->type]));
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int scr_get_const_string(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (game::Scr_CastString(value))
|
||||
{
|
||||
assert(value->type == game::VAR_STRING);
|
||||
return value->u.stringValue;
|
||||
}
|
||||
|
||||
game::Scr_ErrorInternal();
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int scr_get_const_istring(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_ISTRING)
|
||||
{
|
||||
return value->u.stringValue;
|
||||
}
|
||||
|
||||
scr_error(va("Type %s is not a localized string", var_typename[value->type]));
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void scr_validate_localized_string_ref(int parm_index, const char* token, int token_len)
|
||||
{
|
||||
assert(token);
|
||||
assert(token_len >= 0);
|
||||
|
||||
if (token_len < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto char_iter = 0; char_iter < token_len; ++char_iter)
|
||||
{
|
||||
if (!std::isalnum(static_cast<unsigned char>(token[char_iter])) && token[char_iter] != '_')
|
||||
{
|
||||
scr_error(va("Illegal localized string reference: %s must contain only alpha-numeric characters and underscores", token));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void scr_get_vector(unsigned int index, float* vector_value)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_VECTOR)
|
||||
{
|
||||
std::memcpy(vector_value, value->u.vectorValue, sizeof(std::float_t[3]));
|
||||
return;
|
||||
}
|
||||
|
||||
scr_error(va("Type %s is not a vector", var_typename[value->type]));
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
}
|
||||
|
||||
int scr_get_int(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_INTEGER)
|
||||
{
|
||||
return value->u.intValue;
|
||||
}
|
||||
|
||||
scr_error(va("Type %s is not an int", var_typename[value->type]));
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
float scr_get_float(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_FLOAT)
|
||||
{
|
||||
return value->u.floatValue;
|
||||
}
|
||||
|
||||
if (value->type == game::VAR_INTEGER)
|
||||
{
|
||||
return static_cast<float>(value->u.intValue);
|
||||
}
|
||||
|
||||
scr_error(va("Type %s is not a float", var_typename[value->type]));
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
int scr_get_pointer_type(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
if ((game::scr_VmPub->top - index)->type == game::VAR_POINTER)
|
||||
{
|
||||
return static_cast<int>(game::GetObjectType((game::scr_VmPub->top - index)->u.uintValue));
|
||||
}
|
||||
|
||||
scr_error(va("Type %s is not an object", var_typename[(game::scr_VmPub->top - index)->type]));
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int scr_get_type(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
return (game::scr_VmPub->top - index)->type;
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* scr_get_type_name(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
return var_typename[(game::scr_VmPub->top - index)->type];
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> find_function(const char* pos)
|
||||
{
|
||||
for (const auto& file : scripting::script_function_table_sort)
|
||||
{
|
||||
for (auto i = file.second.begin(); i != file.second.end() && std::next(i) != file.second.end(); ++i)
|
||||
{
|
||||
const auto next = std::next(i);
|
||||
if (pos >= i->second && pos < next->second)
|
||||
{
|
||||
return {std::make_pair(i->first, file.first)};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
class error final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scr_emit_function_hook.create(0x1403ED900, &scr_emit_function_stub);
|
||||
|
||||
utils::hook::call(0x1403ED894, compile_error_stub); // LinkFile
|
||||
utils::hook::call(0x1403ED8E8, compile_error_stub); // LinkFile
|
||||
utils::hook::call(0x1403ED9DB, find_variable_stub); // Scr_EmitFunction
|
||||
|
||||
// Restore basic error messages for commonly used scr functions
|
||||
utils::hook::jump(0x1403F8990, scr_get_object);
|
||||
utils::hook::jump(0x1403F8510, scr_get_const_string);
|
||||
utils::hook::jump(0x1403F82F0, scr_get_const_istring);
|
||||
utils::hook::jump(0x140327870, scr_validate_localized_string_ref);
|
||||
utils::hook::jump(0x1403F8EC0, scr_get_vector);
|
||||
utils::hook::jump(0x1403F88D0, scr_get_int);
|
||||
utils::hook::jump(0x1403F8820, scr_get_float);
|
||||
|
||||
utils::hook::jump(0x1403F8BA0, scr_get_pointer_type);
|
||||
utils::hook::jump(0x1403F8D70, scr_get_type);
|
||||
utils::hook::jump(0x1403F8DE0, scr_get_type_name);
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
scr_emit_function_hook.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::error)
|
6
src/client/component/gsc/script_error.hpp
Normal file
6
src/client/component/gsc/script_error.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::optional<std::pair<std::string, std::string>> find_function(const char* pos);
|
||||
}
|
288
src/client/component/gsc/script_extension.cpp
Normal file
288
src/client/component/gsc/script_extension.cpp
Normal file
@ -0,0 +1,288 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "game/scripting/functions.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
#include "component/console.hpp"
|
||||
#include "component/command.hpp"
|
||||
|
||||
#include "script_error.hpp"
|
||||
#include "script_extension.hpp"
|
||||
#include "script_loading.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::uint16_t function_id_start = 0x2DF;
|
||||
void* func_table[0x1000];
|
||||
|
||||
const game::dvar_t* developer_script = nullptr;
|
||||
|
||||
namespace
|
||||
{
|
||||
#define RVA(ptr) static_cast<std::uint32_t>(reinterpret_cast<std::size_t>(ptr) - 0x140000000)
|
||||
|
||||
struct gsc_error : public std::runtime_error
|
||||
{
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
std::unordered_map<std::uint16_t, game::BuiltinFunction> functions;
|
||||
|
||||
bool force_error_print = false;
|
||||
std::optional<std::string> gsc_error_msg;
|
||||
|
||||
std::unordered_map<std::uint32_t, game::BuiltinFunction> builtin_funcs_overrides;
|
||||
|
||||
utils::hook::detour scr_register_function_hook;
|
||||
|
||||
unsigned int scr_get_function_stub(const char** p_name, int* type)
|
||||
{
|
||||
const auto result = utils::hook::invoke<unsigned int>(0x1403318B0, p_name, type);
|
||||
|
||||
for (const auto& [name, func] : functions)
|
||||
{
|
||||
game::Scr_RegisterFunction(func, 0, name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void execute_custom_function(game::BuiltinFunction function)
|
||||
{
|
||||
auto error = false;
|
||||
|
||||
try
|
||||
{
|
||||
function();
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
error = true;
|
||||
force_error_print = true;
|
||||
gsc_error_msg = ex.what();
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
game::Scr_ErrorInternal();
|
||||
}
|
||||
}
|
||||
|
||||
void vm_call_builtin_function(const std::uint32_t index)
|
||||
{
|
||||
const auto func = reinterpret_cast<game::BuiltinFunction>(scripting::get_function_by_index(index));
|
||||
|
||||
const auto custom = functions.contains(static_cast<std::uint16_t>(index));
|
||||
if (!custom)
|
||||
{
|
||||
func();
|
||||
}
|
||||
else
|
||||
{
|
||||
execute_custom_function(func);
|
||||
}
|
||||
}
|
||||
|
||||
void builtin_call_error(const std::string& error)
|
||||
{
|
||||
const auto pos = game::scr_function_stack->pos;
|
||||
const auto function_id = *reinterpret_cast<std::uint16_t*>(reinterpret_cast<std::size_t>(pos - 2));
|
||||
|
||||
if (function_id > 0x1000)
|
||||
{
|
||||
console::warn("in call to builtin method \"%s\"%s", gsc_ctx->meth_name(function_id).data(), error.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("in call to builtin function \"%s\"%s", gsc_ctx->func_name(function_id).data(), error.data());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> get_opcode_name(const std::uint8_t opcode)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto index = gsc_ctx->opcode_enum(opcode);
|
||||
return {gsc_ctx->opcode_name(index)};
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void print_callstack()
|
||||
{
|
||||
for (auto frame = game::scr_VmPub->function_frame; frame != game::scr_VmPub->function_frame_start; --frame)
|
||||
{
|
||||
const auto pos = frame == game::scr_VmPub->function_frame ? game::scr_function_stack->pos : frame->fs.pos;
|
||||
const auto function = find_function(frame->fs.pos);
|
||||
|
||||
if (function.has_value())
|
||||
{
|
||||
console::warn("\tat function \"%s\" in file \"%s.gsc\"\n", function.value().first.data(), function.value().second.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("\tat unknown location %p\n", pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vm_error_stub(int mark_pos)
|
||||
{
|
||||
if (!developer_script->current.enabled && !force_error_print)
|
||||
{
|
||||
utils::hook::invoke<void>(0x1404B6790, mark_pos);
|
||||
return;
|
||||
}
|
||||
|
||||
console::warn("******* script runtime error ********\n");
|
||||
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(0x148806768);
|
||||
|
||||
const std::string error = gsc_error_msg.has_value() ? std::format(": {}", gsc_error_msg.value()) : std::string();
|
||||
|
||||
if ((opcode_id >= 0x1A && opcode_id <= 0x20) || (opcode_id >= 0xA9 && opcode_id <= 0xAF))
|
||||
{
|
||||
builtin_call_error(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto opcode = get_opcode_name(opcode_id);
|
||||
if (opcode.has_value())
|
||||
{
|
||||
console::warn("while processing instruction %s%s\n", opcode.value().data(), error.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("while processing instruction 0x%X%s\n", opcode_id, error.data());
|
||||
}
|
||||
}
|
||||
|
||||
force_error_print = false;
|
||||
gsc_error_msg = {};
|
||||
|
||||
print_callstack();
|
||||
console::warn("************************************\n");
|
||||
utils::hook::invoke<void>(0x1404B6790, mark_pos);
|
||||
}
|
||||
|
||||
void scr_register_function_stub(void* func, int type, unsigned int name)
|
||||
{
|
||||
if (const auto itr = builtin_funcs_overrides.find(name); itr != builtin_funcs_overrides.end())
|
||||
{
|
||||
func = itr->second;
|
||||
}
|
||||
|
||||
scr_register_function_hook.invoke<void>(func, type, name);
|
||||
}
|
||||
|
||||
void scr_print()
|
||||
{
|
||||
for (auto i = 0u; i < game::Scr_GetNumParam(); ++i)
|
||||
{
|
||||
console::info("%s", game::Scr_GetString(i));
|
||||
}
|
||||
}
|
||||
|
||||
void scr_print_ln()
|
||||
{
|
||||
for (auto i = 0u; i < game::Scr_GetNumParam(); ++i)
|
||||
{
|
||||
console::info("%s", game::Scr_GetString(i));
|
||||
}
|
||||
|
||||
console::info("\n");
|
||||
}
|
||||
|
||||
void assert_cmd()
|
||||
{
|
||||
if (!game::Scr_GetInt(0))
|
||||
{
|
||||
scr_error("Assert fail");
|
||||
}
|
||||
}
|
||||
|
||||
void assert_ex_cmd()
|
||||
{
|
||||
if (!game::Scr_GetInt(0))
|
||||
{
|
||||
scr_error(utils::string::va("Assert fail: %s", game::Scr_GetString(1)));
|
||||
}
|
||||
}
|
||||
|
||||
void assert_msg_cmd()
|
||||
{
|
||||
scr_error(utils::string::va("Assert fail: %s", game::Scr_GetString(0)));
|
||||
}
|
||||
}
|
||||
|
||||
void scr_error(const char* error)
|
||||
{
|
||||
force_error_print = true;
|
||||
gsc_error_msg = error;
|
||||
|
||||
game::Scr_ErrorInternal();
|
||||
}
|
||||
|
||||
void override_function(const std::string& name, game::BuiltinFunction func)
|
||||
{
|
||||
const auto id = gsc_ctx->func_id(name);
|
||||
builtin_funcs_overrides.emplace(id, func);
|
||||
}
|
||||
|
||||
void add_function(const std::string& name, game::BuiltinFunction function)
|
||||
{
|
||||
++function_id_start;
|
||||
functions[function_id_start] = function;
|
||||
gsc_ctx->func_add(name, function_id_start);
|
||||
}
|
||||
|
||||
class extension final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
scr_register_function_hook.create(game::Scr_RegisterFunction, &scr_register_function_stub);
|
||||
|
||||
override_function("print", &scr_print);
|
||||
override_function("println", &scr_print_ln);
|
||||
|
||||
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403115BC, 0x1403EDAEC), 0x1000); // Scr_RegisterFunction
|
||||
|
||||
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403115C2 + 4, 0x1403EDAF2 + 4), RVA(&func_table)); // Scr_RegisterFunction
|
||||
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x14031F097 + 3, 0x1403FB7F7 + 3), RVA(&func_table)); // VM_Execute_0
|
||||
utils::hook::inject(SELECT_VALUE(0x140311964 + 3, 0x1403EDEE4 + 3), &func_table); // Scr_BeginLoadScripts
|
||||
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE, "Enable developer script comments");
|
||||
|
||||
utils::hook::nop(0x1403FB7F7 + 5, 2);
|
||||
utils::hook::call(0x1403FB7F7, vm_call_builtin_function);
|
||||
|
||||
utils::hook::call(0x1403FCAB0, vm_error_stub); // LargeLocalResetToMark
|
||||
|
||||
utils::hook::call(0x1403EDF1F, scr_get_function_stub);
|
||||
|
||||
override_function("assert", &assert_cmd);
|
||||
override_function("assertex", &assert_ex_cmd);
|
||||
override_function("assertmsg", &assert_msg_cmd);
|
||||
|
||||
add_function("executecommand", []
|
||||
{
|
||||
const auto* cmd = game::Scr_GetString(0);
|
||||
command::execute(cmd);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::extension)
|
12
src/client/component/gsc/script_extension.hpp
Normal file
12
src/client/component/gsc/script_extension.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
extern void* func_table[0x1000];
|
||||
|
||||
extern const game::dvar_t* developer_script;
|
||||
|
||||
void scr_error(const char* error);
|
||||
void override_function(const std::string& name, game::BuiltinFunction func);
|
||||
void add_function(const std::string& name, game::BuiltinFunction function);
|
||||
}
|
396
src/client/component/gsc/script_loading.cpp
Normal file
396
src/client/component/gsc/script_loading.cpp
Normal file
@ -0,0 +1,396 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/compression.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/memory.hpp>
|
||||
|
||||
#include "component/filesystem.hpp"
|
||||
#include "component/console.hpp"
|
||||
#include "component/scripting.hpp"
|
||||
#include "component/fastfiles.hpp"
|
||||
|
||||
#include "script_loading.hpp"
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::unique_ptr<xsk::gsc::s1_pc::context> gsc_ctx;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<std::string, unsigned int> main_handles;
|
||||
std::unordered_map<std::string, unsigned int> init_handles;
|
||||
|
||||
std::unordered_map<std::string, game::ScriptFile*> loaded_scripts;
|
||||
utils::memory::allocator script_allocator;
|
||||
|
||||
const game::dvar_t* developer_script;
|
||||
|
||||
void clear()
|
||||
{
|
||||
main_handles.clear();
|
||||
init_handles.clear();
|
||||
loaded_scripts.clear();
|
||||
script_allocator.clear();
|
||||
}
|
||||
|
||||
bool read_raw_script_file(const std::string& name, std::string* data)
|
||||
{
|
||||
if (filesystem::read_file(name, data))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// This will prevent 'fake' GSC raw files from being compiled.
|
||||
// They are parsed by the game's own parser later as they are special files.
|
||||
if (name.starts_with("maps/createfx") || name.starts_with("maps/createart") ||
|
||||
(name.starts_with("maps/mp") && name.ends_with("_fx.gsc")))
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
console::info("Refusing to compile rawfile '%s\n", name.data());
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* name_str = name.data();
|
||||
if (game::DB_XAssetExists(game::ASSET_TYPE_RAWFILE, name_str) &&
|
||||
!game::DB_IsXAssetDefault(game::ASSET_TYPE_RAWFILE, name_str))
|
||||
{
|
||||
const auto asset = game::DB_FindXAssetHeader(game::ASSET_TYPE_RAWFILE, name.data(), false);
|
||||
const auto len = game::DB_GetRawFileLen(asset.rawfile);
|
||||
data->resize(len);
|
||||
game::DB_GetRawBuffer(asset.rawfile, data->data(), len);
|
||||
if (len > 0)
|
||||
{
|
||||
data->pop_back();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
game::ScriptFile* load_custom_script(const char* file_name, const std::string& real_name)
|
||||
{
|
||||
if (const auto itr = loaded_scripts.find(real_name); itr != loaded_scripts.end())
|
||||
{
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto& compiler = gsc_ctx->compiler();
|
||||
auto& assembler = gsc_ctx->assembler();
|
||||
|
||||
std::string source_buffer{};
|
||||
if (!read_raw_script_file(real_name + ".gsc", &source_buffer))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> data;
|
||||
data.assign(source_buffer.begin(), source_buffer.end());
|
||||
|
||||
const auto assembly_ptr = compiler.compile(real_name, data);
|
||||
// Pair of two buffers. First is the byte code and second is the stack
|
||||
const auto output_script = assembler.assemble(*assembly_ptr);
|
||||
|
||||
const auto script_file_ptr = static_cast<game::ScriptFile*>(script_allocator.allocate(sizeof(game::ScriptFile)));
|
||||
script_file_ptr->name = file_name;
|
||||
|
||||
script_file_ptr->bytecodeLen = static_cast<int>(std::get<0>(output_script).size);
|
||||
script_file_ptr->len = static_cast<int>(std::get<1>(output_script).size);
|
||||
|
||||
const auto byte_code_size = static_cast<std::uint32_t>(std::get<0>(output_script).size + 1);
|
||||
const auto stack_size = static_cast<std::uint32_t>(std::get<1>(output_script).size + 1);
|
||||
|
||||
script_file_ptr->buffer = static_cast<char*>(script_allocator.allocate(stack_size));
|
||||
std::memcpy(const_cast<char*>(script_file_ptr->buffer), std::get<1>(output_script).data, std::get<1>(output_script).size);
|
||||
|
||||
script_file_ptr->bytecode = static_cast<std::uint8_t*>(game::PMem_AllocFromSource_NoDebug(byte_code_size, 4, 1, 5));
|
||||
std::memcpy(script_file_ptr->bytecode, std::get<0>(output_script).data, std::get<0>(output_script).size);
|
||||
|
||||
script_file_ptr->compressedLen = 0;
|
||||
|
||||
loaded_scripts[real_name] = script_file_ptr;
|
||||
|
||||
return script_file_ptr;
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
console::error("*********** script compile error *************\n");
|
||||
console::error("failed to compile '%s':\n%s", real_name.data(), ex.what());
|
||||
console::error("**********************************************\n");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_script_file_name(const std::string& name)
|
||||
{
|
||||
const auto id = gsc_ctx->token_id(name);
|
||||
if (!id)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
return std::to_string(id);
|
||||
}
|
||||
|
||||
std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>> read_compiled_script_file(const std::string& name, const std::string& real_name)
|
||||
{
|
||||
const auto* script_file = game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name.data(), false).scriptfile;
|
||||
if (!script_file)
|
||||
{
|
||||
throw std::runtime_error(std::format("Could not load scriptfile '{}'", real_name));
|
||||
}
|
||||
|
||||
console::info("Decompiling scriptfile '%s'\n", real_name.data());
|
||||
|
||||
const auto len = script_file->compressedLen;
|
||||
const std::string stack{script_file->buffer, static_cast<std::uint32_t>(len)};
|
||||
|
||||
const auto decompressed_stack = utils::compression::zlib::decompress(stack);
|
||||
|
||||
std::vector<std::uint8_t> stack_data;
|
||||
stack_data.assign(decompressed_stack.begin(), decompressed_stack.end());
|
||||
|
||||
return {{script_file->bytecode, static_cast<std::uint32_t>(script_file->bytecodeLen)}, stack_data};
|
||||
}
|
||||
|
||||
void load_script(const std::string& name)
|
||||
{
|
||||
if (!game::Scr_LoadScript(name.data()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto main_handle = game::Scr_GetFunctionHandle(name.data(), gsc_ctx->token_id("main"));
|
||||
const auto init_handle = game::Scr_GetFunctionHandle(name.data(), gsc_ctx->token_id("init"));
|
||||
|
||||
if (main_handle)
|
||||
{
|
||||
console::info("Loaded '%s::main'\n", name.data());
|
||||
main_handles[name] = main_handle;
|
||||
}
|
||||
|
||||
if (init_handle)
|
||||
{
|
||||
console::info("Loaded '%s::init'\n", name.data());
|
||||
init_handles[name] = init_handle;
|
||||
}
|
||||
}
|
||||
|
||||
void load_scripts(const std::filesystem::path& root_dir)
|
||||
{
|
||||
const std::filesystem::path script_dir = root_dir / "scripts";
|
||||
if (!utils::io::directory_exists(script_dir.generic_string()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scripts = utils::io::list_files(script_dir.generic_string());
|
||||
for (const auto& script : scripts)
|
||||
{
|
||||
if (!script.ends_with(".gsc"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::filesystem::path path(script);
|
||||
const auto relative = path.lexically_relative(root_dir).generic_string();
|
||||
const auto base_name = relative.substr(0, relative.size() - 4);
|
||||
|
||||
load_script(base_name);
|
||||
}
|
||||
}
|
||||
|
||||
int db_is_x_asset_default(game::XAssetType type, const char* name)
|
||||
{
|
||||
if (loaded_scripts.contains(name))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return game::DB_IsXAssetDefault(type, name);
|
||||
}
|
||||
|
||||
void gscr_post_load_scripts_stub()
|
||||
{
|
||||
utils::hook::invoke<void>(0x140323F20);
|
||||
|
||||
if (game::VirtualLobby_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
fastfiles::enum_assets(game::ASSET_TYPE_RAWFILE, [](const game::XAssetHeader header)
|
||||
{
|
||||
const std::string name = header.rawfile->name;
|
||||
|
||||
if (name.ends_with(".gsc") && name.starts_with("scripts/"))
|
||||
{
|
||||
// Remove .gsc from the file name as it will be re-added by the game later
|
||||
const auto base_name = name.substr(0, name.size() - 4);
|
||||
load_script(base_name);
|
||||
}
|
||||
}, true);
|
||||
|
||||
for (const auto& path : filesystem::get_search_paths())
|
||||
{
|
||||
load_scripts(path);
|
||||
}
|
||||
}
|
||||
|
||||
void db_get_raw_buffer_stub(const game::RawFile* rawfile, char* buf, const int size)
|
||||
{
|
||||
if (rawfile->len > 0 && rawfile->compressedLen == 0)
|
||||
{
|
||||
std::memset(buf, 0, size);
|
||||
std::memcpy(buf, rawfile->buffer, std::min(rawfile->len, size));
|
||||
return;
|
||||
}
|
||||
|
||||
game::DB_GetRawBuffer(rawfile, buf, size);
|
||||
}
|
||||
|
||||
void g_load_structs_stub()
|
||||
{
|
||||
if (!game::VirtualLobby_Loaded())
|
||||
{
|
||||
for (auto& function_handle : main_handles)
|
||||
{
|
||||
console::info("Executing '%s::main'\n", function_handle.first.data());
|
||||
const auto thread = game::Scr_ExecThread(static_cast<int>(function_handle.second), 0);
|
||||
game::RemoveRefToObject(thread);
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::invoke<void>(0x1403380D0);
|
||||
}
|
||||
|
||||
void scr_load_level_stub()
|
||||
{
|
||||
utils::hook::invoke<void>(0x140325B90);
|
||||
|
||||
if (game::VirtualLobby_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& function_handle : init_handles)
|
||||
{
|
||||
console::info("Executing '%s::init'\n", function_handle.first.data());
|
||||
const auto thread = game::Scr_ExecThread(static_cast<int>(function_handle.second), 0);
|
||||
game::RemoveRefToObject(thread);
|
||||
}
|
||||
}
|
||||
|
||||
void scr_begin_load_scripts_stub()
|
||||
{
|
||||
const auto comp_mode = developer_script->current.enabled ?
|
||||
xsk::gsc::build::dev :
|
||||
xsk::gsc::build::prod;
|
||||
|
||||
gsc_ctx->init(comp_mode, []([[maybe_unused]] auto const* ctx, const auto& included_path) -> std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>>
|
||||
{
|
||||
const auto script_name = std::filesystem::path(included_path).replace_extension().string();
|
||||
|
||||
std::string file_buffer;
|
||||
if (!read_raw_script_file(included_path, &file_buffer) || file_buffer.empty())
|
||||
{
|
||||
const auto name = get_script_file_name(script_name);
|
||||
if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, name.data()))
|
||||
{
|
||||
return read_compiled_script_file(name, script_name);
|
||||
}
|
||||
|
||||
throw std::runtime_error(std::format("Could not load gsc file '{}'", script_name));
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> script_data;
|
||||
script_data.assign(file_buffer.begin(), file_buffer.end());
|
||||
|
||||
return {{}, script_data};
|
||||
});
|
||||
|
||||
utils::hook::invoke<void>(SELECT_VALUE(0x1403118E0, 0x1403EDE60));
|
||||
}
|
||||
|
||||
void scr_end_load_scripts_stub()
|
||||
{
|
||||
// Cleanup the compiler
|
||||
gsc_ctx->cleanup();
|
||||
|
||||
utils::hook::invoke<void>(SELECT_VALUE(0x140243780, 0x1403EDF90));
|
||||
}
|
||||
}
|
||||
|
||||
game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default)
|
||||
{
|
||||
std::string real_name = name;
|
||||
const auto id = static_cast<std::uint16_t>(std::strtol(name, nullptr, 10));
|
||||
if (id)
|
||||
{
|
||||
real_name = gsc_ctx->token_name(id);
|
||||
}
|
||||
|
||||
auto* script = load_custom_script(name, real_name);
|
||||
if (script)
|
||||
{
|
||||
return script;
|
||||
}
|
||||
|
||||
return game::DB_FindXAssetHeader(type, name, allow_create_default).scriptfile;
|
||||
}
|
||||
|
||||
class loading final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_load() override
|
||||
{
|
||||
gsc_ctx = std::make_unique<xsk::gsc::s1_pc::context>();
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
// Load our scripts with an uncompressed stack
|
||||
utils::hook::call(SELECT_VALUE(0x14031ABB0, 0x1403F7380), db_get_raw_buffer_stub);
|
||||
|
||||
utils::hook::call(SELECT_VALUE(0x1403309E9, 0x1403309E9), scr_begin_load_scripts_stub); // GScr_LoadScripts
|
||||
utils::hook::call(SELECT_VALUE(0x14023DA84, 0x140330B9C), scr_end_load_scripts_stub); // GScr_LoadScripts
|
||||
|
||||
developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE, "Enable developer script comments");
|
||||
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ProcessScript
|
||||
utils::hook::call(0x1403F7317, find_script);
|
||||
utils::hook::call(0x1403F7327, db_is_x_asset_default);
|
||||
|
||||
// GScr_LoadScripts
|
||||
utils::hook::call(0x140330B97, gscr_post_load_scripts_stub);
|
||||
|
||||
// Exec script handles
|
||||
utils::hook::call(0x1402F71AE, g_load_structs_stub);
|
||||
utils::hook::call(0x1402F71C7, scr_load_level_stub);
|
||||
|
||||
scripting::on_shutdown([](int free_scripts)
|
||||
{
|
||||
if (free_scripts)
|
||||
{
|
||||
clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::loading)
|
9
src/client/component/gsc/script_loading.hpp
Normal file
9
src/client/component/gsc/script_loading.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <xsk/gsc/engine/s1_pc.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
extern std::unique_ptr<xsk::gsc::s1_pc::context> gsc_ctx;
|
||||
|
||||
game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default);
|
||||
}
|
59
src/client/component/input.cpp
Normal file
59
src/client/component/input.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "game_console.hpp"
|
||||
#include "server_list.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace input
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour cl_char_event_hook;
|
||||
utils::hook::detour cl_key_event_hook;
|
||||
|
||||
void cl_char_event_stub(const int local_client_num, const int key)
|
||||
{
|
||||
if (!game_console::console_char_event(local_client_num, key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cl_char_event_hook.invoke<void>(local_client_num, key);
|
||||
}
|
||||
|
||||
void cl_key_event_stub(const int local_client_num, const int key, const signed int down, const int arg4)
|
||||
{
|
||||
if (!game_console::console_key_event(local_client_num, key, down))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (game::environment::is_mp() && !server_list::sl_key_event(key, down))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cl_key_event_hook.invoke<void>(local_client_num, key, down, arg4);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cl_char_event_hook.create(SELECT_VALUE(0x14013E7B0, 0x140206690), cl_char_event_stub);
|
||||
cl_key_event_hook.create(SELECT_VALUE(0x14013EB20, 0x140206900), cl_key_event_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(input::component)
|
52
src/client/component/localized_strings.cpp
Normal file
52
src/client/component/localized_strings.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace localized_strings
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour seh_string_ed_get_string_hook;
|
||||
|
||||
using localized_map = std::unordered_map<std::string, std::string>;
|
||||
utils::concurrency::container<localized_map> localized_overrides;
|
||||
|
||||
const char* seh_string_ed_get_string(const char* reference)
|
||||
{
|
||||
return localized_overrides.access<const char*>([&](const localized_map& map)
|
||||
{
|
||||
const auto entry = map.find(reference);
|
||||
if (entry != map.end())
|
||||
{
|
||||
return utils::string::va("%s", entry->second.data());
|
||||
}
|
||||
|
||||
return seh_string_ed_get_string_hook.invoke<const char*>(reference);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void override(const std::string& key, const std::string& value)
|
||||
{
|
||||
localized_overrides.access([&](localized_map& map)
|
||||
{
|
||||
map[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// Change some localized strings
|
||||
seh_string_ed_get_string_hook.create(SELECT_VALUE(0x140339CF0, 0x140474FC0), &seh_string_ed_get_string);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(localized_strings::component)
|
6
src/client/component/localized_strings.hpp
Normal file
6
src/client/component/localized_strings.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace localized_strings
|
||||
{
|
||||
void override(const std::string& key, const std::string& value);
|
||||
}
|
172
src/client/component/logger.cpp
Normal file
172
src/client/component/logger.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "party.hpp"
|
||||
#include "console.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace logger
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour com_error_hook;
|
||||
|
||||
game::dvar_t* logger_dev = nullptr;
|
||||
|
||||
void print_error(const char* msg, ...)
|
||||
{
|
||||
char buffer[2048]{};
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, msg);
|
||||
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
|
||||
va_end(ap);
|
||||
|
||||
console::error("%s", buffer);
|
||||
}
|
||||
|
||||
void print_com_error(int, const char* msg, ...)
|
||||
{
|
||||
char buffer[2048]{};
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, msg);
|
||||
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
|
||||
va_end(ap);
|
||||
|
||||
console::error("%s", buffer);
|
||||
}
|
||||
|
||||
void com_error_stub(const int error, const char* msg, ...)
|
||||
{
|
||||
char buffer[2048]{};
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, msg);
|
||||
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
|
||||
va_end(ap);
|
||||
|
||||
console::error("Error: %s\n", buffer);
|
||||
|
||||
party::clear_sv_motd(); // clear sv_motd on error if it exists
|
||||
|
||||
com_error_hook.invoke<void>(error, "%s", buffer);
|
||||
}
|
||||
|
||||
void print_warning(const char* msg, ...)
|
||||
{
|
||||
char buffer[2048]{};
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, msg);
|
||||
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
|
||||
va_end(ap);
|
||||
|
||||
console::warn("%s", buffer);
|
||||
}
|
||||
|
||||
void print(const char* msg, ...)
|
||||
{
|
||||
char buffer[2048]{};
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, msg);
|
||||
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
|
||||
va_end(ap);
|
||||
|
||||
console::info("%s", buffer);
|
||||
}
|
||||
|
||||
void print_dev(const char* msg, ...)
|
||||
{
|
||||
if (!logger_dev->current.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[2048]{};
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, msg);
|
||||
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
|
||||
va_end(ap);
|
||||
|
||||
console::info("%s", buffer);
|
||||
}
|
||||
|
||||
// nullsub_56
|
||||
void nullsub_56()
|
||||
{
|
||||
utils::hook::call(0x1400D2572, print_warning);
|
||||
utils::hook::call(0x1400D257E, print_warning);
|
||||
utils::hook::call(0x1400D2586, print_warning);
|
||||
utils::hook::call(0x1400D2592, print_warning);
|
||||
|
||||
utils::hook::call(0x1400D2B7D, print_warning);
|
||||
utils::hook::call(0x1400D2B89, print_warning);
|
||||
utils::hook::call(0x1400D2B91, print_warning);
|
||||
utils::hook::call(0x1400D2B9D, print_warning);
|
||||
|
||||
utils::hook::call(0x1400D78ED, print_warning);
|
||||
utils::hook::call(0x1400D78F9, print_warning);
|
||||
utils::hook::call(0x1400D7901, print_warning);
|
||||
utils::hook::call(0x1400D790D, print_warning);
|
||||
|
||||
utils::hook::call(0x1400D84F8, print_warning);
|
||||
utils::hook::call(0x1400D850C, print_warning);
|
||||
|
||||
utils::hook::call(0x1400D8C08, print_warning);
|
||||
utils::hook::call(0x1400D8C28, print_warning);
|
||||
utils::hook::call(0x1400D8C34, print_warning);
|
||||
|
||||
utils::hook::call(0x1400D8CD8, print_warning);
|
||||
utils::hook::call(0x1400D8CF8, print_warning);
|
||||
utils::hook::call(0x1400D8D04, print_warning);
|
||||
utils::hook::call(0x1400D8D1D, print_warning);
|
||||
|
||||
utils::hook::call(0x1400DAE67, print_warning);
|
||||
utils::hook::call(0x1400DB019, print_warning);
|
||||
}
|
||||
|
||||
// sub_1400E7420
|
||||
void sub_1400E7420()
|
||||
{
|
||||
utils::hook::call(0x1400CEC1C, print_warning);
|
||||
utils::hook::call(0x1400D218E, print_warning);
|
||||
utils::hook::call(0x1400D2319, print_warning);
|
||||
utils::hook::call(0x1400D65BC, print_dev);
|
||||
utils::hook::call(0x1400D7C94, print_dev);
|
||||
utils::hook::call(0x1400DAB6A, print_dev);
|
||||
utils::hook::call(0x1400DB9BC, print_dev);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
nullsub_56();
|
||||
sub_1400E7420();
|
||||
|
||||
// Make havok script's print function actually print
|
||||
utils::hook::jump(0x140701A1C, print);
|
||||
}
|
||||
|
||||
if (!game::environment::is_sp())
|
||||
{
|
||||
utils::hook::call(0x1404D8543, print_com_error);
|
||||
}
|
||||
|
||||
com_error_hook.create(game::Com_Error, com_error_stub);
|
||||
|
||||
logger_dev = game::Dvar_RegisterBool("logger_dev", false, game::DVAR_FLAG_SAVED, "Print dev stuff");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(logger::component)
|
60
src/client/component/lui.cpp
Normal file
60
src/client/component/lui.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace lui
|
||||
{
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_mp()) return;
|
||||
|
||||
// Don't show create cod account popup
|
||||
utils::hook::set<uint32_t>(0x1400EAD12, 0);
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Enable development menus (causes issues in sp)
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x1400B4ABC, 0x140109FAC), 1);
|
||||
#endif
|
||||
|
||||
command::add("lui_open", [](const command::params& params)
|
||||
{
|
||||
if (params.size() <= 1)
|
||||
{
|
||||
console::info("usage: lui_open <name>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
game::LUI_OpenMenu(0, params[1], 0, 0, 0);
|
||||
});
|
||||
|
||||
command::add("lui_open_popup", [](const command::params& params)
|
||||
{
|
||||
if (params.size() <= 1)
|
||||
{
|
||||
console::info("usage: lui_open_popup <name>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
game::LUI_OpenMenu(0, params[1], 1, 0, 0);
|
||||
});
|
||||
|
||||
command::add("runMenuScript", [](const command::params& params)
|
||||
{
|
||||
const auto args_str = params.join(1);
|
||||
const auto* args = args_str.data();
|
||||
game::UI_RunMenuScript(0, &args);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(lui::component)
|
306
src/client/component/map_rotation.cpp
Normal file
306
src/client/component/map_rotation.cpp
Normal file
@ -0,0 +1,306 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "map_rotation.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace map_rotation
|
||||
{
|
||||
namespace
|
||||
{
|
||||
rotation_data dedicated_rotation;
|
||||
|
||||
const game::dvar_t* sv_map_rotation;
|
||||
const game::dvar_t* sv_map_rotation_current;
|
||||
const game::dvar_t* sv_random_map_rotation;
|
||||
|
||||
void set_gametype(const std::string& gametype)
|
||||
{
|
||||
assert(!gametype.empty());
|
||||
|
||||
auto* g_gametype = game::Dvar_FindVar("g_gametype");
|
||||
game::Dvar_SetString(g_gametype, gametype.data());
|
||||
}
|
||||
|
||||
void launch_map(const std::string& mapname)
|
||||
{
|
||||
assert(!mapname.empty());
|
||||
|
||||
command::execute(utils::string::va("map %s", mapname.data()), false);
|
||||
}
|
||||
|
||||
void launch_default_map()
|
||||
{
|
||||
auto* mapname = game::Dvar_FindVar("mapname");
|
||||
if (mapname && std::strcmp(mapname->current.string, "mp_vlobby_room") != 0)
|
||||
{
|
||||
launch_map(mapname->current.string);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (game::Com_GetCurrentCoDPlayMode() == game::CODPLAYMODE_ZOMBIES)
|
||||
{
|
||||
launch_map("mp_zombie_lab");
|
||||
}
|
||||
else
|
||||
{
|
||||
launch_map("mp_venus");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void apply_rotation(rotation_data& rotation)
|
||||
{
|
||||
assert(!rotation.empty());
|
||||
|
||||
std::size_t i = 0;
|
||||
while (i < rotation.get_entries_size())
|
||||
{
|
||||
const auto& entry = rotation.get_next_entry();
|
||||
if (entry.first == "map"s)
|
||||
{
|
||||
console::info("Loading new map: '%s'\n", entry.second.data());
|
||||
if (!game::SV_MapExists(entry.second.data()))
|
||||
{
|
||||
console::info("map_rotation: '%s' map doesn't exist!\n", entry.second.data());
|
||||
launch_default_map();
|
||||
return;
|
||||
}
|
||||
|
||||
launch_map(entry.second);
|
||||
|
||||
// Map was found so we exit the loop
|
||||
break;
|
||||
}
|
||||
|
||||
if (entry.first == "gametype"s)
|
||||
{
|
||||
console::info("Applying new gametype: '%s'\n", entry.second.data());
|
||||
set_gametype(entry.second);
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i == rotation.get_entries_size())
|
||||
{
|
||||
console::error("Map rotation does not contain any map. Restarting\n");
|
||||
launch_default_map();
|
||||
}
|
||||
}
|
||||
|
||||
void load_rotation(const std::string& data)
|
||||
{
|
||||
static auto loaded = false;
|
||||
if (loaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
try
|
||||
{
|
||||
dedicated_rotation.parse(data);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
console::error("%s: %s contains invalid data!\n", ex.what(), sv_map_rotation->name);
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
console::info("dedicated_rotation size after parsing is '%zu'", dedicated_rotation.get_entries_size());
|
||||
#endif
|
||||
}
|
||||
|
||||
void load_map_rotation()
|
||||
{
|
||||
const std::string map_rotation = sv_map_rotation->current.string;
|
||||
if (!map_rotation.empty())
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
console::info("%s is not empty. Parsing...\n", sv_map_rotation->name);
|
||||
#endif
|
||||
load_rotation(map_rotation);
|
||||
}
|
||||
}
|
||||
|
||||
void apply_map_rotation_current(const std::string& data)
|
||||
{
|
||||
assert(!data.empty());
|
||||
|
||||
rotation_data rotation_current;
|
||||
|
||||
try
|
||||
{
|
||||
rotation_current.parse(data);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
console::error("%s: %s contains invalid data!\n", ex.what(), sv_map_rotation_current->name);
|
||||
}
|
||||
|
||||
game::Dvar_SetString(sv_map_rotation_current, "");
|
||||
|
||||
if (rotation_current.empty())
|
||||
{
|
||||
console::warn("%s is empty or contains invalid data\n", sv_map_rotation_current->name);
|
||||
launch_default_map();
|
||||
return;
|
||||
}
|
||||
|
||||
apply_rotation(rotation_current);
|
||||
}
|
||||
|
||||
void randomize_map_rotation()
|
||||
{
|
||||
if (sv_random_map_rotation->current.enabled)
|
||||
{
|
||||
console::info("Randomizing the map rotation\n");
|
||||
dedicated_rotation.randomize();
|
||||
}
|
||||
}
|
||||
|
||||
void perform_map_rotation()
|
||||
{
|
||||
if (game::Live_SyncOnlineDataFlags(0) != 0)
|
||||
{
|
||||
scheduler::on_game_initialized(perform_map_rotation, scheduler::pipeline::main, 1s);
|
||||
return;
|
||||
}
|
||||
|
||||
console::info("Rotating map...\n");
|
||||
|
||||
// This takes priority because of backwards compatibility
|
||||
const std::string map_rotation_current = sv_map_rotation_current->current.string;
|
||||
if (!map_rotation_current.empty())
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
console::info("Applying %s\n", sv_map_rotation_current->name);
|
||||
#endif
|
||||
apply_map_rotation_current(map_rotation_current);
|
||||
return;
|
||||
}
|
||||
|
||||
load_map_rotation();
|
||||
if (dedicated_rotation.empty())
|
||||
{
|
||||
console::warn("%s is empty or contains invalid data. Restarting map\n", sv_map_rotation->name);
|
||||
launch_default_map();
|
||||
return;
|
||||
}
|
||||
|
||||
randomize_map_rotation();
|
||||
|
||||
apply_rotation(dedicated_rotation);
|
||||
}
|
||||
|
||||
void trigger_map_rotation()
|
||||
{
|
||||
scheduler::schedule([]
|
||||
{
|
||||
if (game::CL_IsCgameInitialized())
|
||||
{
|
||||
return scheduler::cond_continue;
|
||||
}
|
||||
|
||||
command::execute("map_rotate", false);
|
||||
return scheduler::cond_end;
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
rotation_data::rotation_data()
|
||||
: index_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void rotation_data::randomize()
|
||||
{
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
|
||||
std::ranges::shuffle(this->rotation_entries_, gen);
|
||||
}
|
||||
|
||||
void rotation_data::add_entry(const std::string& key, const std::string& value)
|
||||
{
|
||||
this->rotation_entries_.emplace_back(std::make_pair(key, value));
|
||||
}
|
||||
|
||||
bool rotation_data::contains(const std::string& key, const std::string& value) const
|
||||
{
|
||||
return std::ranges::any_of(this->rotation_entries_, [&](const auto& entry)
|
||||
{
|
||||
return entry.first == key && entry.second == value;
|
||||
});
|
||||
}
|
||||
|
||||
bool rotation_data::empty() const noexcept
|
||||
{
|
||||
return this->rotation_entries_.empty();
|
||||
}
|
||||
|
||||
std::size_t rotation_data::get_entries_size() const noexcept
|
||||
{
|
||||
return this->rotation_entries_.size();
|
||||
}
|
||||
|
||||
rotation_data::rotation_entry& rotation_data::get_next_entry()
|
||||
{
|
||||
const auto index = this->index_;
|
||||
++this->index_ %= this->rotation_entries_.size();
|
||||
return this->rotation_entries_.at(index);
|
||||
}
|
||||
|
||||
void rotation_data::parse(const std::string& data)
|
||||
{
|
||||
const auto tokens = utils::string::split(data, ' ');
|
||||
for (std::size_t i = 0; !tokens.empty() && i < (tokens.size() - 1); i += 2)
|
||||
{
|
||||
const auto& key = tokens[i];
|
||||
const auto& value = tokens[i + 1];
|
||||
|
||||
if (key == "map"s || key == "gametype"s)
|
||||
{
|
||||
this->add_entry(key, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw map_rotation_parse_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler::once([]
|
||||
{
|
||||
sv_map_rotation = game::Dvar_RegisterString("sv_mapRotation", "", game::DVAR_FLAG_NONE, "");
|
||||
sv_map_rotation_current = game::Dvar_RegisterString("sv_mapRotationCurrent", "", game::DVAR_FLAG_NONE, "");
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
sv_random_map_rotation = game::Dvar_RegisterBool("sv_randomMapRotation", false, game::DVAR_FLAG_NONE, "Randomize map rotation");
|
||||
|
||||
command::add("map_rotate", &perform_map_rotation);
|
||||
|
||||
// Hook GScr_ExitLevel
|
||||
utils::hook::jump(0x14032E490, &trigger_map_rotation);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(map_rotation::component)
|
34
src/client/component/map_rotation.hpp
Normal file
34
src/client/component/map_rotation.hpp
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
namespace map_rotation
|
||||
{
|
||||
struct map_rotation_parse_error : public std::exception
|
||||
{
|
||||
[[nodiscard]] const char* what() const noexcept override { return "Map Rotation Parse Erro"; }
|
||||
};
|
||||
|
||||
class rotation_data
|
||||
{
|
||||
public:
|
||||
using rotation_entry = std::pair<std::string, std::string>;
|
||||
|
||||
rotation_data();
|
||||
|
||||
void randomize();
|
||||
|
||||
// In case a new way to enrich the map rotation is added (other than sv_mapRotation)
|
||||
// this method should be called to add a new entry (gamemode/map & value)
|
||||
void add_entry(const std::string& key, const std::string& value);
|
||||
|
||||
[[nodiscard]] bool contains(const std::string& key, const std::string& value) const;
|
||||
[[nodiscard]] bool empty() const noexcept;
|
||||
[[nodiscard]] std::size_t get_entries_size() const noexcept;
|
||||
[[nodiscard]] rotation_entry& get_next_entry();
|
||||
|
||||
void parse(const std::string& data);
|
||||
|
||||
private:
|
||||
std::vector<rotation_entry> rotation_entries_;
|
||||
std::size_t index_;
|
||||
};
|
||||
}
|
287
src/client/component/network.cpp
Normal file
287
src/client/component/network.cpp
Normal file
@ -0,0 +1,287 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "network.hpp"
|
||||
#include "console.hpp"
|
||||
#include "dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace network
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<std::string, callback>& get_callbacks()
|
||||
{
|
||||
static std::unordered_map<std::string, callback> callbacks{};
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
bool handle_command(game::netadr_s* address, const char* command, game::msg_t* message)
|
||||
{
|
||||
const auto cmd_string = utils::string::to_lower(command);
|
||||
auto& callbacks = get_callbacks();
|
||||
const auto handler = callbacks.find(cmd_string);
|
||||
const auto offset = cmd_string.size() + 5;
|
||||
if (message->cursize < offset || handler == callbacks.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string data(message->data + offset, message->cursize - offset);
|
||||
|
||||
handler->second(*address, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
void handle_command_stub(utils::hook::assembler& a)
|
||||
{
|
||||
const auto return_unhandled = a.newLabel();
|
||||
|
||||
a.pushad64();
|
||||
|
||||
a.mov(r8, rsi); // message
|
||||
a.mov(rdx, rdi); // command
|
||||
a.mov(rcx, r14); // netaddr
|
||||
|
||||
a.call_aligned(handle_command);
|
||||
|
||||
a.test(al, al);
|
||||
a.jz(return_unhandled);
|
||||
|
||||
// Command handled
|
||||
a.popad64();
|
||||
a.mov(al, 1);
|
||||
a.jmp(0x14020AA10);
|
||||
|
||||
a.bind(return_unhandled);
|
||||
a.popad64();
|
||||
a.jmp(0x14020A19A);
|
||||
}
|
||||
|
||||
int net_compare_base_address(const game::netadr_s* a1, const game::netadr_s* a2)
|
||||
{
|
||||
if (a1->type == a2->type)
|
||||
{
|
||||
switch (a1->type)
|
||||
{
|
||||
case game::netadrtype_t::NA_BOT:
|
||||
case game::netadrtype_t::NA_LOOPBACK:
|
||||
return a1->port == a2->port;
|
||||
|
||||
case game::netadrtype_t::NA_IP:
|
||||
return !memcmp(a1->ip, a2->ip, 4);
|
||||
case game::netadrtype_t::NA_BROADCAST:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int net_compare_address(const game::netadr_s* a1, const game::netadr_s* a2)
|
||||
{
|
||||
return net_compare_base_address(a1, a2) && a1->port == a2->port;
|
||||
}
|
||||
|
||||
void reconnect_migratated_client(void*, game::netadr_s* from, const int, const int, const char*,
|
||||
const char*, bool)
|
||||
{
|
||||
// This happens when a client tries to rejoin after being recently disconnected, OR by a duplicated guid
|
||||
// We don't want this to do anything. It decides to crash seemingly randomly
|
||||
// Rather than try and let the player in, just tell them they are a duplicate player and reject connection
|
||||
game::NET_OutOfBandPrint(game::NS_SERVER, from, "error\nYou are already connected to the server.");
|
||||
}
|
||||
}
|
||||
|
||||
void on(const std::string& command, const callback& callback)
|
||||
{
|
||||
get_callbacks()[utils::string::to_lower(command)] = callback;
|
||||
}
|
||||
|
||||
int dw_send_to_stub(const int size, const char* src, game::netadr_s* a3)
|
||||
{
|
||||
sockaddr s = {};
|
||||
game::NetadrToSockadr(a3, &s);
|
||||
return sendto(*game::query_socket, src, size, 0, &s, 16) >= 0;
|
||||
}
|
||||
|
||||
void send(const game::netadr_s& address, const std::string& command, const std::string& data, const char separator)
|
||||
{
|
||||
std::string packet = "\xFF\xFF\xFF\xFF";
|
||||
packet.append(command);
|
||||
packet.push_back(separator);
|
||||
packet.append(data);
|
||||
|
||||
send_data(address, packet);
|
||||
}
|
||||
|
||||
void send_data(const game::netadr_s& address, const std::string& data)
|
||||
{
|
||||
auto size = static_cast<int>(data.size());
|
||||
if (address.type == game::NA_LOOPBACK)
|
||||
{
|
||||
// TODO: Fix this for loopback
|
||||
if (size > 1280)
|
||||
{
|
||||
console::error("Packet was too long. Truncated!\n");
|
||||
size = 1280;
|
||||
}
|
||||
|
||||
game::NET_SendLoopPacket(game::NS_CLIENT1, size, data.data(), &address);
|
||||
}
|
||||
else
|
||||
{
|
||||
game::Sys_SendPacket(size, data.data(), &address);
|
||||
}
|
||||
}
|
||||
|
||||
bool are_addresses_equal(const game::netadr_s& a, const game::netadr_s& b)
|
||||
{
|
||||
return net_compare_address(&a, &b);
|
||||
}
|
||||
|
||||
const char* net_adr_to_string(const game::netadr_s& a)
|
||||
{
|
||||
if (a.type == game::netadrtype_t::NA_LOOPBACK)
|
||||
{
|
||||
return "loopback";
|
||||
}
|
||||
|
||||
if (a.type == game::netadrtype_t::NA_BOT)
|
||||
{
|
||||
return "bot";
|
||||
}
|
||||
|
||||
if (a.type == game::netadrtype_t::NA_IP || a.type == game::netadrtype_t::NA_BROADCAST)
|
||||
{
|
||||
if (a.port)
|
||||
{
|
||||
return utils::string::va("%u.%u.%u.%u:%u", a.ip[0], a.ip[1], a.ip[2], a.ip[3], htons(a.port));
|
||||
}
|
||||
|
||||
return utils::string::va("%u.%u.%u.%u", a.ip[0], a.ip[1], a.ip[2], a.ip[3]);
|
||||
}
|
||||
|
||||
return "bad";
|
||||
}
|
||||
|
||||
game::dvar_t* register_netport_stub(const char* dvarName, int value, int min, int max, unsigned int flags,
|
||||
const char* description)
|
||||
{
|
||||
auto dvar = game::Dvar_RegisterInt("net_port", 27016, 0, 0xFFFFu, game::DVAR_FLAG_LATCHED, "Network port");
|
||||
|
||||
// read net_port from command line
|
||||
command::read_startup_variable("net_port");
|
||||
|
||||
return dvar;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// redirect dw_sendto to raw socket
|
||||
//utils::hook::jump(0x1404D850A, reinterpret_cast<void*>(0x1404D849A));
|
||||
utils::hook::call(0x1404D851F, dw_send_to_stub);
|
||||
utils::hook::jump(game::Sys_SendPacket, dw_send_to_stub);
|
||||
|
||||
// intercept command handling
|
||||
utils::hook::jump(0x14020A175, utils::hook::assemble(handle_command_stub), true);
|
||||
|
||||
// handle xuid without secure connection
|
||||
utils::hook::nop(0x14043FFF8, 6);
|
||||
|
||||
utils::hook::jump(0x1403DA700, net_compare_address);
|
||||
utils::hook::jump(0x1403DA750, net_compare_base_address);
|
||||
|
||||
// don't establish secure conenction
|
||||
utils::hook::set<uint8_t>(0x140232BBD, 0xEB);
|
||||
utils::hook::set<uint8_t>(0x140232C9A, 0xEB);
|
||||
utils::hook::set<uint8_t>(0x140232F8D, 0xEB);
|
||||
utils::hook::set<uint8_t>(0x14020862F, 0xEB);
|
||||
|
||||
// ignore unregistered connection
|
||||
utils::hook::jump(0x140439EA9, reinterpret_cast<void*>(0x140439E28));
|
||||
utils::hook::set<uint8_t>(0x140439E9E, 0xEB);
|
||||
|
||||
// disable xuid verification
|
||||
utils::hook::set<uint8_t>(0x140022319, 0xEB);
|
||||
utils::hook::set<uint8_t>(0x140022334, 0xEB);
|
||||
|
||||
// disable xuid verification
|
||||
utils::hook::nop(0x14043CC4C, 2);
|
||||
utils::hook::set<uint8_t>(0x14043CCA8, 0xEB);
|
||||
|
||||
// ignore configstring mismatch
|
||||
utils::hook::set<uint8_t>(0x140211610, 0xEB);
|
||||
|
||||
// ignore dw handle in SV_PacketEvent
|
||||
utils::hook::set<uint8_t>(0x140442F6D, 0xEB);
|
||||
utils::hook::call(0x140442F61, &net_compare_address);
|
||||
|
||||
// ignore dw handle in SV_FindClientByAddress
|
||||
utils::hook::set<uint8_t>(0x14044256D, 0xEB);
|
||||
utils::hook::call(0x140442561, &net_compare_address);
|
||||
|
||||
// ignore dw handle in SV_DirectConnect
|
||||
utils::hook::set<uint8_t>(0x140439BA8, 0xEB);
|
||||
utils::hook::set<uint8_t>(0x140439DA5, 0xEB);
|
||||
utils::hook::call(0x140439B9B, &net_compare_address);
|
||||
utils::hook::call(0x140439D98, &net_compare_address);
|
||||
|
||||
// increase cl_maxpackets
|
||||
dvars::override::register_int("cl_maxpackets", 1000, 1, 1000, game::DVAR_FLAG_SAVED);
|
||||
|
||||
// increase snaps
|
||||
dvars::override::register_int("sv_remote_client_snapshot_msec", 33, 33, 100, game::DVAR_FLAG_NONE);
|
||||
|
||||
// ignore impure client
|
||||
utils::hook::jump(0x14043AC0D, reinterpret_cast<void*>(0x14043ACA3));
|
||||
|
||||
// don't send checksum
|
||||
utils::hook::set<uint8_t>(0x1404D84C0, 0);
|
||||
utils::hook::set<uint8_t>(0x1404D8519, 0);
|
||||
|
||||
// don't read checksum
|
||||
utils::hook::jump(0x1404D842B, 0x1404D8453);
|
||||
|
||||
// don't try to reconnect client
|
||||
utils::hook::call(0x140439D4D, reconnect_migratated_client);
|
||||
utils::hook::nop(0x140439D28, 4); // this crashes when reconnecting for some reason
|
||||
|
||||
// allow server owner to modify net_port before the socket bind
|
||||
utils::hook::call(0x1404D7A3D, register_netport_stub);
|
||||
utils::hook::call(0x1404D7E28, register_netport_stub);
|
||||
|
||||
// increase allowed packet size
|
||||
const auto max_packet_size = 0x20000;
|
||||
utils::hook::set<int>(0x1403DADE6, max_packet_size);
|
||||
utils::hook::set<int>(0x1403DAE20, max_packet_size);
|
||||
utils::hook::set<int>(0x1403DAD14, max_packet_size);
|
||||
utils::hook::set<int>(0x1403DAD35, max_packet_size);
|
||||
|
||||
// ignore built in "print" oob command and add in our own
|
||||
utils::hook::set<std::uint8_t>(0x14020A723, 0xEB);
|
||||
on("print", [](const game::netadr_s&, const std::string& data)
|
||||
{
|
||||
console::info("%s", data.data());
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(network::component)
|
47
src/client/component/network.hpp
Normal file
47
src/client/component/network.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
namespace network
|
||||
{
|
||||
using callback = std::function<void(const game::netadr_s&, const std::string&)>;
|
||||
|
||||
void on(const std::string& command, const callback& callback);
|
||||
void send(const game::netadr_s& address, const std::string& command, const std::string& data = {}, char separator = ' ');
|
||||
void send_data(const game::netadr_s& address, const std::string& data);
|
||||
|
||||
bool are_addresses_equal(const game::netadr_s& a, const game::netadr_s& b);
|
||||
|
||||
const char* net_adr_to_string(const game::netadr_s& a);
|
||||
}
|
||||
|
||||
inline bool operator==(const game::netadr_s& a, const game::netadr_s& b)
|
||||
{
|
||||
return network::are_addresses_equal(a, b); //
|
||||
}
|
||||
|
||||
inline bool operator!=(const game::netadr_s& a, const game::netadr_s& b)
|
||||
{
|
||||
return !(a == b); //
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
template <>
|
||||
struct equal_to<game::netadr_s>
|
||||
{
|
||||
using result_type = bool;
|
||||
|
||||
bool operator()(const game::netadr_s& lhs, const game::netadr_s& rhs) const
|
||||
{
|
||||
return network::are_addresses_equal(lhs, rhs);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<game::netadr_s>
|
||||
{
|
||||
size_t operator()(const game::netadr_s& x) const noexcept
|
||||
{
|
||||
return hash<uint32_t>()(*reinterpret_cast<const uint32_t*>(&x.ip[0])) ^ hash<uint16_t>()(x.port);
|
||||
}
|
||||
};
|
||||
}
|
208
src/client/component/notifies.cpp
Normal file
208
src/client/component/notifies.cpp
Normal file
@ -0,0 +1,208 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "notifies.hpp"
|
||||
#include "scripting.hpp"
|
||||
#include "game_log.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace notifies
|
||||
{
|
||||
bool hook_enabled = true;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct gsc_hook
|
||||
{
|
||||
const char* target_pos{};
|
||||
};
|
||||
|
||||
std::unordered_map<const char*, gsc_hook> vm_execute_hooks;
|
||||
utils::hook::detour scr_player_killed_hook;
|
||||
utils::hook::detour scr_player_damage_hook;
|
||||
|
||||
char empty_function[2] = {0x32, 0x34}; // CHECK_CLEAR_PARAMS, END
|
||||
const char* target_function = nullptr;
|
||||
|
||||
|
||||
|
||||
void client_command_stub(const int client_num)
|
||||
{
|
||||
if (game::mp::g_entities[client_num].client == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
command::params_sv params;
|
||||
|
||||
if (params[0] == "say"s || params[0] == "say_team"s)
|
||||
{
|
||||
std::string message(params.join(1));
|
||||
|
||||
auto msg_index = 0;
|
||||
if (message[msg_index] == '\x1F')
|
||||
{
|
||||
msg_index = 1;
|
||||
}
|
||||
|
||||
auto hidden = false;
|
||||
if (message[msg_index] == '/')
|
||||
{
|
||||
hidden = true;
|
||||
|
||||
if (msg_index == 1)
|
||||
{
|
||||
// Overwrite / with \x1F only if present
|
||||
message[msg_index] = message[msg_index - 1];
|
||||
}
|
||||
// Skip over the first character
|
||||
message.erase(message.begin());
|
||||
}
|
||||
|
||||
scheduler::once([params, message, msg_index, client_num]
|
||||
{
|
||||
const auto* guid = game::SV_GetGuid(client_num);
|
||||
const auto* name = game::mp::svs_clients[client_num].name;
|
||||
|
||||
game_log::g_log_printf("%s;%s;%i;%s;%s\n",
|
||||
params[0],
|
||||
guid,
|
||||
client_num,
|
||||
name,
|
||||
message.data()
|
||||
);
|
||||
|
||||
}, scheduler::pipeline::server);
|
||||
|
||||
if (hidden)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ClientCommand
|
||||
utils::hook::invoke<void>(0x1402E98F0, client_num);
|
||||
}
|
||||
|
||||
unsigned int local_id_to_entity(unsigned int local_id)
|
||||
{
|
||||
const auto variable = game::scr_VarGlob->objectVariableValue[local_id];
|
||||
return variable.u.f.next;
|
||||
}
|
||||
|
||||
bool execute_vm_hook(const char* pos)
|
||||
{
|
||||
if (!vm_execute_hooks.contains(pos))
|
||||
{
|
||||
hook_enabled = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hook_enabled && pos > reinterpret_cast<char*>(vm_execute_hooks.size()))
|
||||
{
|
||||
hook_enabled = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto hook = vm_execute_hooks[pos];
|
||||
target_function = hook.target_pos;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void vm_execute_stub(utils::hook::assembler& a)
|
||||
{
|
||||
const auto replace = a.newLabel();
|
||||
const auto end = a.newLabel();
|
||||
|
||||
a.pushad64();
|
||||
|
||||
a.mov(rcx, r14);
|
||||
a.call_aligned(execute_vm_hook);
|
||||
|
||||
a.cmp(al, 0);
|
||||
a.jne(replace);
|
||||
|
||||
a.popad64();
|
||||
a.jmp(end);
|
||||
|
||||
a.bind(end);
|
||||
|
||||
a.movzx(r15d, byte_ptr(r14));
|
||||
a.inc(r14);
|
||||
a.lea(eax, dword_ptr(r15, -0x17));
|
||||
a.mov(dword_ptr(rbp, 0x68), r15d);
|
||||
|
||||
a.jmp(0x1403FA143);
|
||||
|
||||
a.bind(replace);
|
||||
|
||||
a.popad64();
|
||||
a.mov(rax, qword_ptr(reinterpret_cast<std::int64_t>(&target_function)));
|
||||
a.mov(r14, rax);
|
||||
a.jmp(end);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_callbacks()
|
||||
{
|
||||
vm_execute_hooks.clear();
|
||||
}
|
||||
|
||||
void enable_vm_execute_hook()
|
||||
{
|
||||
hook_enabled = true;
|
||||
}
|
||||
|
||||
void disable_vm_execute_hook()
|
||||
{
|
||||
hook_enabled = false;
|
||||
}
|
||||
|
||||
void set_gsc_hook(const char* source, const char* target)
|
||||
{
|
||||
gsc_hook hook;
|
||||
hook.target_pos = target;
|
||||
vm_execute_hooks[source] = hook;
|
||||
}
|
||||
|
||||
void clear_hook(const char* pos)
|
||||
{
|
||||
vm_execute_hooks.erase(pos);
|
||||
}
|
||||
|
||||
std::size_t get_hook_count()
|
||||
{
|
||||
return vm_execute_hooks.size();
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::call(0x14043A9AD, client_command_stub);
|
||||
|
||||
utils::hook::jump(0x1403FA134, utils::hook::assemble(vm_execute_stub), true);
|
||||
|
||||
scripting::on_shutdown([](bool free_scripts)
|
||||
{
|
||||
if (free_scripts)
|
||||
{
|
||||
vm_execute_hooks.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(notifies::component)
|
15
src/client/component/notifies.hpp
Normal file
15
src/client/component/notifies.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
namespace notifies
|
||||
{
|
||||
extern bool hook_enabled;
|
||||
|
||||
void set_gsc_hook(const char* source, const char* target);
|
||||
void clear_hook(const char* pos);
|
||||
std::size_t get_hook_count();
|
||||
|
||||
void clear_callbacks();
|
||||
|
||||
void enable_vm_execute_hook();
|
||||
void disable_vm_execute_hook();
|
||||
}
|
638
src/client/component/party.cpp
Normal file
638
src/client/component/party.cpp
Normal file
@ -0,0 +1,638 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "party.hpp"
|
||||
#include "console.hpp"
|
||||
#include "command.hpp"
|
||||
#include "network.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "server_list.hpp"
|
||||
|
||||
#include "steam/steam.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/info_string.hpp>
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
#include <version.hpp>
|
||||
|
||||
namespace party
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct
|
||||
{
|
||||
game::netadr_s host{};
|
||||
std::string challenge{};
|
||||
bool hostDefined{false};
|
||||
} connect_state;
|
||||
|
||||
std::string sv_motd;
|
||||
|
||||
int sv_maxclients;
|
||||
|
||||
void perform_game_initialization()
|
||||
{
|
||||
command::execute("onlinegame 1", true);
|
||||
command::execute("xstartprivateparty", true);
|
||||
command::execute("xblive_privatematch 1", true);
|
||||
command::execute("startentitlements", true);
|
||||
}
|
||||
|
||||
void connect_to_party(const game::netadr_s& target, const std::string& mapname, const std::string& gametype)
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (game::Live_SyncOnlineDataFlags(0) != 0)
|
||||
{
|
||||
// initialize the game after onlinedataflags is 32 (workaround)
|
||||
if (game::Live_SyncOnlineDataFlags(0) == 32)
|
||||
{
|
||||
scheduler::once([=]()
|
||||
{
|
||||
command::execute("xstartprivateparty", true);
|
||||
command::execute("disconnect", true); // 32 -> 0
|
||||
|
||||
connect_to_party(target, mapname, gametype);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduler::once([=]()
|
||||
{
|
||||
connect_to_party(target, mapname, gametype);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
perform_game_initialization();
|
||||
|
||||
// exit from virtuallobby
|
||||
reinterpret_cast<void(*)()>(0x14020EB90)();
|
||||
|
||||
// CL_ConnectFromParty
|
||||
char session_info[0x100] = {};
|
||||
reinterpret_cast<void(*)(int, char*, const game::netadr_s*, const char*, const char*)>(0x140209360)(
|
||||
0, session_info, &target, mapname.data(), gametype.data());
|
||||
}
|
||||
|
||||
std::string get_dvar_string(const std::string& dvar)
|
||||
{
|
||||
const auto* dvar_value = game::Dvar_FindVar(dvar.data());
|
||||
if (dvar_value && dvar_value->current.string)
|
||||
{
|
||||
return {dvar_value->current.string};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool get_dvar_bool(const std::string& dvar)
|
||||
{
|
||||
const auto* dvar_value = game::Dvar_FindVar(dvar.data());
|
||||
if (dvar_value && dvar_value->current.enabled)
|
||||
{
|
||||
return dvar_value->current.enabled;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void didyouknow_stub(const char* dvar_name, const char* string)
|
||||
{
|
||||
if (!party::sv_motd.empty())
|
||||
{
|
||||
string = party::sv_motd.data();
|
||||
}
|
||||
|
||||
// This function either does Dvar_SetString or Dvar_RegisterString for the given dvar
|
||||
reinterpret_cast<void(*)(const char*, const char*)>(0x1404C39B0)(dvar_name, string);
|
||||
}
|
||||
|
||||
void disconnect_stub()
|
||||
{
|
||||
if (!game::VirtualLobby_Loaded())
|
||||
{
|
||||
if (game::CL_IsCgameInitialized())
|
||||
{
|
||||
game::CL_ForwardCommandToServer(0, "disconnect");
|
||||
game::CL_WritePacket(0);
|
||||
}
|
||||
|
||||
game::CL_Disconnect(0);
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::detour cl_disconnect_hook;
|
||||
|
||||
void cl_disconnect_stub(int a1)
|
||||
{
|
||||
clear_sv_motd();
|
||||
cl_disconnect_hook.invoke<void>(a1);
|
||||
}
|
||||
|
||||
const auto drop_reason_stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.mov(rdx, rdi);
|
||||
a.mov(ecx, 2);
|
||||
a.jmp(0x140209DD9);
|
||||
});
|
||||
}
|
||||
|
||||
void clear_sv_motd()
|
||||
{
|
||||
sv_motd.clear();
|
||||
}
|
||||
|
||||
int get_client_num_by_name(const std::string& name)
|
||||
{
|
||||
for (auto i = 0; !name.empty() && i < *game::mp::svs_numclients; ++i)
|
||||
{
|
||||
if (game::mp::g_entities[i].client)
|
||||
{
|
||||
char client_name[16] = {0};
|
||||
strncpy_s(client_name, game::mp::g_entities[i].client->name, sizeof(client_name));
|
||||
game::I_CleanStr(client_name);
|
||||
|
||||
if (client_name == name)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void reset_connect_state()
|
||||
{
|
||||
connect_state = {};
|
||||
}
|
||||
|
||||
int get_client_count()
|
||||
{
|
||||
auto count = 0;
|
||||
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
|
||||
{
|
||||
if (game::mp::svs_clients[i].header.state >= 1)
|
||||
{
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int get_bot_count()
|
||||
{
|
||||
auto count = 0;
|
||||
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
|
||||
{
|
||||
if (game::mp::svs_clients[i].header.state >= 1 &&
|
||||
game::SV_BotIsBot(i))
|
||||
{
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
game::netadr_s& get_target()
|
||||
{
|
||||
return connect_state.host;
|
||||
}
|
||||
|
||||
void connect(const game::netadr_s& target)
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
command::execute("lui_open_popup popup_acceptinginvite", false);
|
||||
|
||||
connect_state.host = target;
|
||||
connect_state.challenge = utils::cryptography::random::get_challenge();
|
||||
connect_state.hostDefined = true;
|
||||
|
||||
network::send(target, "getInfo", connect_state.challenge);
|
||||
}
|
||||
|
||||
void start_map(const std::string& mapname)
|
||||
{
|
||||
if (game::Live_SyncOnlineDataFlags(0) > 32)
|
||||
{
|
||||
scheduler::once([=]()
|
||||
{
|
||||
command::execute("map " + mapname, false);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!game::SV_MapExists(mapname.data()))
|
||||
{
|
||||
console::info("Map '%s' doesn't exist.\n", mapname.data());
|
||||
return;
|
||||
}
|
||||
|
||||
auto* current_mapname = game::Dvar_FindVar("mapname");
|
||||
if (current_mapname && utils::string::to_lower(current_mapname->current.string) ==
|
||||
utils::string::to_lower(mapname) && (game::SV_Loaded() && !game::VirtualLobby_Loaded()))
|
||||
{
|
||||
console::info("Restarting map: %s\n", mapname.data());
|
||||
command::execute("map_restart", false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
if (game::SV_Loaded())
|
||||
{
|
||||
const auto* args = "Leave";
|
||||
game::UI_RunMenuScript(0, &args);
|
||||
}
|
||||
|
||||
perform_game_initialization();
|
||||
}
|
||||
|
||||
console::info("Starting map: %s\n", mapname.data());
|
||||
|
||||
auto* gametype = game::Dvar_FindVar("g_gametype");
|
||||
if (gametype && gametype->current.string)
|
||||
{
|
||||
command::execute(utils::string::va("ui_gametype %s", gametype->current.string), true);
|
||||
}
|
||||
command::execute(utils::string::va("ui_mapname %s", mapname.data()), true);
|
||||
|
||||
/*auto* maxclients = game::Dvar_FindVar("sv_maxclients");
|
||||
if (maxclients)
|
||||
{
|
||||
command::execute(utils::string::va("ui_maxclients %i", maxclients->current.integer), true);
|
||||
command::execute(utils::string::va("party_maxplayers %i", maxclients->current.integer), true);
|
||||
}*/
|
||||
|
||||
const auto* args = "StartServer";
|
||||
game::UI_RunMenuScript(0, &args);
|
||||
}
|
||||
}
|
||||
|
||||
int server_client_count()
|
||||
{
|
||||
return sv_maxclients;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// hook disconnect command function
|
||||
utils::hook::jump(0x14020A010, disconnect_stub);
|
||||
|
||||
// detour CL_Disconnect to clear motd
|
||||
cl_disconnect_hook.create(0x140209EC0, cl_disconnect_stub);
|
||||
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
// show custom drop reason
|
||||
utils::hook::nop(0x140209D5C, 13);
|
||||
utils::hook::jump(0x140209D5C, drop_reason_stub, true);
|
||||
}
|
||||
// enable custom kick reason in GScr_KickPlayer
|
||||
utils::hook::set<uint8_t>(0x14032ED80, 0xEB);
|
||||
|
||||
command::add("map", [](const command::params& argument)
|
||||
{
|
||||
if (argument.size() != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
start_map(argument[1]);
|
||||
});
|
||||
|
||||
command::add("map_restart", []()
|
||||
{
|
||||
if (!game::SV_Loaded() || game::VirtualLobby_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
*reinterpret_cast<int*>(0x1488692B0) = 1; // sv_map_restart
|
||||
*reinterpret_cast<int*>(0x1488692B4) = 1; // sv_loadScripts
|
||||
*reinterpret_cast<int*>(0x1488692B8) = 0; // sv_migrate
|
||||
reinterpret_cast<void(*)(int)>(0x140437460)(0); // SV_CheckLoadGame
|
||||
});
|
||||
|
||||
command::add("fast_restart", []()
|
||||
{
|
||||
if (game::SV_Loaded() && !game::VirtualLobby_Loaded())
|
||||
{
|
||||
game::SV_FastRestart(0);
|
||||
}
|
||||
});
|
||||
|
||||
command::add("reconnect", [](const command::params& argument)
|
||||
{
|
||||
if (!connect_state.hostDefined)
|
||||
{
|
||||
console::info("Cannot connect to server.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (game::CL_IsCgameInitialized())
|
||||
{
|
||||
command::execute("disconnect");
|
||||
command::execute("reconnect");
|
||||
}
|
||||
else
|
||||
{
|
||||
connect(connect_state.host);
|
||||
}
|
||||
});
|
||||
|
||||
command::add("connect", [](const command::params& argument)
|
||||
{
|
||||
if (argument.size() != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::netadr_s target{};
|
||||
if (game::NET_StringToAdr(argument[1], &target))
|
||||
{
|
||||
connect(target);
|
||||
}
|
||||
});
|
||||
|
||||
command::add("kickClient", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("usage: kickClient <num>, <reason>(optional)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!game::SV_Loaded() || game::VirtualLobby_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string reason;
|
||||
if (params.size() > 2)
|
||||
{
|
||||
reason = params.join(2);
|
||||
}
|
||||
if (reason.empty())
|
||||
{
|
||||
reason = "EXE_PLAYERKICKED";
|
||||
}
|
||||
|
||||
const auto client_num = atoi(params.get(1));
|
||||
if (client_num < 0 || client_num >= *game::mp::svs_numclients)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler::once([client_num, reason]()
|
||||
{
|
||||
game::SV_KickClientNum(client_num, reason.data());
|
||||
}, scheduler::pipeline::server);
|
||||
});
|
||||
|
||||
command::add("kick", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("usage: kick <name>, <reason>(optional)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!game::SV_Loaded() || game::VirtualLobby_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string reason;
|
||||
if (params.size() > 2)
|
||||
{
|
||||
reason = params.join(2);
|
||||
}
|
||||
if (reason.empty())
|
||||
{
|
||||
reason = "EXE_PLAYERKICKED";
|
||||
}
|
||||
|
||||
const std::string name = params.get(1);
|
||||
if (name == "all"s)
|
||||
{
|
||||
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
|
||||
{
|
||||
scheduler::once([i, reason]
|
||||
{
|
||||
game::SV_KickClientNum(i, reason.data());
|
||||
}, scheduler::pipeline::server);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto client_num = get_client_num_by_name(name);
|
||||
if (client_num < 0 || client_num >= *game::mp::svs_numclients)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler::once([client_num, reason]()
|
||||
{
|
||||
game::SV_KickClientNum(client_num, reason.data());
|
||||
}, scheduler::pipeline::server);
|
||||
});
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
game::Dvar_RegisterString("sv_sayName", "console", game::DvarFlags::DVAR_FLAG_NONE,
|
||||
"The name to pose as for 'say' commands");
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
command::add("tell", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto client_num = atoi(params.get(1));
|
||||
const auto message = params.join(2);
|
||||
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
||||
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE,
|
||||
utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
||||
printf("%s -> %i: %s\n", name, client_num, message.data());
|
||||
});
|
||||
|
||||
command::add("tellraw", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto client_num = atoi(params.get(1));
|
||||
const auto message = params.join(2);
|
||||
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE,
|
||||
utils::string::va("%c \"%s\"", 84, message.data()));
|
||||
printf("%i: %s\n", client_num, message.data());
|
||||
});
|
||||
|
||||
command::add("say", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto message = params.join(1);
|
||||
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
||||
|
||||
game::SV_GameSendServerCommand(
|
||||
-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
||||
printf("%s: %s\n", name, message.data());
|
||||
});
|
||||
|
||||
command::add("sayraw", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto message = params.join(1);
|
||||
|
||||
game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE,
|
||||
utils::string::va("%c \"%s\"", 84, message.data()));
|
||||
printf("%s\n", message.data());
|
||||
});
|
||||
|
||||
utils::hook::call(0x14048811C, didyouknow_stub); // allow custom didyouknow based on sv_motd
|
||||
|
||||
network::on("getInfo", [](const game::netadr_s& target, const std::string& data)
|
||||
{
|
||||
utils::info_string info{};
|
||||
info.set("challenge", data);
|
||||
info.set("gamename", "S1");
|
||||
info.set("hostname", get_dvar_string("sv_hostname"));
|
||||
info.set("gametype", get_dvar_string("g_gametype"));
|
||||
info.set("sv_motd", get_dvar_string("sv_motd"));
|
||||
info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits));
|
||||
info.set("mapname", get_dvar_string("mapname"));
|
||||
info.set("isPrivate", get_dvar_string("g_password").empty() ? "0" : "1");
|
||||
info.set("clients", std::to_string(get_client_count()));
|
||||
info.set("bots", std::to_string(get_bot_count()));
|
||||
info.set("sv_maxclients", std::to_string(*game::mp::svs_numclients));
|
||||
info.set("protocol", std::to_string(PROTOCOL));
|
||||
info.set("playmode", utils::string::va("%i", game::Com_GetCurrentCoDPlayMode()));
|
||||
info.set("sv_running", std::to_string(get_dvar_bool("sv_running")));
|
||||
info.set("dedicated", std::to_string(get_dvar_bool("dedicated")));
|
||||
info.set("shortversion", SHORTVERSION);
|
||||
|
||||
network::send(target, "infoResponse", info.build(), '\n');
|
||||
});
|
||||
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
network::on("infoResponse", [](const game::netadr_s& target, const std::string& data)
|
||||
{
|
||||
const utils::info_string info(data);
|
||||
server_list::handle_info_response(target, info);
|
||||
|
||||
if (connect_state.host != target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.get("challenge") != connect_state.challenge)
|
||||
{
|
||||
const auto* error_msg = "Invalid challenge.";
|
||||
console::error("%s\n", error_msg);
|
||||
game::Com_Error(game::ERR_DROP, "%s", error_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto gamename = info.get("gamename");
|
||||
if (gamename != "S1"s)
|
||||
{
|
||||
const auto* error_msg = "Invalid gamename.";
|
||||
console::error("%s\n", error_msg);
|
||||
game::Com_Error(game::ERR_DROP, "%s", error_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto playmode = info.get("playmode");
|
||||
if (std::atoi(playmode.data()) != game::Com_GetCurrentCoDPlayMode())
|
||||
{
|
||||
const auto* error_msg = "Invalid playmode.";
|
||||
console::error("%s\n", error_msg);
|
||||
game::Com_Error(game::ERR_DROP, "%s", error_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sv_running = info.get("sv_running");
|
||||
if (sv_running != "1"s)
|
||||
{
|
||||
const auto* error_msg = "Server not running.";
|
||||
console::error("%s\n", error_msg);
|
||||
game::Com_Error(game::ERR_DROP, "%s", error_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto mapname = info.get("mapname");
|
||||
if (mapname.empty())
|
||||
{
|
||||
const auto* error_msg = "Invalid map.";
|
||||
console::error("%s\n", error_msg);
|
||||
game::Com_Error(game::ERR_DROP, "%s", error_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto gametype = info.get("gametype");
|
||||
if (gametype.empty())
|
||||
{
|
||||
const auto* error_msg = "Invalid gametype.";
|
||||
console::error("%s\n", error_msg);
|
||||
game::Com_Error(game::ERR_DROP, "%s", error_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
sv_motd = info.get("sv_motd");
|
||||
|
||||
try
|
||||
{
|
||||
sv_maxclients = std::stoi(info.get("sv_maxclients"));
|
||||
}
|
||||
catch([[maybe_unused]] const std::exception& ex)
|
||||
{
|
||||
sv_maxclients = 1;
|
||||
}
|
||||
|
||||
connect_to_party(target, mapname, gametype);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(party::component)
|
19
src/client/component/party.hpp
Normal file
19
src/client/component/party.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
namespace party
|
||||
{
|
||||
void reset_connect_state();
|
||||
|
||||
void connect(const game::netadr_s& target);
|
||||
void start_map(const std::string& mapname);
|
||||
|
||||
void clear_sv_motd();
|
||||
int server_client_count();
|
||||
|
||||
[[nodiscard]] int get_client_num_by_name(const std::string& name);
|
||||
|
||||
[[nodiscard]] int get_client_count();
|
||||
[[nodiscard]] int get_bot_count();
|
||||
|
||||
[[nodiscard]] game::netadr_s& get_target();
|
||||
}
|
322
src/client/component/patches.cpp
Normal file
322
src/client/component/patches.cpp
Normal file
@ -0,0 +1,322 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "dvars.hpp"
|
||||
#include "fastfiles.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#include <version.hpp>
|
||||
|
||||
namespace patches
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour live_get_local_client_name_hook;
|
||||
|
||||
const char* live_get_local_client_name()
|
||||
{
|
||||
return game::Dvar_FindVar("name")->current.string;
|
||||
}
|
||||
|
||||
utils::hook::detour sv_kick_client_num_hook;
|
||||
|
||||
void sv_kick_client_num(const int client_num, const char* reason)
|
||||
{
|
||||
// Don't kick bot to equalize team balance.
|
||||
if (reason == "EXE_PLAYERKICKED_BOT_BALANCE"s)
|
||||
{
|
||||
return;
|
||||
}
|
||||
return sv_kick_client_num_hook.invoke<void>(client_num, reason);
|
||||
}
|
||||
|
||||
std::string get_login_username()
|
||||
{
|
||||
char username[UNLEN + 1];
|
||||
DWORD username_len = UNLEN + 1;
|
||||
if (!GetUserNameA(username, &username_len))
|
||||
{
|
||||
return "Unknown Soldier";
|
||||
}
|
||||
|
||||
return std::string{username, username_len - 1};
|
||||
}
|
||||
|
||||
utils::hook::detour com_register_dvars_hook;
|
||||
|
||||
void com_register_dvars_stub()
|
||||
{
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
// Make name save
|
||||
game::Dvar_RegisterString("name", get_login_username().data(), game::DVAR_FLAG_SAVED, "Player name.");
|
||||
|
||||
// Disable data validation error popup
|
||||
game::Dvar_RegisterInt("data_validation_allow_drop", 0, 0, 0, game::DVAR_FLAG_NONE, "");
|
||||
}
|
||||
|
||||
return com_register_dvars_hook.invoke<void>();
|
||||
}
|
||||
|
||||
game::dvar_t* register_com_maxfps_stub(const char* name, int /*value*/, int /*min*/, int /*max*/, const unsigned int /*flags*/, const char* description)
|
||||
{
|
||||
return game::Dvar_RegisterInt(name, 0, 0, 1000, game::DVAR_FLAG_SAVED, description);
|
||||
}
|
||||
|
||||
game::dvar_t* register_cg_fov_stub(const char* name, float value, float min, float /*max*/, const unsigned int /*flags*/, const char* description)
|
||||
{
|
||||
return game::Dvar_RegisterFloat(name, value, min, 160, game::DVAR_FLAG_SAVED, description);
|
||||
}
|
||||
|
||||
game::dvar_t* register_fovscale_stub(const char* name, float /*value*/, float /*min*/, float /*max*/, unsigned int /*flags*/, const char* desc)
|
||||
{
|
||||
return game::Dvar_RegisterFloat(name, 1.0f, 0.2f, 5.0f, game::DVAR_FLAG_SAVED, desc);
|
||||
}
|
||||
|
||||
int dvar_command_patch() // game makes this return an int and compares with eax instead of al -_-
|
||||
{
|
||||
const command::params args{};
|
||||
|
||||
if (args.size() <= 0)
|
||||
return 0;
|
||||
|
||||
auto* dvar = game::Dvar_FindVar(args.get(0));
|
||||
if (dvar)
|
||||
{
|
||||
if (args.size() == 1)
|
||||
{
|
||||
const auto* const current = game::Dvar_ValueToString(dvar, dvar->current);
|
||||
const auto* const reset = game::Dvar_ValueToString(dvar, dvar->reset);
|
||||
console::info("\"%s\" is: \"%s^7\" default: \"%s^7\"\n", dvar->name, current, reset);
|
||||
console::info(" %s\n", dvars::dvar_get_domain(dvar->type, dvar->domain).data());
|
||||
}
|
||||
else
|
||||
{
|
||||
char command[0x1000] = {0};
|
||||
game::Dvar_GetCombinedString(command, 1);
|
||||
game::Dvar_SetCommand(args.get(0), command);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* db_read_raw_file_stub(const char* filename, char* buf, const int size)
|
||||
{
|
||||
std::string file_name = filename;
|
||||
if (file_name.find(".cfg") == std::string::npos)
|
||||
{
|
||||
file_name.append(".cfg");
|
||||
}
|
||||
|
||||
const auto file = filesystem::file(file_name);
|
||||
if (file.exists())
|
||||
{
|
||||
snprintf(buf, size, "%s\n", file.get_buffer().data());
|
||||
return buf;
|
||||
}
|
||||
|
||||
return game::DB_ReadRawFile(filename, buf, size);
|
||||
}
|
||||
|
||||
void aim_assist_add_to_target_list(void* a1, void* a2)
|
||||
{
|
||||
if (!dvars::aimassist_enabled->current.enabled)
|
||||
return;
|
||||
|
||||
game::AimAssist_AddToTargetList(a1, a2);
|
||||
}
|
||||
|
||||
void missing_content_error_stub(int /*mode*/, const char* /*message*/)
|
||||
{
|
||||
game::Com_Error(game::ERR_DROP, utils::string::va("MISSING FILE\n%s.ff", fastfiles::get_current_fastfile().data()));
|
||||
}
|
||||
|
||||
void bsp_sys_error_stub(const char* error, const char* arg1)
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
game::Sys_Error(error, arg1);
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduler::once([]()
|
||||
{
|
||||
command::execute("reconnect");
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
game::Com_Error(game::ERR_DROP, error, arg1);
|
||||
}
|
||||
}
|
||||
|
||||
void set_client_dvar_from_server_stub(void* a1, void* a2, const char* dvar, const char* value)
|
||||
{
|
||||
if (utils::string::to_lower(dvar) == "cg_fov")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// CG_SetClientDvarFromServer
|
||||
reinterpret_cast<void(*)(void*, void*, const char*, const char*)>(0x1401BF0A0)(a1, a2, dvar, value);
|
||||
}
|
||||
|
||||
utils::hook::detour cmd_lui_notify_server_hook;
|
||||
void cmd_lui_notify_server_stub(game::mp::gentity_s* ent)
|
||||
{
|
||||
command::params_sv params{};
|
||||
const auto menu_id = atoi(params.get(1));
|
||||
const auto client = &game::mp::svs_clients[ent->s.number];
|
||||
|
||||
// 22 => "end_game"
|
||||
if (menu_id == 22 && client->header.remoteAddress.type != game::NA_LOOPBACK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cmd_lui_notify_server_hook.invoke<void>(ent);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// Increment ref-count on these
|
||||
LoadLibraryA("PhysXDevice64.dll");
|
||||
LoadLibraryA("PhysXUpdateLoader64.dll");
|
||||
|
||||
// Register dvars
|
||||
com_register_dvars_hook.create(SELECT_VALUE(0x1402F86F0, 0x1403CF7F0), &com_register_dvars_stub);
|
||||
|
||||
// Unlock fps in main menu
|
||||
utils::hook::set<BYTE>(SELECT_VALUE(0x140144F5B, 0x140213C3B), 0xEB);
|
||||
|
||||
// Unlock com_maxfps
|
||||
utils::hook::call(SELECT_VALUE(0x1402F8726, 0x1403CF8CA), register_com_maxfps_stub);
|
||||
|
||||
// Unlock cg_fov
|
||||
utils::hook::call(SELECT_VALUE(0x1400EF830, 0x140014F66), register_cg_fov_stub);
|
||||
|
||||
// Unlock cg_fovscale
|
||||
utils::hook::call(SELECT_VALUE(0x140227599, 0x140014F9B), register_fovscale_stub);
|
||||
|
||||
// Patch Dvar_Command to print out values how CoD4 does it
|
||||
utils::hook::jump(SELECT_VALUE(0x1402FB4C0, 0x1403D31C0), dvar_command_patch);
|
||||
|
||||
// Show missing fastfiles
|
||||
utils::hook::call(SELECT_VALUE(0x1401817AF, 0x1402742A8), missing_content_error_stub);
|
||||
|
||||
// Allow executing custom cfg files with the "exec" command
|
||||
utils::hook::call(SELECT_VALUE(0x1402EE225, 0x1403AF7CD), db_read_raw_file_stub);
|
||||
|
||||
// Fix mouse lag
|
||||
utils::hook::nop(SELECT_VALUE(0x14038FAFF, 0x1404DB1AF), 6);
|
||||
scheduler::loop([]()
|
||||
{
|
||||
SetThreadExecutionState(ES_DISPLAY_REQUIRED);
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
// Allow kbam input when gamepad is enabled
|
||||
utils::hook::nop(SELECT_VALUE(0x14013EF83, 0x140206DB3), 2);
|
||||
utils::hook::nop(SELECT_VALUE(0x14013CBAC, 0x140204710), 6);
|
||||
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
patch_sp();
|
||||
}
|
||||
else
|
||||
{
|
||||
patch_mp();
|
||||
}
|
||||
}
|
||||
|
||||
static void patch_mp()
|
||||
{
|
||||
// Use name dvar
|
||||
live_get_local_client_name_hook.create(0x1404D47F0, &live_get_local_client_name);
|
||||
|
||||
// Patch SV_KickClientNum
|
||||
sv_kick_client_num_hook.create(0x1404377A0, &sv_kick_client_num);
|
||||
|
||||
// block changing name in-game
|
||||
utils::hook::set<uint8_t>(0x140438850, 0xC3);
|
||||
|
||||
// patch "Couldn't find the bsp for this map." error to not be fatal in mp
|
||||
utils::hook::call(0x14026E63B, bsp_sys_error_stub);
|
||||
|
||||
// client side aim assist dvar
|
||||
dvars::aimassist_enabled = game::Dvar_RegisterBool("aimassist_enabled", true, game::DVAR_FLAG_SAVED, "Enables aim assist for controllers");
|
||||
utils::hook::call(0x140003609, aim_assist_add_to_target_list);
|
||||
|
||||
// isProfanity
|
||||
utils::hook::set(0x14023BDC0, 0xC3C033);
|
||||
|
||||
// disable emblems
|
||||
dvars::override::register_int("emblems_active", 0, 0, 0, game::DVAR_FLAG_NONE);
|
||||
utils::hook::set<uint8_t>(0x140479590, 0xC3); // don't register commands
|
||||
|
||||
// disable elite_clan
|
||||
dvars::override::register_int("elite_clan_active", 0, 0, 0, game::DVAR_FLAG_NONE);
|
||||
utils::hook::set<uint8_t>(0x14054AB20, 0xC3); // don't register commands
|
||||
|
||||
// disable codPointStore
|
||||
dvars::override::register_int("codPointStore_enabled", 0, 0, 0, game::DVAR_FLAG_NONE);
|
||||
|
||||
// don't register every replicated dvar as a network dvar
|
||||
utils::hook::nop(0x1403534BE, 5); // dvar_foreach
|
||||
|
||||
// Patch "Server is on a different version"
|
||||
utils::hook::inject(0x1404398B2, VERSION);
|
||||
|
||||
// prevent servers overriding our fov
|
||||
utils::hook::call(0x1401BB782, set_client_dvar_from_server_stub);
|
||||
utils::hook::nop(0x1403D1195, 5);
|
||||
utils::hook::nop(0x1400FAE36, 5);
|
||||
utils::hook::set<uint8_t>(0x14019B9B9, 0xEB);
|
||||
|
||||
// some anti tamper thing that kills performance
|
||||
dvars::override::register_int("dvl", 0, 0, 0, game::DVAR_FLAG_READ);
|
||||
|
||||
// unlock safeArea_*
|
||||
utils::hook::jump(0x140219F5E, 0x140219F67);
|
||||
utils::hook::jump(0x140219F80, 0x140219F8E);
|
||||
dvars::override::register_float("safeArea_adjusted_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_float("safeArea_adjusted_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_float("safeArea_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_float("safeArea_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
|
||||
// move chat position on the screen above menu splashes
|
||||
dvars::override::register_vector2("cg_hudChatPosition", 5, 170, 0, 640, game::DVAR_FLAG_SAVED);
|
||||
|
||||
// allow servers to check for new packages more often
|
||||
dvars::override::register_int("sv_network_fps", 1000, 20, 1000, game::DVAR_FLAG_SAVED);
|
||||
|
||||
// Massively increate timeouts
|
||||
dvars::override::register_int("cl_timeout", 90, 90, 1800, game::DVAR_FLAG_NONE); // Seems unused
|
||||
dvars::override::register_int("sv_timeout", 90, 90, 1800, game::DVAR_FLAG_NONE); // 30 - 0 - 1800
|
||||
dvars::override::register_int("cl_connectTimeout", 120, 120, 1800, game::DVAR_FLAG_NONE); // Seems unused
|
||||
dvars::override::register_int("sv_connectTimeout", 120, 120, 1800, game::DVAR_FLAG_NONE); // 60 - 0 - 1800
|
||||
|
||||
game::Dvar_RegisterInt("scr_game_spectatetype", 1, 0, 99, game::DVAR_FLAG_REPLICATED, "");
|
||||
|
||||
// Prevent clients from ending the game as non host by sending 'end_game' lui notification
|
||||
cmd_lui_notify_server_hook.create(0x1402E9390, cmd_lui_notify_server_stub);
|
||||
}
|
||||
|
||||
static void patch_sp()
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(patches::component)
|
46
src/client/component/ranked.cpp
Normal file
46
src/client/component/ranked.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "dvars.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
|
||||
namespace ranked
|
||||
{
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
dvars::override::register_bool("xblive_privatematch", false, game::DVAR_FLAG_REPLICATED);
|
||||
}
|
||||
|
||||
if (game::environment::is_dedi() && !utils::flags::has_flag("unranked"))
|
||||
{
|
||||
dvars::override::register_bool("xblive_privatematch", false, game::DVAR_FLAG_REPLICATED | game::DVAR_FLAG_WRITE);
|
||||
|
||||
// Some dvar used in gsc
|
||||
game::Dvar_RegisterBool("force_ranking", true, game::DVAR_FLAG_WRITE, "Force ranking");
|
||||
}
|
||||
|
||||
// Always run bots, even if xblive_privatematch is 0
|
||||
utils::hook::set(0x14013A0C0, 0xC301B0); // BG_BotSystemEnabled
|
||||
utils::hook::set(0x140139A60, 0xC301B0); // BG_AISystemEnabled
|
||||
utils::hook::set(0x14013A080, 0xC301B0); // BG_BotFastFileEnabled
|
||||
utils::hook::set(0x14013A200, 0xC301B0); // BG_BotsUsingTeamDifficulty
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(ranked::component)
|
213
src/client/component/rcon.cpp
Normal file
213
src/client/component/rcon.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "network.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace rcon
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool is_redirecting_ = false;
|
||||
game::netadr_s redirect_target_ = {};
|
||||
std::recursive_mutex redirect_lock;
|
||||
|
||||
void setup_redirect(const game::netadr_s& target)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(redirect_lock);
|
||||
|
||||
is_redirecting_ = true;
|
||||
redirect_target_ = target;
|
||||
}
|
||||
|
||||
void clear_redirect()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(redirect_lock);
|
||||
|
||||
is_redirecting_ = false;
|
||||
redirect_target_ = {};
|
||||
}
|
||||
|
||||
void send_rcon_command(const std::string& password, const std::string& data)
|
||||
{
|
||||
// If you are the server, don't bother with rcon and just execute the command
|
||||
if (game::Dvar_FindVar("sv_running")->current.enabled)
|
||||
{
|
||||
game::Cbuf_AddText(0, data.data());
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.empty())
|
||||
{
|
||||
console::info("You must login first to use RCON\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (*reinterpret_cast<std::int32_t*>(0x1417E1690) >= 5) //clientUIActive.connectionState >= CA_CONNECTED
|
||||
{
|
||||
const auto target = *reinterpret_cast<game::netadr_s*>(0x141C7A8FC);
|
||||
const auto buffer = password + " " + data;
|
||||
network::send(target, "rcon", buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("You need to be connected to a server!\n");
|
||||
}
|
||||
}
|
||||
|
||||
std::string build_status_buffer()
|
||||
{
|
||||
const auto sv_maxclients = game::Dvar_FindVar("sv_maxclients");
|
||||
const auto mapname = game::Dvar_FindVar("mapname");
|
||||
|
||||
std::string buffer{};
|
||||
buffer.append(utils::string::va("map: %s\n", mapname->current.string));
|
||||
buffer.append(
|
||||
"num score bot ping guid name address qport\n");
|
||||
buffer.append(
|
||||
"--- ----- --- ---- -------------------------------- ---------------- --------------------- -----\n");
|
||||
|
||||
for (int i = 0; i < sv_maxclients->current.integer; i++)
|
||||
{
|
||||
const auto client = &game::mp::svs_clients[i];
|
||||
|
||||
char clean_name[32] = { 0 };
|
||||
strncpy_s(clean_name, client->name, sizeof(clean_name));
|
||||
game::I_CleanStr(clean_name);
|
||||
|
||||
if (client->header.state >= 1)
|
||||
{
|
||||
buffer.append(utils::string::va("%3i %5i %3s %s %32s %16s %21s %5i\n",
|
||||
i,
|
||||
game::G_GetClientScore(i),
|
||||
game::SV_BotIsBot(i) ? "Yes" : "No",
|
||||
(client->header.state == 2)
|
||||
? "CNCT"
|
||||
: (client->header.state == 1)
|
||||
? "ZMBI"
|
||||
: utils::string::va("%4i", game::SV_GetClientPing(i)),
|
||||
game::SV_GetGuid(i),
|
||||
clean_name,
|
||||
network::net_adr_to_string(client->header.remoteAddress),
|
||||
client->header.remoteAddress.port)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
bool message_redirect(const std::string& message)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(redirect_lock);
|
||||
|
||||
if (is_redirecting_)
|
||||
{
|
||||
network::send(redirect_target_, "print", message);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
game::Dvar_RegisterString("rcon_password", "", game::DvarFlags::DVAR_FLAG_NONE,
|
||||
"The password for remote console");
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
command::add("status", []()
|
||||
{
|
||||
const auto sv_running = game::Dvar_FindVar("sv_running");
|
||||
if (game::VirtualLobby_Loaded() || !sv_running || !sv_running->current.enabled)
|
||||
{
|
||||
console::error("Server is not running\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto status_buffer = build_status_buffer();
|
||||
console::info(status_buffer.data());
|
||||
});
|
||||
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
command::add("rcon", [&](const command::params& params)
|
||||
{
|
||||
static std::string rcon_password{};
|
||||
|
||||
if (params.size() < 2) return;
|
||||
|
||||
const auto operation = params.get(1);
|
||||
if (operation == "login"s)
|
||||
{
|
||||
if (params.size() < 3) return;
|
||||
|
||||
rcon_password = params.get(2);
|
||||
}
|
||||
else if (operation == "logout"s)
|
||||
{
|
||||
rcon_password.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
send_rcon_command(rcon_password, params.join(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
network::on("rcon", [](const game::netadr_s& addr, const std::string_view& data)
|
||||
{
|
||||
const auto message = std::string{data};
|
||||
const auto pos = message.find_first_of(" ");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
console::info("Invalid RCon request from %s\n", network::net_adr_to_string(addr));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto password = message.substr(0, pos);
|
||||
const auto command = message.substr(pos + 1);
|
||||
const auto rcon_password = game::Dvar_FindVar("rcon_password");
|
||||
if (command.empty() || !rcon_password || !rcon_password->current.string || !strlen(
|
||||
rcon_password->current.string))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
setup_redirect(addr);
|
||||
|
||||
if (password != rcon_password->current.string)
|
||||
{
|
||||
console::error("Invalid rcon password\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
command::execute(command, true);
|
||||
}
|
||||
|
||||
clear_redirect();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(rcon::component)
|
6
src/client/component/rcon.hpp
Normal file
6
src/client/component/rcon.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace rcon
|
||||
{
|
||||
bool message_redirect(const std::string& message);
|
||||
}
|
83
src/client/component/redirect.cpp
Normal file
83
src/client/component/redirect.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game_module.hpp"
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace redirect
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void launch_complementary_game(const bool singleplayer, const std::string& mode = "")
|
||||
{
|
||||
const auto self = game_module::get_host_module();
|
||||
|
||||
STARTUPINFOA startup_info;
|
||||
PROCESS_INFORMATION process_info;
|
||||
|
||||
ZeroMemory(&startup_info, sizeof(startup_info));
|
||||
ZeroMemory(&process_info, sizeof(process_info));
|
||||
startup_info.cb = sizeof(startup_info);
|
||||
|
||||
auto* arguments = const_cast<char*>(utils::string::va("%s%s%s", self.get_path().data(),
|
||||
(singleplayer ? " -singleplayer" : " -multiplayer"),
|
||||
(mode.empty() ? "" : (" +"s + mode).data())));
|
||||
CreateProcessA(self.get_path().data(), arguments, nullptr, nullptr, false, NULL, nullptr, nullptr,
|
||||
&startup_info, &process_info);
|
||||
|
||||
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(process_info.hThread);
|
||||
}
|
||||
|
||||
if (process_info.hProcess && process_info.hProcess != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(process_info.hProcess);
|
||||
}
|
||||
}
|
||||
|
||||
HINSTANCE shell_execute_a(const HWND hwnd, const LPCSTR operation, const LPCSTR file, const LPCSTR parameters,
|
||||
const LPCSTR directory, const INT show_cmd)
|
||||
{
|
||||
if (utils::string::starts_with(file, "steam://run/209650/"))
|
||||
{
|
||||
launch_complementary_game(true);
|
||||
return HINSTANCE(33);
|
||||
}
|
||||
else if (utils::string::starts_with(file, "steam://run/209660/"))
|
||||
{
|
||||
std::string mode(file);
|
||||
mode.erase(0, 20);
|
||||
if (!mode.empty())
|
||||
{
|
||||
mode = utils::string::replace(mode, "%2b", ""); // '+'
|
||||
mode = utils::string::replace(mode, "%2", " "); // ' '
|
||||
}
|
||||
|
||||
launch_complementary_game(false, mode);
|
||||
return HINSTANCE(33);
|
||||
}
|
||||
|
||||
return ShellExecuteA(hwnd, operation, file, parameters, directory, show_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
if (library == "SHELL32.dll")
|
||||
{
|
||||
if (function == "ShellExecuteA")
|
||||
{
|
||||
return shell_execute_a;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(redirect::component)
|
82
src/client/component/renderer.cpp
Normal file
82
src/client/component/renderer.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace renderer
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour r_init_draw_method_hook;
|
||||
utils::hook::detour r_update_front_end_dvar_options_hook;
|
||||
|
||||
int get_fullbright_technique()
|
||||
{
|
||||
switch (dvars::r_fullbright->current.integer)
|
||||
{
|
||||
case 3:
|
||||
return 13;
|
||||
case 2:
|
||||
return 25;
|
||||
default:
|
||||
return game::TECHNIQUE_UNLIT;
|
||||
}
|
||||
}
|
||||
|
||||
void gfxdrawmethod()
|
||||
{
|
||||
game::gfxDrawMethod->drawScene = game::GFX_DRAW_SCENE_STANDARD;
|
||||
game::gfxDrawMethod->baseTechType = dvars::r_fullbright->current.enabled ? get_fullbright_technique() : game::TECHNIQUE_LIT;
|
||||
game::gfxDrawMethod->emissiveTechType = dvars::r_fullbright->current.enabled ? get_fullbright_technique() : game::TECHNIQUE_EMISSIVE;
|
||||
game::gfxDrawMethod->forceTechType = dvars::r_fullbright->current.enabled ? get_fullbright_technique() : 182;
|
||||
}
|
||||
|
||||
void r_init_draw_method_stub()
|
||||
{
|
||||
gfxdrawmethod();
|
||||
}
|
||||
|
||||
bool r_update_front_end_dvar_options_stub()
|
||||
{
|
||||
if (dvars::r_fullbright->modified)
|
||||
{
|
||||
game::Dvar_ClearModified(dvars::r_fullbright);
|
||||
game::R_SyncRenderThread();
|
||||
|
||||
gfxdrawmethod();
|
||||
}
|
||||
|
||||
return r_update_front_end_dvar_options_hook.invoke<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dvars::r_fullbright = game::Dvar_RegisterInt("r_fullbright", 0, 0, 3, game::DVAR_FLAG_SAVED, "Toggles rendering without lighting");
|
||||
|
||||
r_init_draw_method_hook.create(SELECT_VALUE(0x14046C150, 0x140588B00), &r_init_draw_method_stub);
|
||||
r_update_front_end_dvar_options_hook.create(SELECT_VALUE(0x1404A5330, 0x1405C3AE0), &r_update_front_end_dvar_options_stub);
|
||||
|
||||
// use "saved" flags for "r_normalMap"
|
||||
utils::hook::set<uint8_t>(SELECT_VALUE(0x14047E0B8, 0x14059AD71), game::DVAR_FLAG_SAVED);
|
||||
|
||||
// use "saved" flags for "r_specularMap"
|
||||
utils::hook::set<uint8_t>(SELECT_VALUE(0x14047E0DA, 0x14059AD99), game::DVAR_FLAG_SAVED);
|
||||
|
||||
// use "saved" flags for "r_specOccMap"
|
||||
utils::hook::set<uint8_t>(SELECT_VALUE(0x14047E0FC, 0x14059ADC1), game::DVAR_FLAG_SAVED);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(renderer::component)
|
70
src/client/component/resources.cpp
Normal file
70
src/client/component/resources.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include <utils/nt.hpp>
|
||||
|
||||
namespace resources
|
||||
{
|
||||
namespace
|
||||
{
|
||||
HICON icon;
|
||||
HANDLE splash, logo;
|
||||
|
||||
HANDLE WINAPI load_image_a(const HINSTANCE handle, LPCSTR name, const UINT type, const int c_x, const int c_y,
|
||||
const UINT load)
|
||||
{
|
||||
const utils::nt::library self;
|
||||
if (!IS_INTRESOURCE(name) && name == "logo.bmp"s) return logo;
|
||||
if (self.get_handle() == handle && name == LPCSTR(0x64)) return splash;
|
||||
|
||||
return LoadImageA(handle, name, type, c_x, c_y, load);
|
||||
}
|
||||
|
||||
HICON WINAPI load_icon_a(const HINSTANCE handle, const LPCSTR name)
|
||||
{
|
||||
const utils::nt::library self;
|
||||
if (self.get_handle() == handle && name == LPCSTR(2)) return icon;
|
||||
|
||||
return LoadIconA(handle, name);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
~component() override
|
||||
{
|
||||
if (icon) DestroyIcon(icon);
|
||||
if (logo) DeleteObject(logo);
|
||||
if (splash) DeleteObject(splash);
|
||||
}
|
||||
|
||||
void post_start() override
|
||||
{
|
||||
const utils::nt::library self;
|
||||
|
||||
icon = LoadIconA(self.get_handle(), MAKEINTRESOURCEA(ID_ICON));
|
||||
logo = LoadImageA(self.get_handle(), MAKEINTRESOURCEA(IMAGE_LOGO), 0, 0, 0, LR_COPYFROMRESOURCE);
|
||||
splash = LoadImageA(self.get_handle(), MAKEINTRESOURCEA(IMAGE_SPLASH), 0, 0, 0, LR_COPYFROMRESOURCE);
|
||||
}
|
||||
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
if (library == "USER32.dll")
|
||||
{
|
||||
if (function == "LoadIconA")
|
||||
{
|
||||
return load_icon_a;
|
||||
}
|
||||
|
||||
if (function == "LoadImageA")
|
||||
{
|
||||
return load_image_a;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(resources::component)
|
197
src/client/component/scheduler.cpp
Normal file
197
src/client/component/scheduler.cpp
Normal file
@ -0,0 +1,197 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/thread.hpp>
|
||||
|
||||
namespace scheduler
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct task
|
||||
{
|
||||
std::function<bool()> handler{};
|
||||
std::chrono::milliseconds interval{};
|
||||
std::chrono::high_resolution_clock::time_point last_call{};
|
||||
};
|
||||
|
||||
using task_list = std::vector<task>;
|
||||
|
||||
class task_pipeline
|
||||
{
|
||||
public:
|
||||
void add(task&& task)
|
||||
{
|
||||
new_callbacks_.access([&task](task_list& tasks)
|
||||
{
|
||||
tasks.emplace_back(std::move(task));
|
||||
});
|
||||
}
|
||||
|
||||
void execute()
|
||||
{
|
||||
callbacks_.access([&](task_list& tasks)
|
||||
{
|
||||
this->merge_callbacks();
|
||||
|
||||
for (auto i = tasks.begin(); i != tasks.end();)
|
||||
{
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
const auto diff = now - i->last_call;
|
||||
|
||||
if (diff < i->interval)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
i->last_call = now;
|
||||
|
||||
const auto res = i->handler();
|
||||
if (res == cond_end)
|
||||
{
|
||||
i = tasks.erase(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
++i;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
utils::concurrency::container<task_list> new_callbacks_;
|
||||
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
|
||||
|
||||
void merge_callbacks()
|
||||
{
|
||||
callbacks_.access([&](task_list& tasks)
|
||||
{
|
||||
new_callbacks_.access([&](task_list& new_tasks)
|
||||
{
|
||||
tasks.insert(tasks.end(), std::move_iterator<task_list::iterator>(new_tasks.begin()), std::move_iterator<task_list::iterator>(new_tasks.end()));
|
||||
new_tasks = {};
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
volatile bool kill = false;
|
||||
std::thread thread;
|
||||
task_pipeline pipelines[pipeline::count];
|
||||
utils::hook::detour r_end_frame_hook;
|
||||
|
||||
void execute(const pipeline type)
|
||||
{
|
||||
assert(type >= 0 && type < pipeline::count);
|
||||
pipelines[type].execute();
|
||||
}
|
||||
|
||||
void r_end_frame_stub()
|
||||
{
|
||||
execute(pipeline::renderer);
|
||||
r_end_frame_hook.invoke<void>();
|
||||
}
|
||||
|
||||
void server_frame_stub()
|
||||
{
|
||||
game::G_Glass_Update();
|
||||
execute(pipeline::server);
|
||||
}
|
||||
|
||||
void main_frame_stub()
|
||||
{
|
||||
execute(pipeline::main);
|
||||
game::Com_Frame_Try_Block_Function();
|
||||
}
|
||||
}
|
||||
|
||||
void schedule(const std::function<bool()>& callback, const pipeline type,
|
||||
const std::chrono::milliseconds delay)
|
||||
{
|
||||
assert(type >= 0 && type < pipeline::count);
|
||||
|
||||
task task;
|
||||
task.handler = callback;
|
||||
task.interval = delay;
|
||||
task.last_call = std::chrono::high_resolution_clock::now();
|
||||
|
||||
pipelines[type].add(std::move(task));
|
||||
}
|
||||
|
||||
void loop(const std::function<void()>& callback, const pipeline type,
|
||||
const std::chrono::milliseconds delay)
|
||||
{
|
||||
schedule([callback]()
|
||||
{
|
||||
callback();
|
||||
return cond_continue;
|
||||
}, type, delay);
|
||||
}
|
||||
|
||||
void once(const std::function<void()>& callback, const pipeline type,
|
||||
const std::chrono::milliseconds delay)
|
||||
{
|
||||
schedule([callback]()
|
||||
{
|
||||
callback();
|
||||
return cond_end;
|
||||
}, type, delay);
|
||||
}
|
||||
|
||||
void on_game_initialized(const std::function<void()>& callback, const pipeline type,
|
||||
const std::chrono::milliseconds delay)
|
||||
{
|
||||
schedule([=]()
|
||||
{
|
||||
const auto dw_init = game::environment::is_sp() ? true : game::Live_SyncOnlineDataFlags(0) == 0;
|
||||
if (dw_init && game::Sys_IsDatabaseReady2())
|
||||
{
|
||||
once(callback, type, delay);
|
||||
return cond_end;
|
||||
}
|
||||
|
||||
return cond_continue;
|
||||
}, pipeline::main);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_start() override
|
||||
{
|
||||
thread = utils::thread::create_named_thread("Async Scheduler", []()
|
||||
{
|
||||
while (!kill)
|
||||
{
|
||||
execute(pipeline::async);
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
r_end_frame_hook.create(SELECT_VALUE(0x1404A3E20, 0x1405C25B0), r_end_frame_stub);
|
||||
|
||||
utils::hook::call(SELECT_VALUE(0x1402F7DC2, 0x1403CEEE2), main_frame_stub);
|
||||
utils::hook::call(SELECT_VALUE(0x140228647, 0x1402F8879), server_frame_stub);
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
kill = true;
|
||||
if (thread.joinable())
|
||||
{
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(scheduler::component)
|
33
src/client/component/scheduler.hpp
Normal file
33
src/client/component/scheduler.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
namespace scheduler
|
||||
{
|
||||
enum pipeline
|
||||
{
|
||||
// Asynchronuous pipeline, disconnected from the game
|
||||
async = 0,
|
||||
|
||||
// The game's rendering pipeline
|
||||
renderer,
|
||||
|
||||
// The game's server thread
|
||||
server,
|
||||
|
||||
// The game's main thread
|
||||
main,
|
||||
|
||||
count,
|
||||
};
|
||||
|
||||
static const bool cond_continue = false;
|
||||
static const bool cond_end = true;
|
||||
|
||||
void schedule(const std::function<bool()>& callback, pipeline type = pipeline::async,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
void loop(const std::function<void()>& callback, pipeline type = pipeline::async,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
void once(const std::function<void()>& callback, pipeline type = pipeline::async,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
void on_game_initialized(const std::function<void()>& callback, pipeline type = pipeline::async,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
}
|
193
src/client/component/scripting.cpp
Normal file
193
src/client/component/scripting.cpp
Normal file
@ -0,0 +1,193 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "game/scripting/functions.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include "gsc/script_loading.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
|
||||
std::unordered_map<std::string, std::vector<std::pair<std::string, const char*>>> script_function_table_sort;
|
||||
std::unordered_map<const char*, std::pair<std::string, std::string>> script_function_table_rev;
|
||||
|
||||
std::string current_file;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour vm_notify_hook;
|
||||
utils::hook::detour scr_load_level_hook;
|
||||
utils::hook::detour g_shutdown_game_hook;
|
||||
|
||||
utils::hook::detour scr_set_thread_position_hook;
|
||||
utils::hook::detour process_script_hook;
|
||||
utils::hook::detour sl_get_canonical_string_hook;
|
||||
|
||||
std::string current_script_file;
|
||||
std::uint32_t current_file_id = 0;
|
||||
|
||||
std::unordered_map<unsigned int, std::string> canonical_string_table;
|
||||
|
||||
std::vector<std::function<void(int)>> shutdown_callbacks;
|
||||
std::vector<std::function<void()>> init_callbacks;
|
||||
|
||||
void scr_load_level_stub()
|
||||
{
|
||||
scr_load_level_hook.invoke<void>();
|
||||
if (!game::VirtualLobby_Loaded())
|
||||
{
|
||||
for (const auto& callback : init_callbacks)
|
||||
{
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void g_shutdown_game_stub(const int free_scripts)
|
||||
{
|
||||
if (free_scripts)
|
||||
{
|
||||
script_function_table_sort.clear();
|
||||
script_function_table.clear();
|
||||
script_function_table_rev.clear();
|
||||
canonical_string_table.clear();
|
||||
}
|
||||
|
||||
for (const auto& callback : shutdown_callbacks)
|
||||
{
|
||||
callback(free_scripts);
|
||||
}
|
||||
|
||||
return g_shutdown_game_hook.invoke<void>(free_scripts);
|
||||
}
|
||||
|
||||
void process_script_stub(const char* filename)
|
||||
{
|
||||
current_script_file = filename;
|
||||
|
||||
const auto file_id = atoi(filename);
|
||||
if (file_id)
|
||||
{
|
||||
current_file_id = static_cast<std::uint16_t>(file_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
current_file_id = 0;
|
||||
current_file = filename;
|
||||
}
|
||||
|
||||
process_script_hook.invoke<void>(filename);
|
||||
}
|
||||
|
||||
void add_function_sort(unsigned int id, const char* pos)
|
||||
{
|
||||
std::string filename = current_file;
|
||||
if (current_file_id)
|
||||
{
|
||||
filename = get_token(current_file_id);
|
||||
}
|
||||
|
||||
if (!script_function_table_sort.contains(filename))
|
||||
{
|
||||
const auto script = gsc::find_script(game::ASSET_TYPE_SCRIPTFILE, current_script_file.data(), false);
|
||||
if (script)
|
||||
{
|
||||
const auto* end = &script->bytecode[script->bytecodeLen];
|
||||
script_function_table_sort[filename].emplace_back("__end__", reinterpret_cast<const char*>(end));
|
||||
}
|
||||
}
|
||||
|
||||
const auto name = scripting::get_token(id);
|
||||
auto& itr = script_function_table_sort[filename];
|
||||
itr.insert(itr.end() - 1, {name, pos});
|
||||
}
|
||||
|
||||
void add_function(const std::string& file, unsigned int id, const char* pos)
|
||||
{
|
||||
const auto name = get_token(id);
|
||||
script_function_table[file][name] = pos;
|
||||
script_function_table_rev[pos] = {file, name};
|
||||
}
|
||||
|
||||
void scr_set_thread_position_stub(unsigned int thread_name, const char* code_pos)
|
||||
{
|
||||
add_function_sort(thread_name, code_pos);
|
||||
|
||||
if (current_file_id)
|
||||
{
|
||||
const auto name = get_token(current_file_id);
|
||||
add_function(name, thread_name, code_pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
add_function(current_file, thread_name, code_pos);
|
||||
}
|
||||
|
||||
scr_set_thread_position_hook.invoke<void>(thread_name, code_pos);
|
||||
}
|
||||
|
||||
unsigned int sl_get_canonical_string_stub(const char* str)
|
||||
{
|
||||
const auto result = sl_get_canonical_string_hook.invoke<unsigned int>(str);
|
||||
canonical_string_table[result] = str;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_token(unsigned int id)
|
||||
{
|
||||
if (const auto itr = canonical_string_table.find(id); itr != canonical_string_table.end())
|
||||
{
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
return find_token(id);
|
||||
}
|
||||
|
||||
void on_shutdown(const std::function<void(int)>& callback)
|
||||
{
|
||||
shutdown_callbacks.push_back(callback);
|
||||
}
|
||||
|
||||
void on_init(const std::function<void()>& callback)
|
||||
{
|
||||
init_callbacks.push_back(callback);
|
||||
}
|
||||
|
||||
std::optional<std::string> get_canonical_string(const unsigned int id)
|
||||
{
|
||||
if (const auto itr = canonical_string_table.find(id); itr != canonical_string_table.end())
|
||||
{
|
||||
return {itr->second};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// SP address is wrong, but should be ok
|
||||
scr_load_level_hook.create(SELECT_VALUE(0x140005260, 0x140325B90), &scr_load_level_stub);
|
||||
g_shutdown_game_hook.create(SELECT_VALUE(0x140228BA0, 0x1402F8C10), &g_shutdown_game_stub);
|
||||
|
||||
scr_set_thread_position_hook.create(SELECT_VALUE(0x1403115E0, 0x1403EDB10), &scr_set_thread_position_stub);
|
||||
process_script_hook.create(SELECT_VALUE(0x14031AB30, 0x1403F7300), &process_script_stub);
|
||||
sl_get_canonical_string_hook.create(game::SL_GetCanonicalString, &sl_get_canonical_string_stub);
|
||||
|
||||
scheduler::loop([]
|
||||
{
|
||||
}, scheduler::pipeline::server);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(scripting::component)
|
16
src/client/component/scripting.hpp
Normal file
16
src/client/component/scripting.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
extern std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
|
||||
extern std::unordered_map<std::string, std::vector<std::pair<std::string, const char*>>> script_function_table_sort;
|
||||
extern std::unordered_map<const char*, std::pair<std::string, std::string>> script_function_table_rev;
|
||||
|
||||
extern std::string current_file;
|
||||
|
||||
void on_shutdown(const std::function<void(int)>& callback);
|
||||
void on_init(const std::function<void()>& callback);
|
||||
|
||||
std::optional<std::string> get_canonical_string(unsigned int id);
|
||||
std::string get_token(unsigned int id);
|
||||
}
|
77
src/client/component/security.cpp
Normal file
77
src/client/component/security.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "console.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace security
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void set_cached_playerdata_stub(const int localclient, const int index1, const int index2)
|
||||
{
|
||||
if (index1 >= 0 && index1 < 18 && index2 >= 0 && index2 < 42)
|
||||
{
|
||||
reinterpret_cast<void(*)(int, int, int)>(0x140536A60)(localclient, index1, index2);
|
||||
}
|
||||
}
|
||||
|
||||
void remap_cached_entities(game::mp::cachedSnapshot_t& snapshot)
|
||||
{
|
||||
static bool printed = false;
|
||||
if(snapshot.num_clients > 1200 && !printed)
|
||||
{
|
||||
printed = true;
|
||||
console::info("Too many entities (%d)... remapping!\n", snapshot.num_clients);
|
||||
}
|
||||
|
||||
snapshot.num_clients = std::min(snapshot.num_clients, 1200);
|
||||
}
|
||||
|
||||
void remap_cached_entities_stub(utils::hook::assembler& a)
|
||||
{
|
||||
a.pushad64();
|
||||
|
||||
a.mov(rcx, rbx);
|
||||
a.call_aligned(remap_cached_entities);
|
||||
|
||||
a.popad64();
|
||||
a.jmp(0x14044DE51);
|
||||
}
|
||||
|
||||
void sv_execute_client_message_stub(game::mp::client_t* client, game::msg_t* msg)
|
||||
{
|
||||
if ((client->reliableSequence - client->reliableAcknowledge) < 0)
|
||||
{
|
||||
client->reliableAcknowledge = client->reliableSequence;
|
||||
console::info("Negative reliableAcknowledge from %s - cl->reliableSequence is %i, reliableAcknowledge is %i\n",
|
||||
client->name, client->reliableSequence, client->reliableAcknowledge);
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::invoke<void>(0x14043AA90, client, msg);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp()) return;
|
||||
|
||||
// Patch vulnerability in PlayerCards_SetCachedPlayerData
|
||||
utils::hook::call(0x1401BB909, set_cached_playerdata_stub);
|
||||
|
||||
// Patch entity overflow
|
||||
utils::hook::jump(0x14044DE3A, assemble(remap_cached_entities_stub), true);
|
||||
|
||||
// It is possible to make the server hang if left unchecked
|
||||
utils::hook::call(0x140443051, sv_execute_client_message_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(security::component)
|
433
src/client/component/server_list.cpp
Normal file
433
src/client/component/server_list.cpp
Normal file
@ -0,0 +1,433 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "server_list.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
#include "network.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "party.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace server_list
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const int server_limit = 14;
|
||||
|
||||
struct server_info
|
||||
{
|
||||
// gotta add more to this
|
||||
int clients;
|
||||
int max_clients;
|
||||
int bots;
|
||||
int ping;
|
||||
std::string host_name;
|
||||
std::string map_name;
|
||||
std::string game_type;
|
||||
game::CodPlayMode play_mode;
|
||||
char in_game;
|
||||
game::netadr_s address;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
game::netadr_s address{};
|
||||
volatile bool requesting = false;
|
||||
std::unordered_map<game::netadr_s, int> queued_servers{};
|
||||
} master_state;
|
||||
|
||||
std::mutex mutex;
|
||||
std::vector<server_info> servers;
|
||||
|
||||
size_t server_list_page = 0;
|
||||
volatile bool update_server_list = false;
|
||||
std::chrono::high_resolution_clock::time_point last_scroll{};
|
||||
|
||||
size_t get_page_count()
|
||||
{
|
||||
const auto count = servers.size() / server_limit;
|
||||
return count + (servers.size() % server_limit > 0);
|
||||
}
|
||||
|
||||
size_t get_page_base_index()
|
||||
{
|
||||
return server_list_page * server_limit;
|
||||
}
|
||||
|
||||
void refresh_server_list()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
servers.clear();
|
||||
master_state.queued_servers.clear();
|
||||
server_list_page = 0;
|
||||
}
|
||||
|
||||
party::reset_connect_state();
|
||||
|
||||
if (get_master_server(master_state.address))
|
||||
{
|
||||
master_state.requesting = true;
|
||||
network::send(master_state.address, "getservers", utils::string::va("S1 %i full empty", PROTOCOL));
|
||||
}
|
||||
}
|
||||
|
||||
void join_server(int, int, const int index)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
|
||||
const auto i = static_cast<size_t>(index) + get_page_base_index();
|
||||
if (i < servers.size())
|
||||
{
|
||||
static auto last_index = ~0ull;
|
||||
if (last_index != i)
|
||||
{
|
||||
last_index = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Connecting to (%d - %zu): %s\n", index, i, servers[i].host_name.data());
|
||||
party::connect(servers[i].address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void trigger_refresh()
|
||||
{
|
||||
update_server_list = true;
|
||||
}
|
||||
|
||||
int ui_feeder_count()
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
if (update_server_list)
|
||||
{
|
||||
update_server_list = false;
|
||||
return 0;
|
||||
}
|
||||
const auto count = static_cast<int>(servers.size());
|
||||
const auto index = get_page_base_index();
|
||||
const auto diff = count - index;
|
||||
return diff > server_limit ? server_limit : static_cast<int>(diff);
|
||||
}
|
||||
|
||||
const char* ui_feeder_item_text(int /*localClientNum*/, void* /*a2*/, void* /*a3*/, const int index,
|
||||
const int column)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
|
||||
const auto i = get_page_base_index() + index;
|
||||
|
||||
if (i >= servers.size())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (column == 0)
|
||||
{
|
||||
return servers[i].host_name.empty() ? "" : utils::string::va("%s", servers[i].host_name.data());
|
||||
}
|
||||
|
||||
if (column == 1)
|
||||
{
|
||||
return servers[i].map_name.empty() ? "" : utils::string::va("%s", servers[i].map_name.data());
|
||||
}
|
||||
|
||||
if (column == 2)
|
||||
{
|
||||
return servers[i].game_type.empty() ? "" : utils::string::va("%s", servers[i].game_type.data());
|
||||
}
|
||||
|
||||
if (column == 3)
|
||||
{
|
||||
return utils::string::va("%d/%d [%d]", servers[i].clients, servers[i].max_clients,
|
||||
servers[i].bots);
|
||||
}
|
||||
|
||||
if (column == 4)
|
||||
{
|
||||
return servers[i].ping ? utils::string::va("%d", servers[i].ping) : "";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void sort_serverlist()
|
||||
{
|
||||
std::ranges::stable_sort(servers, [](const server_info& a, const server_info& b)
|
||||
{
|
||||
if (a.clients == b.clients)
|
||||
{
|
||||
return a.ping < b.ping;
|
||||
}
|
||||
|
||||
return a.clients > b.clients;
|
||||
});
|
||||
}
|
||||
|
||||
void insert_server(server_info&& server)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
servers.emplace_back(std::move(server));
|
||||
sort_serverlist();
|
||||
trigger_refresh();
|
||||
}
|
||||
|
||||
void do_frame_work()
|
||||
{
|
||||
auto& queue = master_state.queued_servers;
|
||||
if (queue.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
|
||||
size_t queried_servers = 0;
|
||||
const size_t query_limit = 3;
|
||||
|
||||
for (auto i = queue.begin(); i != queue.end();)
|
||||
{
|
||||
if (i->second)
|
||||
{
|
||||
const auto now = game::Sys_Milliseconds();
|
||||
if (now - i->second > 10'000)
|
||||
{
|
||||
i = queue.erase(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (queried_servers++ < query_limit)
|
||||
{
|
||||
i->second = game::Sys_Milliseconds();
|
||||
network::send(i->first, "getInfo", utils::cryptography::random::get_challenge());
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_server_list_open()
|
||||
{
|
||||
return game::Menu_IsMenuOpenAndVisible(0, "menu_systemlink_join");
|
||||
}
|
||||
|
||||
bool is_scrolling_disabled()
|
||||
{
|
||||
return update_server_list || (std::chrono::high_resolution_clock::now() - last_scroll) < 500ms;
|
||||
}
|
||||
|
||||
bool scroll_down()
|
||||
{
|
||||
if (!is_server_list_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_scrolling_disabled() && server_list_page + 1 < get_page_count())
|
||||
{
|
||||
last_scroll = std::chrono::high_resolution_clock::now();
|
||||
++server_list_page;
|
||||
trigger_refresh();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool scroll_up()
|
||||
{
|
||||
if (!is_server_list_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_scrolling_disabled() && server_list_page > 0)
|
||||
{
|
||||
last_scroll = std::chrono::high_resolution_clock::now();
|
||||
--server_list_page;
|
||||
trigger_refresh();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void resize_host_name(std::string& name)
|
||||
{
|
||||
name = utils::string::split(name, '\n').front();
|
||||
|
||||
game::Font_s* font;
|
||||
if (game::Com_GetCurrentCoDPlayMode() == game::CODPLAYMODE_ZOMBIES)
|
||||
{
|
||||
font = game::R_RegisterFont("fonts/zmBodyFont");
|
||||
}
|
||||
else
|
||||
{
|
||||
font = game::R_RegisterFont("fonts/bodyFont");
|
||||
}
|
||||
auto text_size = game::UI_TextWidth(name.data(), 32, font, 1.0f);
|
||||
|
||||
while (text_size > 450)
|
||||
{
|
||||
text_size = game::UI_TextWidth(name.data(), 32, font, 1.0f);
|
||||
name.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
void lui_open_menu_stub(int /*controllerIndex*/, const char* /*menu*/, int /*a3*/, int /*a4*/,
|
||||
unsigned int /*a5*/)
|
||||
{
|
||||
refresh_server_list();
|
||||
game::Cmd_ExecuteSingleCommand(0, 0, "lui_open menu_systemlink_join\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool sl_key_event(const int key, const int down)
|
||||
{
|
||||
if (down)
|
||||
{
|
||||
if (key == game::keyNum_t::K_MWHEELUP)
|
||||
{
|
||||
return !scroll_up();
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_MWHEELDOWN)
|
||||
{
|
||||
return !scroll_down();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_master_server(game::netadr_s& address)
|
||||
{
|
||||
return game::NET_StringToAdr("server.alterware.dev:20810", &address);
|
||||
}
|
||||
|
||||
void handle_info_response(const game::netadr_s& address, const utils::info_string& info)
|
||||
{
|
||||
// Don't show servers that aren't dedicated!
|
||||
const auto dedicated = info.get("dedicated");
|
||||
if (dedicated != "1"s)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't show servers that aren't running!
|
||||
const auto sv_running = info.get("sv_running");
|
||||
if (sv_running != "1"s)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle servers of the same playmode!
|
||||
const auto playmode = static_cast<game::CodPlayMode>(std::atoi(info.get("playmode").data()));
|
||||
if (game::Com_GetCurrentCoDPlayMode() != playmode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int start_time{};
|
||||
const auto now = game::Sys_Milliseconds();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
const auto entry = master_state.queued_servers.find(address);
|
||||
|
||||
if (entry == master_state.queued_servers.end() || !entry->second)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
start_time = entry->second;
|
||||
master_state.queued_servers.erase(entry);
|
||||
}
|
||||
|
||||
server_info server{};
|
||||
server.address = address;
|
||||
server.host_name = info.get("hostname");
|
||||
server.map_name = game::UI_GetMapDisplayName(info.get("mapname").data());
|
||||
server.game_type = game::UI_GetGameTypeDisplayName(info.get("gametype").data());
|
||||
server.play_mode = playmode;
|
||||
server.clients = std::atoi(info.get("clients").data());
|
||||
server.max_clients = std::atoi(info.get("sv_maxclients").data());
|
||||
server.bots = std::atoi(info.get("bots").data());
|
||||
server.ping = std::min(now - start_time, 999);
|
||||
|
||||
server.in_game = 1;
|
||||
|
||||
resize_host_name(server.host_name);
|
||||
|
||||
insert_server(std::move(server));
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_mp()) return;
|
||||
|
||||
// replace UI_RunMenuScript call in LUI_CoD_LuaCall_RefreshServerList to our refresh_servers
|
||||
utils::hook::call(0x1400F5AA1, &refresh_server_list);
|
||||
utils::hook::call(0x1400F5F26, &join_server);
|
||||
utils::hook::nop(0x1400F5F45, 5);
|
||||
|
||||
// do feeder stuff
|
||||
utils::hook::call(0x1400F5B55, &ui_feeder_count);
|
||||
utils::hook::call(0x1400F5D35, &ui_feeder_item_text);
|
||||
|
||||
scheduler::loop(do_frame_work, scheduler::pipeline::main);
|
||||
|
||||
network::on("getServersResponse", [](const game::netadr_s& target, const std::string_view& data)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
if (!master_state.requesting || master_state.address != target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
master_state.requesting = false;
|
||||
|
||||
std::optional<size_t> start{};
|
||||
for (size_t i = 0; i + 6 < data.size(); ++i)
|
||||
{
|
||||
if (data[i + 6] == '\\')
|
||||
{
|
||||
start.emplace(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!start.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto i = start.value(); i + 6 < data.size(); i += 7)
|
||||
{
|
||||
if (data[i + 6] != '\\')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
game::netadr_s address{};
|
||||
address.type = game::NA_IP;
|
||||
address.localNetID = game::NS_CLIENT1;
|
||||
memcpy(&address.ip[0], data.data() + i + 0, 4);
|
||||
memcpy(&address.port, data.data() + i + 4, 2);
|
||||
|
||||
master_state.queued_servers[address] = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(server_list::component)
|
10
src/client/component/server_list.hpp
Normal file
10
src/client/component/server_list.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <utils/info_string.hpp>
|
||||
|
||||
namespace server_list
|
||||
{
|
||||
bool get_master_server(game::netadr_s& address);
|
||||
void handle_info_response(const game::netadr_s& address, const utils::info_string& info);
|
||||
|
||||
bool sl_key_event(int key, int down);
|
||||
}
|
53
src/client/component/slowmotion.cpp
Normal file
53
src/client/component/slowmotion.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace slowmotion
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void scr_cmd_set_slow_motion()
|
||||
{
|
||||
if (game::Scr_GetNumParam() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int duration = 1000;
|
||||
float end = 1.0f;
|
||||
const float start = game::Scr_GetFloat(0);
|
||||
|
||||
if (game::Scr_GetNumParam() >= 2)
|
||||
{
|
||||
end = game::Scr_GetFloat(1u);
|
||||
}
|
||||
|
||||
if (game::Scr_GetNumParam() >= 3)
|
||||
{
|
||||
duration = static_cast<int>(game::Scr_GetFloat(2u) * 1000.0f);
|
||||
}
|
||||
|
||||
game::SV_SetConfigstring(10, utils::string::va("%i %i %g %g", *game::mp::gameTime, duration, start, end));
|
||||
game::Com_SetSlowMotion(start, end, duration);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::jump(0x14030EF90, scr_cmd_set_slow_motion);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(slowmotion::component)
|
141
src/client/component/splash.cpp
Normal file
141
src/client/component/splash.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "game_module.hpp"
|
||||
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace splash
|
||||
{
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_start() override
|
||||
{
|
||||
const utils::nt::library self;
|
||||
image_ = LoadImageA(self, MAKEINTRESOURCE(IMAGE_SPLASH), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
|
||||
}
|
||||
|
||||
void post_load() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->show();
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
// Disable native splash screen
|
||||
utils::hook::nop(SELECT_VALUE(0x14038D88C, 0x1404D74E3), 5);
|
||||
utils::hook::jump(SELECT_VALUE(0x14038ECD0, 0x1404D8A70), destroy_stub);
|
||||
utils::hook::jump(SELECT_VALUE(0x14038ED10, 0x1404D8AB0), destroy_stub);
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
this->destroy();
|
||||
|
||||
MSG msg;
|
||||
while (this->window_ && IsWindow(this->window_))
|
||||
{
|
||||
if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE))
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
}
|
||||
|
||||
this->window_ = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
HWND window_{};
|
||||
HANDLE image_{};
|
||||
|
||||
static void destroy_stub()
|
||||
{
|
||||
component_loader::get<component>()->destroy();
|
||||
}
|
||||
|
||||
void destroy() const
|
||||
{
|
||||
if (this->window_ && IsWindow(this->window_))
|
||||
{
|
||||
ShowWindow(this->window_, SW_HIDE);
|
||||
DestroyWindow(this->window_);
|
||||
UnregisterClassA("s1-mod Splash Screen", utils::nt::library{});
|
||||
}
|
||||
}
|
||||
|
||||
void show()
|
||||
{
|
||||
WNDCLASSA wnd_class;
|
||||
|
||||
const auto self = game_module::get_host_module();
|
||||
|
||||
wnd_class.style = CS_DROPSHADOW;
|
||||
wnd_class.cbClsExtra = 0;
|
||||
wnd_class.cbWndExtra = 0;
|
||||
wnd_class.lpszMenuName = nullptr;
|
||||
wnd_class.lpfnWndProc = DefWindowProcA;
|
||||
wnd_class.hInstance = self;
|
||||
wnd_class.hIcon = LoadIconA(self, reinterpret_cast<LPCSTR>(102));
|
||||
wnd_class.hCursor = LoadCursorA(nullptr, IDC_APPSTARTING);
|
||||
wnd_class.hbrBackground = reinterpret_cast<HBRUSH>(6);
|
||||
wnd_class.lpszClassName = "s1-mod Splash Screen";
|
||||
|
||||
if (RegisterClassA(&wnd_class))
|
||||
{
|
||||
const auto x_pixels = GetSystemMetrics(SM_CXFULLSCREEN);
|
||||
const auto y_pixels = GetSystemMetrics(SM_CYFULLSCREEN);
|
||||
|
||||
if (image_)
|
||||
{
|
||||
this->window_ = CreateWindowExA(WS_EX_APPWINDOW, "s1-mod Splash Screen", "s1-mod",
|
||||
WS_POPUP | WS_SYSMENU,
|
||||
(x_pixels - 320) / 2, (y_pixels - 100) / 2, 320, 100, nullptr,
|
||||
nullptr,
|
||||
self, nullptr);
|
||||
|
||||
if (this->window_)
|
||||
{
|
||||
auto* const image_window = CreateWindowExA(0, "Static", nullptr, WS_CHILD | WS_VISIBLE | 0xEu,
|
||||
0, 0,
|
||||
320, 100, this->window_, nullptr, self, nullptr);
|
||||
if (image_window)
|
||||
{
|
||||
RECT rect;
|
||||
SendMessageA(image_window, 0x172u, 0, reinterpret_cast<LPARAM>(image_));
|
||||
GetWindowRect(image_window, &rect);
|
||||
|
||||
const int width = rect.right - rect.left;
|
||||
rect.left = (x_pixels - width) / 2;
|
||||
|
||||
const int height = rect.bottom - rect.top;
|
||||
rect.top = (y_pixels - height) / 2;
|
||||
|
||||
rect.right = rect.left + width;
|
||||
rect.bottom = rect.top + height;
|
||||
AdjustWindowRect(&rect, WS_CHILD | WS_VISIBLE | 0xEu, 0);
|
||||
SetWindowPos(this->window_, nullptr, rect.left, rect.top, rect.right - rect.left,
|
||||
rect.bottom - rect.top, SWP_NOZORDER);
|
||||
|
||||
ShowWindow(this->window_, SW_SHOW);
|
||||
UpdateWindow(this->window_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(splash::component)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user