]> git.dpolakovic.space - cheapo-linux-port/commitdiff
feat: ported to Linux from Mac OS. master 1.0.0
authorDavid Polakovic
Sat, 31 Oct 2026 14:44:26 +0000 (15:44 +0100)
committerDavid Polakovic
Sat, 31 Oct 2026 14:48:04 +0000 (15:48 +0100)
-builds on GCC 14+
-add Makefile
-updated README

17 files changed:
Cheapo.xcodeproj/project.pbxproj [new file with mode: 0644]
Cheapo.xcodeproj/project.xcworkspace/contents.xcworkspacedata [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
Makefile.win [new file with mode: 0755]
README.md [new file with mode: 0644]
cmd.c [new file with mode: 0755]
compress.c [new file with mode: 0755]
main.c [new file with mode: 0755]
misc.c [new file with mode: 0755]
mkalpha [new file with mode: 0755]
mklinux [new file with mode: 0755]
mksparc [new file with mode: 0755]
mkwin.bat [new file with mode: 0755]
proxy.c [new file with mode: 0755]
proxy.h [new file with mode: 0755]
zipit.bat [new file with mode: 0755]

diff --git a/Cheapo.xcodeproj/project.pbxproj b/Cheapo.xcodeproj/project.pbxproj
new file mode 100644 (file)
index 0000000..164c982
--- /dev/null
@@ -0,0 +1,261 @@
+// !$*UTF8*$!
+{
+       archiveVersion = 1;
+       classes = {
+       };
+       objectVersion = 46;
+       objects = {
+
+/* Begin PBXBuildFile section */
+               D26CD42914DA8AAE00270FD9 /* cmd.c in Sources */ = {isa = PBXBuildFile; fileRef = D26CD42414DA8AAE00270FD9 /* cmd.c */; };
+               D26CD42A14DA8AAE00270FD9 /* compress.c in Sources */ = {isa = PBXBuildFile; fileRef = D26CD42514DA8AAE00270FD9 /* compress.c */; };
+               D26CD42B14DA8AAE00270FD9 /* misc.c in Sources */ = {isa = PBXBuildFile; fileRef = D26CD42614DA8AAE00270FD9 /* misc.c */; };
+               D26CD42C14DA8AAE00270FD9 /* proxy.c in Sources */ = {isa = PBXBuildFile; fileRef = D26CD42714DA8AAE00270FD9 /* proxy.c */; };
+               D26CD43014DA8D8700270FD9 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = D26CD42F14DA8D8700270FD9 /* main.c */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+               D26CD41014DA8A7700270FD9 /* CopyFiles */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 2147483647;
+                       dstPath = /usr/share/man/man1/;
+                       dstSubfolderSpec = 0;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 1;
+               };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+               D26CD41214DA8A7700270FD9 /* cheapo */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = cheapo; sourceTree = BUILT_PRODUCTS_DIR; };
+               D26CD42414DA8AAE00270FD9 /* cmd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cmd.c; sourceTree = SOURCE_ROOT; };
+               D26CD42514DA8AAE00270FD9 /* compress.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = compress.c; sourceTree = SOURCE_ROOT; };
+               D26CD42614DA8AAE00270FD9 /* misc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = misc.c; sourceTree = SOURCE_ROOT; };
+               D26CD42714DA8AAE00270FD9 /* proxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = proxy.c; sourceTree = SOURCE_ROOT; };
+               D26CD42814DA8AAE00270FD9 /* proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = proxy.h; sourceTree = SOURCE_ROOT; };
+               D26CD42F14DA8D8700270FD9 /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = main.c; sourceTree = SOURCE_ROOT; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+               D26CD40F14DA8A7700270FD9 /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+               D26CD40714DA8A7700270FD9 = {
+                       isa = PBXGroup;
+                       children = (
+                               D26CD41514DA8A7700270FD9 /* Cheapo */,
+                               D26CD41314DA8A7700270FD9 /* Products */,
+                       );
+                       sourceTree = "<group>";
+               };
+               D26CD41314DA8A7700270FD9 /* Products */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D26CD41214DA8A7700270FD9 /* cheapo */,
+                       );
+                       name = Products;
+                       sourceTree = "<group>";
+               };
+               D26CD41514DA8A7700270FD9 /* Cheapo */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D26CD42D14DA8AB900270FD9 /* Headers */,
+                               D26CD42414DA8AAE00270FD9 /* cmd.c */,
+                               D26CD42514DA8AAE00270FD9 /* compress.c */,
+                               D26CD42F14DA8D8700270FD9 /* main.c */,
+                               D26CD42614DA8AAE00270FD9 /* misc.c */,
+                               D26CD42714DA8AAE00270FD9 /* proxy.c */,
+                       );
+                       path = Cheapo;
+                       sourceTree = "<group>";
+               };
+               D26CD42D14DA8AB900270FD9 /* Headers */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D26CD42814DA8AAE00270FD9 /* proxy.h */,
+                       );
+                       name = Headers;
+                       sourceTree = "<group>";
+               };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+               D26CD41114DA8A7700270FD9 /* Cheapo */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = D26CD41C14DA8A7700270FD9 /* Build configuration list for PBXNativeTarget "Cheapo" */;
+                       buildPhases = (
+                               D26CD40E14DA8A7700270FD9 /* Sources */,
+                               D26CD40F14DA8A7700270FD9 /* Frameworks */,
+                               D26CD41014DA8A7700270FD9 /* CopyFiles */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = Cheapo;
+                       productName = Cheapo;
+                       productReference = D26CD41214DA8A7700270FD9 /* cheapo */;
+                       productType = "com.apple.product-type.tool";
+               };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+               D26CD40914DA8A7700270FD9 /* Project object */ = {
+                       isa = PBXProject;
+                       attributes = {
+                               LastUpgradeCheck = 0500;
+                       };
+                       buildConfigurationList = D26CD40C14DA8A7700270FD9 /* Build configuration list for PBXProject "Cheapo" */;
+                       compatibilityVersion = "Xcode 3.2";
+                       developmentRegion = English;
+                       hasScannedForEncodings = 0;
+                       knownRegions = (
+                               en,
+                       );
+                       mainGroup = D26CD40714DA8A7700270FD9;
+                       productRefGroup = D26CD41314DA8A7700270FD9 /* Products */;
+                       projectDirPath = "";
+                       projectRoot = "";
+                       targets = (
+                               D26CD41114DA8A7700270FD9 /* Cheapo */,
+                       );
+               };
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+               D26CD40E14DA8A7700270FD9 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               D26CD42914DA8AAE00270FD9 /* cmd.c in Sources */,
+                               D26CD42A14DA8AAE00270FD9 /* compress.c in Sources */,
+                               D26CD42B14DA8AAE00270FD9 /* misc.c in Sources */,
+                               D26CD42C14DA8AAE00270FD9 /* proxy.c in Sources */,
+                               D26CD43014DA8D8700270FD9 /* main.c in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+               D26CD41A14DA8A7700270FD9 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_SEARCH_USER_PATHS = NO;
+                               ARCHS = "$(NATIVE_ARCH_ACTUAL)";
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               COPY_PHASE_STRIP = NO;
+                               GCC_CHAR_IS_UNSIGNED_CHAR = NO;
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_DYNAMIC_NO_PIC = NO;
+                               GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+                               GCC_OPTIMIZATION_LEVEL = 0;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "DEBUG=1",
+                                       "$(inherited)",
+                               );
+                               GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+                               GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               MACOSX_DEPLOYMENT_TARGET = 10.7;
+                               ONLY_ACTIVE_ARCH = YES;
+                               SDKROOT = macosx;
+                       };
+                       name = Debug;
+               };
+               D26CD41B14DA8A7700270FD9 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_SEARCH_USER_PATHS = NO;
+                               ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               COPY_PHASE_STRIP = YES;
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               GCC_CHAR_IS_UNSIGNED_CHAR = NO;
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+                               GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               MACOSX_DEPLOYMENT_TARGET = 10.7;
+                               SDKROOT = macosx;
+                       };
+                       name = Release;
+               };
+               D26CD41D14DA8A7700270FD9 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_SEARCH_USER_PATHS = YES;
+                               GCC_CHAR_IS_UNSIGNED_CHAR = NO;
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO;
+                               PRODUCT_NAME = cheapo;
+                       };
+                       name = Debug;
+               };
+               D26CD41E14DA8A7700270FD9 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_SEARCH_USER_PATHS = YES;
+                               GCC_CHAR_IS_UNSIGNED_CHAR = NO;
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO;
+                               PRODUCT_NAME = cheapo;
+                       };
+                       name = Release;
+               };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+               D26CD40C14DA8A7700270FD9 /* Build configuration list for PBXProject "Cheapo" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               D26CD41A14DA8A7700270FD9 /* Debug */,
+                               D26CD41B14DA8A7700270FD9 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               D26CD41C14DA8A7700270FD9 /* Build configuration list for PBXNativeTarget "Cheapo" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               D26CD41D14DA8A7700270FD9 /* Debug */,
+                               D26CD41E14DA8A7700270FD9 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+/* End XCConfigurationList section */
+       };
+       rootObject = D26CD40914DA8A7700270FD9 /* Project object */;
+}
diff --git a/Cheapo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Cheapo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644 (file)
index 0000000..f15d672
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:Cheapo.xcodeproj">
+   </FileRef>
+</Workspace>
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..5ab025b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  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
+this service 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 make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  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.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+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
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the 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 a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE 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.
+
+                     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
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    This is the MacOS X port of Cheapo. Cheapo is a proxy that can be used to route QuakeWorld network traffic. Additionally, the proxy can modify the data and has features for enhancing gameplay. You can connect to cheapo as if it were a Quakeworld server, and then give it commands that forward you to a real server. The proxy can also be run on a firewall machine incapable of handling Quake traffic, so that machines inside the firewall can be used for playing.
+    Copyright (C) 2013  fruitz-of-dojo
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision 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, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  {signature of Ty Coon}, 1 April 1989
+  Ty Coon, President of Vice
+
+This 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.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..dbfd8d4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,35 @@
+# Cheapo - QuakeWorld proxy, Linux build
+#
+#   make                 build ./cheapo
+#   make install         install to /usr/local/bin (PREFIX=... to change)
+#   make clean
+#
+# The Windows build uses Makefile.win (nmake /f Makefile.win).
+
+CC      ?= cc
+CFLAGS  ?= -O2
+LDLIBS  += -lm
+PREFIX  ?= /usr/local
+BINDIR  ?= $(PREFIX)/bin
+
+SRCS = main.c proxy.c cmd.c misc.c compress.c
+OBJS = $(SRCS:.c=.o)
+
+all: cheapo
+
+cheapo: $(OBJS)
+       $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
+
+$(OBJS): proxy.h
+
+install: cheapo
+       install -d $(DESTDIR)$(BINDIR)
+       install -m 755 cheapo $(DESTDIR)$(BINDIR)/cheapo
+
+uninstall:
+       rm -f $(DESTDIR)$(BINDIR)/cheapo
+
+clean:
+       rm -f cheapo $(OBJS)
+
+.PHONY: all install uninstall clean
diff --git a/Makefile.win b/Makefile.win
new file mode 100755 (executable)
index 0000000..e043d46
--- /dev/null
@@ -0,0 +1,23 @@
+NODEBUG=1\r
+\r
+!include <win32.mak>\r
+\r
+clflags = -Ox -W3 -nologo -DWIN32\r
+ldflags = $(lflags) $(ldebug)\r
+ldlibs  = $(conlibs) wsock32.lib user32.lib\r
+\r
+objs = main.obj compress.obj cmd.obj proxy.obj misc.obj\r
+\r
+all: cheapo.exe\r
+\r
+cheapo.exe: $(objs)\r
+    $(link) $(ldflags) -out:$@ $(objs) $(ldlibs)\r
+\r
+.c.obj:\r
+    $(cc) $(clflags) -c $*.c\r
+\r
+main.c  : proxy.h\r
+proxy.c : proxy.h\r
+cmd.c   : proxy.h\r
+misc.c  : proxy.h\r
+\r
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..8fe008c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+Cheapo Linux Port
+Linux port of classic Quake lagswitch   
+copyright 2026, David Polakovic, licensed under GPLv2
+
+
+ABOUT:
+Cheapo is a QuakeWorld proxy written by Sami Tammilehto in 1998–1999. You
+connect to Cheapo as if it were a QuakeWorld server, then tell it which real
+server to forward you to. It can also read and modify the traffic, and it has
+features for enhancing gameplay (reports, location messages, timers).
+
+This repository is a fork of the
+[MacOS X port](https://github.com/fruitz-of-dojo/Quake-Cheapo), changed so
+that it builds and runs cleanly on modern 64-bit Linux. It is still the
+same Cheapo V2.61; no gameplay behaviour has been changed.
+
+
+BUILDING:
+You need a GCC and make, then in the directory run:
+       make
+And then:       
+       ./cheapo
+To install it system-wide:
+       sudo make install
+       make PREFIX=$HOME/.local install
+
+
+HOW TO RUN:
+Simply use
+       cheapo -n
+It listens on UDP 27500 and never auto-exit. Then in your QuakeWorld client:
+       connect <proxy-host>:27500
+       say .connect <server-host>:27500
+To list all options run
+       cheapo -?
+Cheapo reads .loc files from, and writes its log files to, the directory it is
+started in.
+
+
+NOTES:
+Cheapo speaks the original QuakeWorld protocol (28) and connects to servers
+as a plain vanilla client, so it works with MVDSV and FTEQW servers, but
+client/server protocol extensions (for example faster chunked downloads)
+are not available through it. Messages it doesn't understand are passed
+through unchanged.
+
+
+CHANGES FROM MacOS X:
+- `proxy.h`: `<unistd.h>` and the prototypes for Cheapo's own `stricmp`,
+  `memicmp`, `strlwr` and `mainloop_low` were included only on macOS. They
+  now apply to every non-Windows system. Without this, GCC 14 and newer
+  refuse to compile the code (implicit function declarations are errors).
+- New GNU `Makefile` with `install`/`clean` targets.
+- The Windows nmake file was renamed from `makefile` to `Makefile.win`,
+  because GNU make reads `makefile` before `Makefile`. `mkwin.bat` now
+  runs `nmake /f Makefile.win`.
+- `mklinux` / `mksparc`: `-lm` moved after the source files, so the link
+  no longer fails with "undefined reference to sqrt".
+
+Tested with gcc version 16.2.1 20260810 (GCC) on Parabola GNU\Linux (x86-64).
+
+
+LICENSE:
+https://www.gnu.org/licenses/gpl-2.0.en.html
+see LICENSE.txt
+Original Cheapo: © 1998–1999 Sami Tammilehto
diff --git a/cmd.c b/cmd.c
new file mode 100755 (executable)
index 0000000..54ef38c
--- /dev/null
+++ b/cmd.c
@@ -0,0 +1,3143 @@
+// Cheapo - a QuakeWorld Proxy
+// Copyright (C) 1998 Sami Tammilehto
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+#include "proxy.h"
+
+//const char *_first_(void)
+char *_first_(void)
+{
+    static char firststring[]="<<<<FFFFFFFF>>>>";
+//    char *firststring="<<<<FFFFFFFF>>>>";
+    return(firststring);
+}
+
+void q_help(User *u,uchar *param) //??
+{
+    if(*param=='.') param++;
+    if(!memicmp(param,"fun",3))
+    {
+        funnyhelp(u);
+    }
+    else if(!memicmp(param,"ascii",5))
+    {
+        int i;
+        for(i=0x01;i<0x20;i+=4)
+        {
+            q_printf(u,"#%02X:%c #%02X:%c #%02X:%c #%02X:%c\n",
+                    i+0,i+0,
+                    i+1,i+1,
+                    i+2,i+2,
+                    i+3,i+3);
+        }
+    }
+    else if(!memicmp(param,"con",3))
+        q_print(u,".connect <address>\n"
+                  "Connects to a server or a proxy.\n"
+                  "If linking to another proxy, the\n"
+                  "first proxy in the chain will\n"
+                  "respond to .command, and the\n"
+                  "last proxy to proxy:command. All\n"
+                  "respond to hostname:command.\n"
+                  );
+    else if(!memicmp(param,"dis",3))
+        q_print(u,".disconnect\n"
+                  "Disconnects from a server or\n"
+                  "a proxy.\n"
+                  );
+    else if(!memicmp(param,"rec",3))
+        q_print(u,".reconnect\n"
+                  "Reconnects to the last address\n"
+                  "connected to.\n"
+                  );
+    else if(!memicmp(param,"use",3))
+        q_print(u,".users\n"
+                  "Lists users connected to this\n"
+                  "proxy and the address they are\n"
+                  "connected to.\n"
+                  );
+    else if(!memicmp(param,"nam",3))
+        q_print(u,".name <name>\n"
+                  "This can be used to rename the\n"
+                  "proxy, so that it responds to\n"
+                  "commands which start <name>:\n"
+                  "proxy: or host: is no longer\n"
+                  "recognized after this command.\n"
+                  );
+    else if(!memicmp(param,"fil",3))
+        q_print(u,".filter <group> <group> ...\n"
+                  "This commands allows dividing a\n"
+                  "team into groups and sending\n"
+                  "messages only to a single group.\n"
+                  "All groupnames must be short and\n"
+                  "start with #. Here's an example:\n"
+                  "  player1 .filter #att\n"
+                  "  player2 .filter #def\n"
+                  "After this a message ending with\n"
+                  "' #att' like 'say_team go! #att'\n"
+                  "is seen only by player 1. Texts\n"
+                  "with no group are seen by all,\n"
+                  "and people without no filters\n"
+                  "see all messages as well. You\n"
+                  "can belong to multiple groups,\n"
+                  "but a message can only have one\n"
+                  "group. All text after '#' is\n"
+                  "removed from the message, and\n"
+                  "there must be a space before '#'.\n"
+                  );
+    else if(!memicmp(param,"tra",3))
+        q_print(u,".track <player>"
+                  "Gives a 1st-person tracking view\n"
+                  "of <player>. Must be a spectator\n"
+                  "and in fly-mode. NOTE: QW 2.3 has\n"
+                  "a cl_chasecam 1 option that gives\n"
+                  "you tracking without a proxy.\n"
+                  );
+    else if(!memicmp(param,"cam",3))
+#ifdef TIMERS
+        q_print(u,".camp <items...>\n"
+                  "Selects which items are timed:\n"
+                  "  none, quad, penta, ring, all\n"
+                  "See also .remind and .guess\n"
+                  );
+#else
+        q_print(u,".camp <items...>\n"
+                  "Timers are no longer available in\n"
+                  "Cheapo, and this command is obsolete.\n"
+                  );
+#endif
+    else if(!memicmp(param,"rem",3))
+#ifdef TIMERS
+        q_print(u,".remind <time>\n"
+                  "Remind <time> seconds before a\n"
+                  "powerup appears with a message\n"
+                  "near center of the screen. Give\n"
+                  "0 to disable remind.\n"
+                  );
+#else
+        q_print(u,".remind <time>\n"
+                  "Timers are no longer available in\n"
+                  "Cheapo, and this command is obsolete.\n"
+                  );
+#endif
+    else if(!memicmp(param,"gue",3))
+#ifdef TIMERS
+        q_print(u,".guess <1/0>\n"
+                  "Normally camp timers trigger\n"
+                  "when somebody picks up a powerup\n"
+                  "and you hear it (guess 0). With\n"
+                  "guess 1, if you don't hear a\n"
+                  "powerup when last counter goes\n"
+                  "to zero, the counter is reset\n"
+                  "(assuming someone probably picked\n"
+                  "the powerup immediately.) The new\n"
+                  "counter has a different symbol to\n"
+                  "show that the count value is a\n"
+                  "guess. Additionally, when someone\n"
+                  "says 'i have <powerup>' and you\n"
+                  "don't have time for the given\n"
+                  "powerup, the counter is reset\n"
+                  "with a guess.\n"
+                  );
+#else
+        q_print(u,".guess <1/0>\n"
+                  "Timers are no longer available in\n"
+                  "Cheapo, and this command is obsolete.\n"
+                  );
+#endif
+    else if(!memicmp(param,"not",3))
+#ifdef TIMERS
+        q_print(u,".notimers < 1 | 0 >\n"
+                  "Disables camp timers. Text 'notimers'\n"
+                  "is shown in f_version and .who proxy\n"
+                  "when this is active. The purpose is\n"
+                  "to allow games where it's easy for\n"
+                  "everyone to check that timers are not\n"
+                  "used. Notimers mode must be set in the\n"
+                  "first proxy if you have linked through\n"
+                  "many proxies. You can enable notimers\n"
+                  "when connected, but not disable them\n"
+                  );
+#else
+        q_print(u,".notimers < 1 | 0 >\n"
+                  "Timers are no longer available in\n"
+                  "Cheapo, and this command is obsolete.\n"
+                  "Notimers is always enabled.\n"
+                  );
+#endif
+    else if(!memicmp(param,"flo",3))
+        q_print(u,".flood <1/0>\n"
+                  "Flood protect protect. If enabled\n"
+                  "the proxy does not allow you to\n"
+                  "trigger floodprotect on the server\n"
+                  "(instead the proxy gives the flood\n"
+                  "protect warning). The advantage is\n"
+                  "that you won't get any flooding\n"
+                  "penalty. Server limits are assumed\n"
+                  "to be 4 messages in 4 sec. Default\n"
+                  "enabled.\n"
+                  );
+    else if(!memicmp(param,"point2",6))
+        q_print(u,"Pointing makes it possible to\n"
+                  "report to teammates an item you\n"
+                  "see/pick using following in say:\n"
+//                  "  %T - item pointed to with move\n"
+//                  "       direction for players\n"
+                  "  %t - item pointed to\n"
+                  "  %i - last item picked up\n"
+                  "  %j - last item pointed to\n"
+                  "  %k - last item picked/pointed\n"
+                  "  %m - last item picked or nearest\n"
+                  "  %x - %t but item name only\n"
+                  "  %y - %t but location only\n"
+                  "The nearest object near center\n"
+                  "of the screen is picked for point.\n"
+                  "Picking only tracks items you can\n"
+                  "point.\n"
+                  );
+    else if(!memicmp(param,"poi",3))
+        q_print(u,".point <items>\n"
+                  "This command controls what objects\n"
+                  "are taken into account for %tijk:\n"
+                  " quad, penta, ring, player, enemy,\n"
+                  " ra, ya, ga, mh, suit, pack,\n"
+                  " rl, gl, lg, sng, ng, ssg\n"
+                  " cell, rock, nail, shel, health\n"
+                  " misc (=rune+key+flag+sentry-etc)\n"
+                  "aliases:\n"
+                  " none  - no items\n"
+                  " power - quad penta ring\n"
+                  " armor - ra ya ga\n"
+                  " all   - everything\n"
+                  " def   - default items:\n"
+                  "   player, pack, armors, mega, rock\n"
+                  "   powerups, lg, rl, ga, sng, misc\n"
+                  "see .help point2 for more info.\n"
+                  );
+    else if(!memicmp(param,"too",3))
+        q_print(u,".took <items>\n"
+                  "Select what items are reported\n"
+                  "with f_took when you collect them.\n"
+                  "Items is a list of:\n"
+                  " quad, penta, ring, player, enemy\n"
+                  " ra, ya, ga, mh, suit, pack,\n"
+                  " rl, gl, lg, sng, ng, ssg\n"
+                  " cell, rock, nail, shel, health\n"
+                  " misc (=rune+key+flag+sentry-etc)\n"
+                  "Default:\n"
+                  " armors, lg, rl, gl\n"
+                  );
+    else if(!memicmp(param,"rep",3))
+        q_print(u,".report <events>\n"
+                  "What events are reported to team\n"
+                  "automatically. Possibilites:\n"
+                  "none   - nothing \n"
+                  "reply  - when someone says something\n"
+                  "         containing ':' (as in f_report)\n"
+                  "         reply with f_report\n"
+                  "power  - when you pick a powerup\n"
+                  "         execute f_powerup.\n"
+/*
+                  "power2 - allows you to specify separate\n"
+                  "         commands for each powerup. You"
+                  "         must define aliases:\n"
+                  "           f_power_quad, f_power_noquad\n"
+                  "           f_power_pent, f_power_nopent\n"
+                  "           f_power_ring, f_power_noring\n"
+*/
+                  "took   - when you collect an item\n"
+                  "         execute f_took (see .took)\n"
+#ifdef TIMERS
+                  "sync  - when you hear anyone pick a\n"
+                  "        powerup a team message like\n"
+                  "        'Quad #1 picked up' is sent.\n"
+                  "        These messages are used to\n"
+                  "        sync powerup timers, and are\n"
+                  "        compatible with Qizmo. NOTE:\n"
+                  "        Cheapo hides these messages\n"
+                  "        when you play, but they do\n"
+                  "        affect the timers.\n"
+                  "show  - show&sync. Same as previous\n"
+                  "        except that the picked up\n"
+                  "        messages are shown. NOTE: if\n"
+                  "        all players in a team use\n"
+                  "        sync there will be multiple\n"
+                  "        messages\n"
+#endif
+                  );
+    else if(!memicmp(param,"lin",3))
+        q_print(u,".line <lines | vga | 512 | 640> [center]\n"
+                  "Set number of text lines to align\n"
+                  "camp timers correctly. Larger\n"
+                  "numbers move the texts down.\n"
+                  "640 - alias for 640x480 (45)\n"
+                  "512 - alias for 512x384 (33)\n"
+                  "vga - alias for 320x200 (11)\n"
+                  "If the server sends a statusline\n"
+                  "(CTF etc) decrease the .line value\n"
+                  "by one so the powerups are shown\n"
+                  "above the statusline. The final\n"
+                  "'center' option centers powerup\n"
+                  "timers (default is left aligned)\n"
+                  );
+    else if(!memicmp(param,"mov",3))
+        q_print(u,".move <lines>\n"
+                  "Move small overlay texts near\n"
+                  "center of screen <lines> lines\n"
+                  "downward. Mostly useful to get\n"
+                  "that Arena FIGHT! away from the\n"
+                  "crosshair (try .move 3) or to\n"
+                  "move the remainder (see .remind)\n"
+                  );
+    else if(!memicmp(param,"ite",3))
+        q_print(u,".items\n"
+                  "List important items in current\n"
+                  "level and their nearest mark.\n"
+                  "Useful for checking how many\n"
+                  "given items are in a level and\n"
+                  "where for example quad2 is.\n"
+                  );
+    else if(!memicmp(param,"ski",3))
+        q_print(u,".skin <myskin> <other>\n"
+                  "Overrides skins for all players\n"
+                  "so that your team has <myskin>\n"
+                  "and other players have <other>.\n"
+                  "Default for <other> is base.\n"
+                  "This change is not visible to\n"
+                  "other players in the server.\n"
+                  "Without parameters .skin restores\n"
+                  "skins to normal. Skin can also\n"
+                  "be specified as * to leave it\n"
+                  "unchanged: '.skin * base' forces\n"
+                  "base to enemies, but leaves\n"
+                  "teammate skins normal.\n"
+                  );
+    else if(!memicmp(param,"col",3))
+        q_print(u,".color <mycolor> <othercolor>\n"
+                  "Overrides colors for all players\n"
+                  "so that your team has <mycolor>\n"
+                  "and other players have <other>.\n"
+                  "Default for <other> is 0 (white).\n"
+                  "This change is not visible to\n"
+                  "other players in the server.\n"
+                  "Without parameters .color restores\n"
+                  "colors to normal. The same color\n"
+                  "is used for top and bottom.\n"
+                  "Color can also be specified as *\n"
+                  "to leave it unchanged: '.color * 0'\n"
+                  "forces white to enemies, but leaves\n"
+                  "teammate colors normal. Color can\n"
+                  "also have two numbers like 3,4 in\n"
+                  "which case the first is topcolor\n"
+                  "and the second is bottomcolor.\n"
+                  );
+    else if(!memicmp(param,"fps",3))
+        q_print(u,".fps a b c d e f g h\n"
+                  "Gives more fps by removing or\n"
+                  "modifying visible events. There\n"
+                  "are 8 options each specified by\n"
+                  "a value. If less than 8 values\n"
+                  "given others are assumed to be 0\n"
+                  "[x]            [values of x]\n"
+                  "<a> = explosions       [0-5]\n"
+                  "<b> = powerup glow     [0-2]\n"
+                  "<c> = muzzle flash     [0-2]\n"
+                  "<d> = gib/body filter  [0-2]\n"
+                  "<e> = rocket change    [0-1]\n"
+                  "<f> = setinfo filter   [0-0]\n"
+                  "<g> = nail filter      [0-4]\n"
+                  "<h> = damage filter    [0-1]\n"
+                  "Try different options to see\n"
+                  "what each number means. 0 is\n"
+                  "normal in all cases. Here is\n"
+                  "an example: .fps 5 1 2 2 1\n"
+                  "The f parameter is ignored.\n"
+                  );
+    else if(!memicmp(param,"mar",3))
+        q_print(u,".mark [name|delete|reload|auto]\n"
+                  "modifies marks that can be used\n"
+                  "to tell player location with\n"
+                  "say_team %l.\n"
+                  "to show nearest:      .mark\n"
+                  "to create a new mark: .mark <name>\n"
+                  "to delete nearest:    .mark delete\n"
+                  "to reload marks:      .mark reload\n"
+                  "to autogenerate:      .mark auto\n"
+                  "Marks are stored in .loc files in\n"
+                  "the current directory (files are\n"
+                  "compatible with FAQ-proxy). All\n"
+                  "commands save the loc file except\n"
+                  "autogenerate which replaces current\n"
+                  "marks with marks based on important\n"
+                  "items (the automarks are not good,\n"
+                  "but better than nothing if you are\n"
+                  "in a hurry).\n"
+                  );
+    else if(!memicmp(param,"wea",3))
+        q_print(u,".weapon <best | none | #: # # # ...>\n"
+                  "controls impulse modification\n"
+                  "best | 1\n"
+                  "  For each impulse command, if that\n"
+                  "  weapon doesn't have ammo or you\n"
+                  "  don't have the weapon, select the\n"
+                  "  next best weapon with ammo. For\n"
+                  "  default impulses the last working\n"
+                  "  weapon in this list is selected:\n"
+                  "  8: 1 2 4 3 5 8\n"
+                  "  7: 1 2 4 3 5 7\n"
+                  "  6: 1 2 4 3 5 6\n"
+                  "  5: 1 2 4 3 5\n"
+                  "  4: 1 2 4\n"
+                  "  3: 1 2 3\n"
+                  "  2: 1 2\n"
+                  "none | 0\n"
+                  "  disable the above impulses\n"
+                  "  modifications (default)\n"
+                  "10: 3 2\n"
+                  "  define your own impulse sequence\n"
+                  "  (numbers above are just examples)\n"
+                  "  This would define impulse 10 to\n"
+                  "  be the equivalent of an alias\n"
+                  "  'impulse 3; wait; impulse 2'.\n"
+                  "10:\n"
+                  "  undefine the above sequences\n"
+                  "You can define up to 8 sequences. Also\n"
+                  "note that these only work with default\n"
+                  "Quake weapons, not for example in TF.\n"
+                  );
+    else if(!memicmp(param,"who",3))
+        q_print(u,".who [proxy | fps]\n"
+                  "Lists players in the server along\n"
+                  "with their teams and skins (like\n"
+                  "Kombat Teams who-command). Options:\n"
+                  ".who proxy  displays what proxies\n"
+                  "            all players are using\n"
+                  ".who fps    displays fps rates of\n"
+                  "            visible players. Last\n"
+                  "            known rates for far\n"
+                  "            away players in ( ).\n"
+                  );
+    else if(!memicmp(param,"sou",3))
+        q_print(u,".sound [triggerchar]\n"
+                  "Selects the soundtrigger character and\n"
+                  "enables soundtriggers. If the character\n"
+                  "is $ (for example) and a team message\n"
+                  "ends like '$items/damage.wav' that sound\n"
+                  "will be played. Start of the message\n"
+                  "is displayed, so 'help $items/damage.wav'\n"
+                  "would print 'help' and play that sound.\n"
+                  "Use 'none' to disable (default disabled).\n"
+                  );
+#ifdef IDENT
+    else if(!memicmp(param,"ide",3))
+        q_print(u,".ident [format formattext]\n"
+                  "Prints who you are looking at above\n"
+                  "the poweruptimers. The format can be\n"
+                  "changed with for example:\n"
+                  ".ident format %N %Aa:%a h:%h\n"
+                  "Format needs only be set once, the\n"
+                  "default is like f_report. You can\n"
+                  "only get status of teammates using\n"
+                  "Cheapo 2.2 or later. Otherwise only\n"
+                  "the name is shown.\n"
+                  );
+#endif
+    else if(!memicmp(param,"sta",3))
+        q_print(u,".stat < what to show ... >\n"
+                  "Display communications statistics\n"
+                  "between proxy and server. Multiple\n"
+                  "options can be specified:\n"
+                  "all   - show all\n"
+                  "ser   - server side stuff\n"
+                  "ext   - extended statistics\n"
+                  "ping  - ping for 8 last packets\n"
+                  "net   - netgraph for 30 packets\n"
+                  "proxy - server/clientside proxies\n"
+                  "reset - reset extended stats\n"
+                  );
+    else if(!memicmp(param,"cpp",3))
+        q_print(u,".cpps <packets/sec>\n"
+                  "Limit the number of client packets\n"
+                  "sent to the server per second. Most\n"
+                  "useful when you set cl_maxfps 72 to\n"
+                  "get smooth play and then want to cut\n"
+                  "the number of packets (with .cpps\n"
+                  "50 for example) so your connection\n"
+                  "won't overload. Dropping looks like\n"
+                  "packet loss in netgraph, but if the\n"
+                  ".cpps limit is greater than half\n"
+                  "your framerate the loss shouldn't\n"
+                  "matter in practice.\n"
+                  );
+    else if(!memicmp(param,"fmo",3))
+        q_print(u,".fmodified < 1 | 0 >\n"
+                  "Enable or disable f_modified which is\n"
+                  "used to verify that no models have\n"
+                  "been modified in official games. QWCL\n"
+                  "must be started from Cheapo command\n"
+                  "line for this to work. (enabled by\n"
+                  "default).\n"
+                  );
+    else if(!memicmp(param,"dmm",3))
+        q_print(u,".dmm 1 | 2 | 3 | auto\n"
+                  "Controls deathmatch mode detection.\n"
+                  "In mode 1 weapons are reported in\n"
+                  "point/took messages. In other modes\n"
+                  "they are not (even when enabled with\n"
+                  "with .point or .took). Default value\n"
+                  "auto reads dmm from server. Values\n"
+                  "1, 2 and 3 can be used to force a\n"
+                  "mode. Previous Cheapo versions acted\n"
+                  "as if mode 1 was always active.\n"
+                  );
+    else if(!memicmp(param,"qui",3))
+        q_print(u,".quiet < 0 | 1 >\n"
+                  "Just '.quiet' silences all proxy\n"
+                  "messages for 0.5 sec, so appending\n"
+                  "it before a list of commands removes\n"
+                  "the normal output they generate.\n"
+                  "Server messages are always printed.\n"
+                  "If you have a longer script, you can\n"
+                  "put '.quiet 1' at start and '.quiet 0'\n"
+                  "at end. This command doesn't print\n"
+                  "anything itself.\n"
+                  );
+    else if(!memicmp(param,"mor",3))
+        q_print(u,
+                  "- for all commands, the first three\n"
+                  "  letters are enough\n"
+                  "- when first connecting to proxy\n"
+                  "  alias proxyautoexec is executed.\n"
+                  "  (you can define it yourself)\n"
+                  "- proxy removes all unrecognized\n"
+                  "  messages starting with '!' or '.'\n"
+                  "- you can talk to other players in\n"
+                  "  the proxy when not connected\n"
+                  "- see .help funny for funny names\n"
+                  "- proxy supports serverside feature\n"
+                  "  disables (fpd=xx, as in Qizmo)\n"
+                  "- proxy defines f_report, f_powerup\n"
+                  "- proxy responds to f_version\n"
+                  "- proxy shows Cheapo in userinfo\n"
+                  );
+    else if(!memicmp(param,"say2",4))
+    {
+        q_print(u,
+                  "additional %-commands supported:\n"
+                  "these five are uppercase:\n"
+                  "  %S   = your skin (for tf)\n"
+                  "  %C   = your bottom color\n"
+                  "  %L   = these work like %l,%e,%o\n"
+//                  "  %U   = your loc & dir (see %T)\n"
+                  "  %P   = last powerup that ended\n"
+                  "  %E   =   except after death they\n"
+                  "  %O   =   are frozen for 5 seconds\n"
+                  "  %r   = last reported location\n"
+                  "see .point for these:\n"
+                  "  %t   = target pointed to\n"
+//                  "  %T   = target pointed to with\n"
+//                  "         move direction for players\n"
+                  "  %x   = like %t but item name only\n"
+                  "  %y   = like %t but location only\n"
+                  "  %i   = last item picked up\n"
+                  "  %j   = last target pointed to\n"
+                  "  %k   = last item picked/pointed\n"
+                  "  %m   = last item picked or nearest\n"
+                  "powerup reporting:\n"
+                  "  %q   = powerups of last seen enemy\n"
+                  "         (or quad if none seen)\n"
+                  "  %g   = soon appearing powerups\n"
+                  "         (or quad if none appearing)\n"
+                  "aliases (redefine if you want):\n"
+                  "f_report  = %[a] %[h] %[b] %p %n\n"
+                  "f_powerup = i have %p\n"
+                  "f_took    = took %i\n"
+                  );
+    }
+    else if(!memicmp(param,"say",3))
+    {
+        q_print(u,
+                  "following %-commands supported:\n"
+                  "status reporting:\n"
+                  "  %A   = armortype\n"
+                  "  %a   = armor\n"
+                  "  %[a] = %Aa:%a (with [] if under 50)\n"
+                  "  %h   = health\n"
+                  "  %[h] = h:%h (with [] if under 50)\n"
+                  "  %w   = current weapon\n"
+                  "  %[w] = %w (with [] if not good)\n"
+                  "  %b   = best weapon\n"
+                  "  %[b] = %b (with [] if not good)\n"
+                  "  %p   = powerups you have\n"
+                  "  %[p] = %p (with [])\n"
+                  "  %s   = report only what is low\n"
+                  "  %l   = nearest location\n"
+                  "  %d   = location you died at\n"
+                  "  %e   = enemies\n"
+                  "  %o   = teammates\n"
+                  "  %n   = send to visible players only\n"
+                  "  %N   = send to ALL but yourself\n"
+                  "         (only use one of %n or %N)\n"
+                  "see .help say2 for more %-commands.\n"
+                  );
+    }
+    else
+    {
+       q_print(u,
+            "for nearest proxy use:  .cmd\n"
+            "for specific proxy use: host:cmd\n"
+            "for final proxy use:    proxy:cmd\n"
+            ".help <command> for more help\n"
+            ".help more      for misc info\n"
+            ".help say       for %-commands\n"
+            "\n"
+#ifdef TIMERS
+            "[connecting]    [camp/hud]\n"
+            "connect         line      \n"
+            "disconnect      camp      \n"
+            "reconnect       remind    \n"
+            "users           guess     \n"
+            "name            move      \n"
+            "                notimers  \n"
+            "[teamplay]                \n"
+            "who             [misc]    \n"
+            "mark            fps       \n"
+            "skin            weapon    \n"
+            "color           items     \n"
+            "point           track     \n"
+            "took            cpps      \n"
+            "report          flood     \n"
+            "sound           stat      \n"
+            "                fmodified \n"
+#else
+            "[connecting]   [visibles]\n"
+            "connect        fps       \n"
+            "disconnect     skin      \n"
+            "reconnect      color     \n"
+            "users                    \n"
+            "name           [misc]    \n"
+            "               who       \n"
+            "[messages]     items     \n"
+            "mark           track     \n"
+            "report         cpps      \n"
+            "point          move      \n"
+            "took           stat      \n"
+            "sound          fmodified \n"
+            "filter         flood     \n"
+            "dmm            weapon    \n"
+            "               quiet     \n"
+#endif
+#ifdef IDENT
+            "ident                     \n"
+#endif
+            );
+    }
+}
+
+void modifycampflags(uchar *s,int *campflags,uchar *resulttxt,int limitmask,int def)
+{
+    int flags=*campflags;
+
+    while(*s)
+    {
+        int set=1,a;
+
+        if(0)
+        {
+        }
+        else if(!memicmp(s,"all",3))
+        {
+            flags=-1;
+        }
+        else if(!memicmp(s,"none",4))
+        {
+            flags=0;
+        }
+        else if(!memicmp(s,"def",3))
+        {
+            flags|=def;
+        }
+        else if(!memicmp(s,"powe",4))
+        {
+            flags|=CAMP_P|CAMP_Q|CAMP_R;
+        }
+        else if(!memicmp(s,"poin",4))
+        {
+            flags|=CAMP_POINTED;
+        }
+        else if(!memicmp(s,"pack",4))
+        {
+            flags|=CAMP_PACK;
+        }
+        else if(!memicmp(s,"rune",4) || !memicmp(s,"flag",4) || !memicmp(s,"misc",4))
+        {
+            flags|=CAMP_RUNE;
+        }
+        else if(!memicmp(s,"armo",4))
+        {
+            flags|=CAMP_RA|CAMP_YA|CAMP_GA;
+        }
+        else if(!memicmp(s,"play",4))
+        {
+            flags|=CAMP_PLAYER;
+        }
+        else if(!memicmp(s,"enem",4))
+        {
+            flags|=CAMP_ENEMY;
+        }
+        else if(!memicmp(s,"heal",4))
+        {
+            flags|=CAMP_HEALTH;
+        }
+        else if(!memicmp(s,"suit",4))
+        {
+            flags|=CAMP_SUIT;
+        }
+        else if(!memicmp(s,"she",3))
+        {
+            flags|=CAMP_SHELLS;
+        }
+        else if(!memicmp(s,"roc",3))
+        {
+            flags|=CAMP_ROCKETS;
+        }
+        else if(!memicmp(s,"nai",3))
+        {
+            flags|=CAMP_NAILS;
+        }
+        else if(!memicmp(s,"cel",3))
+        {
+            flags|=CAMP_CELLS;
+        }
+        else if(!memicmp(s,"ra",2))
+        {
+            a=CAMP_RA;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+        else if(!memicmp(s,"ya",2))
+        {
+            a=CAMP_YA;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+        else if(!memicmp(s,"ga",2))
+        {
+            a=CAMP_GA;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+        else if(!memicmp(s,"me",2) || !memicmp(s,"mh",2))
+        {
+            a=CAMP_MH;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+        else if(!memicmp(s,"rl",2))
+        {
+            a=CAMP_RL;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+        else if(!memicmp(s,"gl",2))
+        {
+            a=CAMP_GL;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+        else if(!memicmp(s,"lg",2))
+        {
+            a=CAMP_LG;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+        else if(!memicmp(s,"ng",2))
+        {
+            a=CAMP_NG;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+        else if(!memicmp(s,"sn",2))
+        {
+            a=CAMP_SNG;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+        else if(!memicmp(s,"ss",2))
+        {
+            a=CAMP_SSG;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+        else if(!memicmp(s,"r",1))
+        {
+            a=CAMP_R;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+        else if(!memicmp(s,"p",1))
+        {
+            a=CAMP_P;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+        else if(!memicmp(s,"q",1))
+        {
+            a=CAMP_Q;
+            if(!set) flags&=~a;
+            else flags|=a;
+        }
+
+        while(*s>32) s++;
+        while(*s<=32 && *s) s++;
+    }
+
+    *resulttxt=0;
+    flags&=limitmask;
+    if(flags&CAMP_RA)       strcat(resulttxt,"ra ");
+    if(flags&CAMP_YA)       strcat(resulttxt,"ya ");
+    if(flags&CAMP_GA)       strcat(resulttxt,"ga ");
+    if(flags&CAMP_MH)       strcat(resulttxt,"mh ");
+    if(flags&CAMP_LG)       strcat(resulttxt,"lg ");
+    if(flags&CAMP_RL)       strcat(resulttxt,"rl ");
+    if(flags&CAMP_GL)       strcat(resulttxt,"gl ");
+    if(flags&CAMP_SNG)      strcat(resulttxt,"sng ");
+    if(flags&CAMP_NG)       strcat(resulttxt,"ng ");
+    if(flags&CAMP_SSG)      strcat(resulttxt,"ssg ");
+    if(flags&CAMP_HEALTH)   strcat(resulttxt,"heal ");
+    if(flags&CAMP_NAILS)    strcat(resulttxt,"nail ");
+    if(flags&CAMP_CELLS)    strcat(resulttxt,"cell ");
+    if(flags&CAMP_SHELLS)   strcat(resulttxt,"shel ");
+    if(flags&CAMP_ROCKETS)  strcat(resulttxt,"rock ");
+    if(flags&CAMP_SUIT)     strcat(resulttxt,"suit ");
+    if(flags&CAMP_R)        strcat(resulttxt,"ring ");
+    if(flags&CAMP_P)        strcat(resulttxt,"pent ");
+    if(flags&CAMP_Q)        strcat(resulttxt,"quad ");
+    if(flags&CAMP_PLAYER)   strcat(resulttxt,"player ");
+    if(flags&CAMP_ENEMY)    strcat(resulttxt,"enemy ");
+    if(flags&CAMP_RUNE)     strcat(resulttxt,"misc ");
+    if(flags&CAMP_PACK)     strcat(resulttxt,"pack ");
+    if(flags&CAMP_POINTED)  strcat(resulttxt,"pointed ");
+
+    *campflags=flags;
+}
+
+char *nextparam(uchar **sp)
+{
+    static uchar buf[64];
+    uchar *d=buf,*s=*sp;
+
+    while(*s<=32 && *s) s++;
+
+    while(d<buf+60 && *s>32)
+    {
+        *d++=*s++;
+    }
+    *d++=0;
+
+    while(*s<=32 && *s) s++;
+
+    *sp=s;
+    return(buf);
+}
+
+void q_command(User *u,uchar *s)
+{
+    uchar tmp[MAXSTR];
+    uchar buf[MAXSTR];
+
+    logx(LOG_DEBUG,"%s command: %s \n",u->name,formatqstring(s));
+
+#ifndef TIMERS
+    u->campthese=0;
+    u->notimers=1;
+#endif
+
+    if(*s=='!')
+    { // internal commands
+        if(!memicmp(s,"!accept",7))
+        { // accept from server (generated message)
+            u->state=STATE_CONNECTED;
+            //q_print(u,"Connected\n");
+            if(loglevel>=LOG_VERBOSE) logx(LOG_CONSOLE,"Join: %s connected to %s\n",u->name,u->connecthere);
+            u->appendserverbytes=0;
+            u->appendclientbytes=0;
+            q_servercmd(u,"new");
+        }
+        else if(!memicmp(s,"!sendnew",8))
+        {
+            q_servercmd(u,"new");
+        }
+        else if(!memicmp(s,"!reject",7))
+        { // reject from server (generated message)
+            c_killserver(u);
+            u->state=STATE_HERE;
+            q_print(u,"Server rejected connection\n");
+            u->doconnect=0;
+        }
+        else if(!memicmp(s,"!init",5))
+        { // init after connect to proxy
+            //if(u->sequence>0)
+            {
+                extern uchar *version;
+                uchar *p;
+                char buf[16];
+                int i;
+#if !defined(__APPLE__) && !defined(MACOSX)
+                static int crcchecked;
+#endif /* !__APPLE__ && !MACOSX */
+                memset(tmp,0x9e,36);
+                tmp[0]=0x9d;
+                tmp[35]=0x9f;
+                tmp[36]='\n';
+                tmp[37]=0;
+
+                if(!*proxy_name)
+                {
+                    gethostname(proxy_name,255);
+                    strlwr(proxy_name);
+                }
+                strncpy(buf,proxy_name,10);
+                for(i=0;i<10;i++)
+                {
+                    if(buf[i]=='.') buf[i]=0;
+                }
+                buf[10]=0;
+
+                if(u->version==24)      p="protocol QuakeWorld 2.3";
+                else if(u->version==23) p="protocol QuakeWorld 2.29";
+                else if(u->version==22) p="protocol QuakeWorld 2.2";
+                else if(u->version==21) p="protocol QuakeWorld 2.1";
+                else                    p="protocol unrecognized";
+
+                expires_gettime();
+
+                q_print(u,tmp);
+                q_printf(u,
+                    "Welcome to %s %s%s\n"
+                    "%s\n"
+                    "type \'%s:help\' for commands\n"
+                    "type \'.help\' for nearest proxy\n"
+                    ,versionname,version,versiontype,p,buf);
+                q_print(u,tmp);
+
+                strcpy(u->proxysay,"say proxy:");
+
+                sprintf(u->proxyhostname,"%s:%i",buf,proxy_port);
+
+                sprintf(u->proxysay2,"say %s:",buf);
+
+                strcpy(u->requestformat,"%N %[a] %[h] %[b] %p");
+
+                sprintf(tmpbuf,"name \"%s\"\n",u->name);
+                q_stufftext(u,tmpbuf);
+
+                u->fmodified=1;
+
+                u->state=STATE_HERE;
+                u->usepercentn=1;
+                u->nofversion=0;
+                u->secondcamp=1;
+                u->floodprot=1;
+                u->pointtell=CAMP_DEF;
+                u->autoitookitems=CAMP_DEFTOOK;
+                u->time_connect=u->time;
+                u->fpd=litemode;
+
+                if(u->version>24)
+                {
+                    q_print(u,"Since the protocol is unrecognized, the proxy will probably not work!\n");
+                }
+
+                {
+                    char ver[16];
+                    sprintf(ver,"%s",version);
+                    cl_setinfo(u,"Cheapo",ver);
+                }
+
+                q_stufftext(u,"alias f_report \"say_team %[a] %[h] %[b] %p%n\"\n");
+                q_stufftext(u,"alias f_powerup \"say_team i have %p\"\n");
+                q_stufftext(u,"alias f_took \"say_team took %i\"\n");
+                q_stufftext(u,"proxyautoexec\n");
+
+                if(gamespy)
+                {
+                    q_stufftext(u,"exec GameSpyC.cfg");
+                }
+            }
+        }
+        else if(!memicmp(s,"!drop",5))
+        { // drop from proxy
+            if(u->state!=STATE_DROPPING)
+            {
+                u->state=STATE_DROPPING;
+                q_printf(u,"Thanks for using %s.\n",versionname);
+                // disconnect server command
+                u->appendserver[u->appendserverbytes++]=0x02;
+            }
+        }
+        else if(!memicmp(s,"!sdisconnect",6))
+        { // server sends disconnect
+            if(loglevel>=LOG_VERBOSE) logx(LOG_CONSOLE,"Drop: %s dropped from %s\n",u->name,u->connecthere);
+            if(u->state!=STATE_DISCONNECTING)
+            {
+                u->state=STATE_DISCONNECTING;
+                u->dropcount=3;
+            }
+        }
+        else if(!memicmp(s,"!sdrop",6))
+        { // drop from server (called when mode is disconnecting)
+            if(u->dropcount>0)
+            {
+                q_servercmd(u,"drop");
+                u->dropcount--;
+            }
+            else
+            {
+                c_killserver(u);
+
+                if(u->servertimeoutmsg)
+                {
+                    u->servertimeoutmsg=0;
+                    q_print(u,"Server timeout.");
+                }
+
+                q_command(u,"!sdisconnect");
+                u->dropcount=0;
+                u->appendserverbytes=0;
+                u->appendclientbytes=0;
+                memset(&u->toserveraddr,0,sizeof(u->toserveraddr));
+                u->state=STATE_HERE;
+
+                q_stufftext(u,"changing\n");
+                q_print(u,"Server disconnected.");
+
+                u->time_forcedisconnect=0;
+            }
+        }
+        else if(!memicmp(s,"!kill",5))
+        { // real drop
+            c_textaddress(tmp,&u->toclientaddr);
+            if(strcmp(u->name,"<challenge>") && strcmp(u->name,"<status>"))
+            {
+                logx(LOG_CONSOLE,"Disconnect: %s from %s (users %i)\n",u->name,tmp,usernum-1);
+            }
+            c_killuser(u);
+        }
+        else if(!memicmp(s,"!errors",5))
+        { // real drop
+            c_textaddress(tmp,&u->toclientaddr);
+            logx(LOG_CONSOLE,"Net errors (disconnected): %s from %s\n",u->name,tmp);
+            c_killuser(u);
+        }
+        else if(!memicmp(s,"!timeout",5))
+        { // real drop
+            c_textaddress(tmp,&u->toclientaddr);
+            logx(LOG_CONSOLE,"Timeout (disconnected): %s from %s\n",u->name,tmp);
+            c_killuser(u);
+        }
+        else if(!memicmp(s,"!challenge",10))
+        { // received a challenge from server
+            u->havechallenge=1;
+            logx(LOG_DEBUG,"Got challenge %i\n",u->challenge);
+            u->time_nexttry=u->time+100;
+        }
+        else if(!memicmp(s,"!try",4))
+        { // try connecting to a server
+            if(u->firsttry==1)
+            {
+                *tmpbuf=0;
+                extractfield(tmpbuf,u->clientinfo,"spectator\\");
+                if(*tmpbuf=='0' || *tmpbuf==0) u->amspectator=0;
+                else u->amspectator=1;
+
+                if(u->servermainaddr.sin_port)
+                {
+                    c_textaddress(tmp,&u->servermainaddr);
+                    if(newsocketmode) memcpy(&u->toserveraddr,&u->servermainaddr,sizeof(SOCKADDR_IN));
+                }
+                else
+                {
+                    q_printf(u,"Illegal address: %s\n",u->connecthere);
+                    u->state=STATE_HERE;
+                    return;
+                }
+
+                if(u->response_b)
+                {
+                    u->response_b++;
+                    if(!u->response_b) u->response_b++;
+                }
+
+                if(u->isreconnect || c_sameaddress(&u->servermainaddr,&u->lastservermainaddr))
+                {
+                    u->time_nexttry=u->time+1600; // 1.6 sec delay
+                }
+                else
+                {
+                    u->time_nexttry=u->time+300;  // 0.3 sec delay
+                }
+                u->lastservermainaddr=u->servermainaddr;
+
+                u->firsttry=2;
+                return;
+            }
+            else if(u->firsttry==2)
+            { // flip client hidden bit to 0
+                if(u->client_hiddenbit)
+                {
+                    u->server_sendsecure=1;
+                }
+                else
+                {
+                    q_print(u,"Challenging...\n");
+                    u->firsttry=0;
+                }
+                u->time_nexttry=u->time+100; // 0.1 sec delay
+                return;
+            }
+            u->isreconnect=0;
+            u->connectingcount--;
+            if(u->challengecount<=0)
+            {
+                q_print(u,"Server not responding.\n");
+                u->state=STATE_HERE;
+            }
+            else if(u->connectingcount<=0)
+            {
+                q_print(u,"Server not responding.\n");
+                u->state=STATE_HERE;
+            }
+            else
+            {
+                if(!u->havechallenge && u->version>=22)
+                { // ask for challenge
+                    memset(tmpbuf,255,4);
+                    strcpy(tmpbuf+4,"getchallenge\n");
+                    if(loglevel>=LOG_DEBUG)
+                    {
+                        c_hexdump("Connect attempt",tmpbuf,5+strlen(tmpbuf+4),u);
+                    }
+                    c_sendservermain(u,tmpbuf,4+strlen(tmpbuf+4));
+                    u->time_nexttry=u->time+1000;
+                    u->challengecount--;
+                }
+                else
+                { // normal connect after that
+                    if(u->challenge==challenge)
+                    {
+                        q_print(u,"Cannot connect to self.\n");
+                        u->state=STATE_HERE;
+                        return;
+                    }
+                    q_print(u,"Connecting...\n");
+                    memset(tmpbuf,255,4);
+                    if(u->version>=22)
+                    {
+                        sprintf(tmpbuf+4,"connect %i %i %i %s",
+                                u->response_a,
+                                u->response_b,
+                                u->challenge,
+                                u->clientinfo);
+                    }
+                    else
+                    {
+                        sprintf(tmpbuf+4,"connect %s",
+                                u->clientinfo);
+                    }
+
+                    if(loglevel>=LOG_DEBUG)
+                    {
+                        c_hexdump("Connect attempt",tmpbuf,5+strlen(tmpbuf+4)+1,u);
+                    }
+                    c_sendservermain(u,tmpbuf,4+strlen(tmpbuf+4)+1);
+                    u->time_nexttry=u->time+3000;
+                }
+            }
+        }
+        else if(!memicmp(s,"!connect",7))
+        {
+            /*
+            lognpackets=200;
+            oldloglevel=loglevel;
+            loglevel=LOG_PACKETS;
+            logx(LOG_CONSOLE,"--------packetdump %i packets--------",lognpackets);
+            */
+
+            c_reopenserver(u);
+
+            u->connectstartsequence=u->sequence;
+            u->havechallenge=0;
+            u->connectingcount=1000000; // be persistent :)
+            u->challengecount=5;
+            u->time_nexttry=0;
+            u->servermainaddr.sin_port=0;
+            u->firsttry=1;
+            u->state=STATE_NAMELOOKUP;
+            u->time_namelookupmsg=u->time+3000;
+            u->time_reskinall=0;
+
+            if(u->isreconnect) q_printf(u,"Reconnecting to %s\n",u->connecthere);
+            else               q_printf(u,"Connecting to %s\n",u->connecthere);
+
+#ifndef WIN32
+            mainloop_low(1); // do namelookup now
+#endif
+        }
+        else if(!memicmp(s,"!disconnect",11))
+        {
+            if(u->servermainaddr.sin_port)
+            {
+                u->time_forcedisconnect=u->time+3000; // force a disconnect in 3 sec
+                u->state=STATE_DISCONNECTING;
+                u->linkedserver=0;
+                u->dropcount=3;
+                if(u->compress_client) u->compress_client=0;
+            }
+        }
+        else if(!memicmp(s,"!servertimeout",13))
+        {
+            q_command(u,"!disconnect");
+            u->servertimeoutmsg=1;
+        }
+    }
+    else
+    { // user commands
+        if(!memicmp(s,".teleport",9))
+        { // FAQ proxy sends
+            // do nothing
+        }
+        else if(!memicmp(s,".hel",4))
+        { // help
+            uchar *p;
+            nextparam(&s); // skip command
+            p=nextparam(&s);
+            q_help(u,p);
+        }
+        else if(!memicmp(s,".nam",4))
+        { // proxy name
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                p=nextparam(&s);
+
+                strncpy(buf,p,10);
+                buf[10]=0;
+                sprintf(u->proxysay,"say %s:",buf);
+                sprintf(u->proxysay2,"say %s:",buf);
+            }
+
+            if(strcmp(u->proxysay,u->proxysay2))
+            {
+                q_printf(u,"Proxy %s responds to:\n"
+                           "%scommand\n"
+                           "%scommand\n"
+                           ".command\n",
+                           u->proxyhostname,
+                           u->proxysay+4,
+                           u->proxysay2+4);
+            }
+            else
+            {
+                q_printf(u,"Proxy %s responds to:\n"
+                           "%scommand\n"
+                           ".command\n",
+                           u->proxyhostname,
+                           u->proxysay+4);
+            }
+        }
+        else if(!memicmp(s,".use",4))
+        { // users/clients
+            int i;
+            for(i=0;i<usernum;i++)
+            {
+                c_textaddress(tmp,&user[i].servermainaddr);
+                if(user[i].state==STATE_HERE) strcpy(tmp,"idle");
+                q_printf(u,"%i: %-10s %s\n",i,user[i].name,tmp);
+            }
+        }
+        else if(!memicmp(s,".con",4))
+        { // connect
+            uchar *p;
+
+            if(u->state==STATE_NAMELOOKUP)
+            {
+                q_print(u,"Namelookup still in progress.\n");
+            }
+            else if(u->state>=STATE_CONNECTED)
+            {
+                q_command(u,"!disconnect");
+            }
+            else u->state=STATE_HERE;
+
+            nextparam(&s);
+            p=nextparam(&s);
+            if(strlen(p)>1)
+            {
+                strcpy(u->connecthere,p);
+                u->doconnect=10;
+                u->isreconnect=0;
+            }
+            u->specmode=0;
+            u->specentity=0;
+        }
+        else if(!memicmp(s,".dis",4))
+        { // disconnect
+            if(u->state==STATE_NAMELOOKUP)
+            {
+                q_print(u,"Namelookup still in progress.\n");
+            }
+            else if(u->state==STATE_CONNECTED)
+            {
+                c_textaddress(tmp,&u->servermainaddr);
+                q_printf(u,"Disconnecting from %s\n",tmp);
+                q_command(u,"!disconnect");
+            }
+            else u->state=STATE_HERE;
+            memset(&u->lastservermainaddr,0,sizeof(u->lastservermainaddr));
+        }
+        else if(!memicmp(s,".rec",4))
+        { // reconnect
+            if(u->state==STATE_NAMELOOKUP)
+            {
+                q_print(u,"Namelookup still in progress.\n");
+            }
+            else
+            {
+                if(u->state==STATE_CONNECTED)
+                {
+                    q_command(u,"!disconnect");
+                }
+                u->doconnect=10;
+                u->isreconnect=1;
+            }
+        }
+        else if(!memicmp(s,".tra",4))
+        { // start tracking
+            uchar *p;
+            if(u->state<STATE_CONNECTED)
+            {
+                q_print(u,"Cannot track, not connected.\n");
+                return;
+            }
+
+            if(!u->amspectator)
+            {
+                q_print(u,"Cannot track, not spectator.\n");
+                return;
+            }
+
+            if(u->client_spectrack)
+            {
+                q_print(u,"Cannot track, must be in fly mode.\n");
+                return;
+            }
+
+            nextparam(&s);
+            p=nextparam(&s);
+            if(*p)
+            {
+                u->specmode=1;
+                u->specentity=0;
+                spectatenext(u,p);
+                if(u->specentity)
+                {
+                    q_printf(u,"Tracking \'%s\' enabled.\n",p);
+                    q_print(u,"Jump to change player. Fire to stop tracking.\n");
+                }
+                else
+                {
+                    spectatenext(u,NULL);
+                    q_printf(u,"Tracking enabled but \'%s\' not found.\n",s);
+                    q_print(u,"Jump to change player. Fire to stop tracking.\n");
+                }
+            }
+            else
+            {
+                q_print(u,"Tracking enabled.\n");
+                q_print(u,"Jump to change player. Fire to stop tracking.\n");
+                u->specmode=1;
+                u->specentity=0;
+                spectatenext(u,NULL);
+            }
+        }
+        else if(!memicmp(s,".mar",4))
+        { // mark
+            uchar *place;
+            int  a;
+            if(u->state!=STATE_CONNECTED)
+            {
+                q_print(u,"Mark unusable, not connected.\n");
+                return;
+            }
+
+            nextparam(&s);
+
+            if(!memicmp(s,"reload",6))
+            {
+                q_loadloc(u,NULL);
+            }
+            else if(!memicmp(s,"auto",4))
+            {
+                static int ordertable[]=
+                    {CAMP_RUNE,CAMP_Q,CAMP_P,CAMP_R,CAMP_RA,CAMP_RL,CAMP_YA,
+                     CAMP_GA,CAMP_MH,CAMP_GL,CAMP_LG,CAMP_SNG,0};
+                int i,j,c,d,pass,ord;
+                int redflag=-1,bluflag=-1;
+                char markname[64];
+                char markname2[64];
+                Camp *ca,*ca2;
+                u->locations=0;
+
+                // do we have flags?
+                for(i=0;i<u->camps;i++)
+                {
+                    ca=&u->camp[i];
+                    if(ca->category==CAMP_RUNE)
+                    {
+                        if(!strcmp(ca->name,"!r")) redflag=i;
+                        if(!strcmp(ca->name,"!b")) bluflag=i;
+                    }
+                }
+                if(redflag<0 || bluflag<0) redflag=bluflag=-1;
+                else
+                {
+                    q_print(u,"Marking based on red/blue flags\n");
+                }
+
+                q_print(u,"Marks replaced with automarks(tm):\n");
+
+                for(pass=0;pass<=2;pass++)
+                { // pass0=single items, 1=dual items, 2=rest of items
+                    for(ord=0;ordertable[ord];ord++)
+                    {
+                        int num,lowerz,upperz,lowercnt,uppercnt;
+
+                        c=ordertable[ord];
+
+                        num=0;
+                        lowerz= 999999;
+                        upperz=-999999;
+                        // find number of items, upper & lower
+                        for(i=0;i<u->camps;i++)
+                        {
+                            ca=&u->camp[i];
+                            if(ca->category&c)
+                            {
+                                if(ca->pos[2]<lowerz) lowerz=ca->pos[2];
+                                if(ca->pos[2]>upperz) upperz=ca->pos[2];
+                                num++;
+                            }
+                        }
+                        lowercnt=0;
+                        uppercnt=0;
+                        // find number of low / high items
+                        for(i=0;i<u->camps;i++)
+                        {
+                            ca=&u->camp[i];
+                            if(ca->category&c)
+                            {
+                                if(abs(ca->pos[2]-lowerz)<64) lowercnt++;
+                                if(abs(ca->pos[2]-upperz)<64) uppercnt++;
+                            }
+                        }
+
+                        if(!num) continue;
+                        if(pass==0 && num>1) continue;
+                        if(pass==1 && num>2) continue;
+
+                        // set texts
+                        for(i=0;i<u->camps;i++)
+                        {
+                            ca=&u->camp[i];
+                            if(ca->category&c)
+                            {
+                                for(j=0;j<u->locations;j++)
+                                {
+                                    d=abs(u->loc[j].x-ca->pos[0])*2+
+                                      abs(u->loc[j].y-ca->pos[1])*2+
+                                      abs(u->loc[j].z-ca->pos[2]);
+                                    if(d<7000) break;
+                                }
+                                if(j==u->locations)
+                                {
+                                    u->loc[u->locations].x=ca->pos[0];
+                                    u->loc[u->locations].y=ca->pos[1];
+                                    u->loc[u->locations].z=ca->pos[2];
+
+                                    *markname=0;
+                                    if(num==1)
+                                    {
+                                        sprintf(markname,"%s",ca->genericname);
+                                    }
+                                    else
+                                    {
+                                        if(lowercnt==1 && abs(ca->pos[2]-lowerz)<64)
+                                            sprintf(markname,"lower-%s",ca->genericname);
+
+                                        if(uppercnt==1 && abs(ca->pos[2]-upperz)<64)
+                                            sprintf(markname,"upper-%s",ca->genericname);
+
+                                        if(!markname[0] && *ca->name=='!')
+                                            sprintf(markname,"%s",ca->genericname);
+
+                                        if(!markname[0])
+                                            sprintf(markname,"%s%c",ca->genericname,ca->name[strlen(ca->name)-1]);
+                                    }
+
+                                    if(*markname)
+                                    {
+                                        *markname2=0;
+                                        if(redflag>=0)
+                                        {
+                                            int dred,dblu;
+                                            ca2=&u->camp[redflag];
+                                            dred=abs(ca2->pos[0]-ca->pos[0])*2+
+                                                 abs(ca2->pos[1]-ca->pos[1])*2+
+                                                 abs(ca2->pos[2]-ca->pos[2]);
+                                            ca2=&u->camp[bluflag];
+                                            dblu=abs(ca2->pos[0]-ca->pos[0])*2+
+                                                 abs(ca2->pos[1]-ca->pos[1])*2+
+                                                 abs(ca2->pos[2]-ca->pos[2]);
+                                            if(dblu*3<dred*2)
+                                            {
+                                                strcpy(markname2,"blue ");
+                                            }
+                                            else if(dred*3<dblu*2)
+                                            {
+                                                strcpy(markname2,"red ");
+                                            }
+                                        }
+
+                                        strcat(markname2,markname);
+
+                                        sprintf(buf,"- %s\n",markname2);
+                                        q_print(u,buf);
+
+                                        q_setlocname(u,u->locations,markname2);
+                                        u->locations++;
+                                        if(u->locations>=u->locationsmax-16)
+                                        {
+                                            q_growlocmax(u,u->locationsmax*2);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            else if(!memicmp(s,"delete",6))
+            { // delete mark
+                int b;
+                place=findnear(u,u->pos[0],u->pos[1],u->pos[2],&a);
+                if(a!=-1)
+                {
+                    q_explode(u,0x04,u->loc[a].x,u->loc[a].y,u->loc[a].z);
+                    q_printf(u,"%s deleted\n",place);
+#if 0
+                    q_setlocname(u,a,NULL);
+                    if(a!=u->locations-1) memmove(u->loc+a,u->loc+a+1,(u->locations-a-1)*sizeof(Location));
+                    u->locations--;
+                    memset(u->loc+u->locations,0,sizeof(Location));
+#else
+                    q_setlocname(u,a,NULL);
+                    for(b=a;b<u->locations-1;b++)
+                    {
+                        memcpy(u->loc+b,u->loc+b+1,sizeof(Location));
+                    }
+                    u->locations--;
+                    memset(u->loc+u->locations,0,sizeof(Location));
+#endif
+
+                    q_savelocs(u);
+                }
+                else
+                { // mark not found
+                    q_printf(u,"no marks\n",place);
+                }
+            }
+            else if(!*s) // report
+            {
+                place=findnear(u,u->pos[0],u->pos[1],u->pos[2],&a);
+                q_printf(u,"near %s\n",place);
+                u->appendserver[u->appendserverbytes++]=0x17;
+                u->appendserver[u->appendserverbytes++]=0x04;
+                u->appendserver[u->appendserverbytes++]=u->loc[a].x;
+                u->appendserver[u->appendserverbytes++]=u->loc[a].x>>8;
+                u->appendserver[u->appendserverbytes++]=u->loc[a].y;
+                u->appendserver[u->appendserverbytes++]=u->loc[a].y>>8;
+                u->appendserver[u->appendserverbytes++]=u->loc[a].z;
+                u->appendserver[u->appendserverbytes++]=u->loc[a].z>>8;
+            }
+            else // new mark
+            {
+                if(strlen(s)>1)
+                {
+                    q_setlocname(u,u->locations,s);
+                    u->loc[u->locations].x = u->pos[0];
+                    u->loc[u->locations].y = u->pos[1];
+                    u->loc[u->locations].z = u->pos[2];
+                    u->locations++;
+                }
+
+                q_printf(u,"%s marked\n",s);
+                q_savelocs(u);
+
+                u->appendserver[u->appendserverbytes++]=0x17;
+                u->appendserver[u->appendserverbytes++]=0x04;
+                u->appendserver[u->appendserverbytes++]=u->pos[0];
+                u->appendserver[u->appendserverbytes++]=u->pos[0]>>8;
+                u->appendserver[u->appendserverbytes++]=u->pos[1];
+                u->appendserver[u->appendserverbytes++]=u->pos[1]>>8;
+                u->appendserver[u->appendserverbytes++]=u->pos[2];
+                u->appendserver[u->appendserverbytes++]=u->pos[2]>>8;
+            }
+        }
+#ifdef TIMERS
+        else if(!memicmp(s,".cam",4))
+        { // camp these
+            nextparam(&s);
+            if(*s) u->campthese=0;
+
+            if(u->notimers)
+            {
+                sprintf(buf,"Timers disabled with .notimers.\n");
+            }
+            else if(u->fpd&FPD_TIMERS)
+            {
+                sprintf(buf,"Timers disabled.\n");
+            }
+            else
+            {
+                strcpy(buf,"Timing: ");
+                modifycampflags(s,&u->campthese,buf+strlen(buf),CAMP_P|CAMP_Q|CAMP_R,CAMP_DEF);
+                strcat(buf,"\n");
+            }
+
+            q_print(u,buf);
+        }
+        else if(!memicmp(s,".rem",4))
+        { // remind
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                u->remindcamp=0;
+            }
+
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                u->remindcamp=atoi(p);
+                if(u->remindcamp<0) u->remindcamp=0;
+                break;
+            }
+
+            if(u->remindcamp) sprintf(buf,"Remind: %i seconds before item\n",u->remindcamp);
+            else strcpy(buf,"Remind: disabled\n");
+            q_print(u,buf);
+        }
+        else if(!memicmp(s,".gue",4))
+        { // guess
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                u->secondcamp=0;
+            }
+
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                u->secondcamp=atoi(p);
+                if(u->secondcamp<=0) u->secondcamp=0;
+                if(u->secondcamp>1) u->secondcamp=1;
+                break;
+            }
+
+            if(u->secondcamp) strcpy(buf,"Guess: estimate if not known\n");
+            else strcpy(buf,"Guess: disabled\n");
+            q_print(u,buf);
+        }
+        else if(!memicmp(s,".notim",6))
+        {
+            int check=1;
+            nextparam(&s);
+            if(*s<=32) check=0;
+            if(*s==0x7f)
+            {
+                check=0;
+                s++;
+            }
+
+            if(check && u->amconnected && *s!='1')
+            {
+                q_print(u,"Notimers mode can only be cleared\n"
+                          "when not connected to a server.\n");
+            }
+            else if(check && u->linkedclient)
+            {
+                q_print(u,"Notimers mode has to be set in the\n"
+                          "first proxy. Use .notimers instead\n"
+                          "of proxy:notimers.\n");
+            }
+            else
+            {
+                char buf[64];
+
+                if(*s=='1')
+                {
+                    u->campthese=0;
+                    u->notimers=1;
+                    u->fpd|=FPD_TIMERS;
+
+                    sprintf(buf,"%s notimers",version);
+                    cl_setinfo(u,"Cheapo",buf);
+                    if(u->amconnected)
+                    {
+                        sprintf(buf,"Cheapo \"%s notimers\"",version);
+                        sendsetinfo(u,buf);
+                    }
+
+                    if(u->linkedserver)
+                    {
+                        sprintf(buf,"say .notimers \x7f%i",u->notimers);
+                        q_servercmd(u,buf);
+                    }
+                }
+                else if(*s=='0')
+                {
+                    u->campthese=0;
+                    u->notimers=0;
+                    u->fpd=0;
+
+                    sprintf(buf,"%s",version);
+                    cl_setinfo(u,"Cheapo",buf);
+
+                    if(u->linkedserver)
+                    {
+                        sprintf(buf,"say .notimers \x7f%i",u->notimers);
+                        q_servercmd(u,buf);
+                    }
+                }
+
+                if(u->notimers)
+                {
+                    q_print(u,"Notimers: powerup timers disabled\n");
+                }
+                else
+                {
+                    q_print(u,"Notimers: powerup timers enabled\n");
+                }
+            }
+        }
+#else
+        else if(!memicmp(s,".rem",4))
+        {
+            //ignore
+        }
+        else if(!memicmp(s,".gue",4))
+        {
+            //ignore
+        }
+        else if(!memicmp(s,".cam",4))
+        {
+            //ignore
+        }
+        else if(!memicmp(s,".not",4))
+        {
+            //ignore
+        }
+#endif
+        else if(!memicmp(s,".poi",4))
+        { // camp these
+            nextparam(&s);
+
+            {
+                if(*s) u->pointtell=0;
+                strcpy(buf,"%t pointing: ");
+                modifycampflags(s,&u->pointtell,buf+strlen(buf),CAMP_LAST-1,CAMP_DEF);
+                strcat(buf,"\n");
+            }
+            *u->msgline=0;
+
+            q_print(u,buf);
+        }
+        else if(!memicmp(s,".too",4))
+        { // took messages
+            nextparam(&s);
+
+            {
+                if(*s) u->autoitookitems=0;
+                strcpy(buf,"auto f_took items: ");
+                modifycampflags(s,&u->autoitookitems,buf+strlen(buf),CAMP_LAST-1,CAMP_DEFTOOK);
+                strcat(buf,"\n");
+            }
+            *u->msgline=0;
+
+            q_print(u,buf);
+            if(!u->autoitook)
+            {
+                q_print(u,"f_took disabled (see .report)\n");
+            }
+        }
+/*
+        else if(!memicmp(s,".spo",4))
+        { // camp these
+            nextparam(&s);
+            if(*s) u->pointshow=0;
+
+            {
+                strcpy(buf,"screen pointing: ");
+                modifycampflags(s,&u->pointshow,buf+strlen(buf),CAMP_LAST-1,CAMP_DEF);
+                strcat(buf,"\n");
+            }
+            *u->msgline=0;
+
+            q_print(u,buf);
+        }
+*/
+        else if(!memicmp(s,".fmo",4))
+        { // fmodified
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                u->fmodified=0;
+            }
+
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                u->fmodified=atoi(p);
+                break;
+            }
+
+            if(u->fmodified) sprintf(buf,"F_modified: enabled\n");
+            else strcpy(buf,"F_modified: disabled\n");
+            q_print(u,buf);
+            if(u->fmodified)
+            {
+                if(!*quakepath)
+                {
+                    q_print(u,"QWCL has not been started from\n"
+                              "Cheapo; f_modified will not work.\n");
+                }
+                else if(quakemodified)
+                {
+                    q_print(u,"QWCL executable has been modified;\n"
+                              "f_modified will not work.\n");
+                }
+                else if(!c_islocalconnect(&u->toclientaddr))
+                {
+                    q_print(u,"QWCL client is not local;\n"
+                              "f_modified will not work.\n");
+                }
+            }
+        }
+        else if(!memicmp(s,".mov",4))
+        { // move
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                u->moveoverlay=0;
+            }
+
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                u->moveoverlay=atoi(p);
+                if(u->moveoverlay<=0) u->moveoverlay=0;
+                if(u->moveoverlay>9) u->moveoverlay=9;
+                break;
+            }
+
+            if(u->secondcamp) sprintf(buf,"Move texts: %i lines down\n",u->moveoverlay);
+            else strcpy(buf,"Move texts: disabled\n");
+            q_print(u,buf);
+        }
+        else if(!memicmp(s,".flo",4))
+        { // flood
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                u->floodprot=0;
+            }
+
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                u->floodprot=atoi(p);
+                if(u->floodprot<=0) u->floodprot=0;
+                if(u->floodprot>1) u->floodprot=1;
+                break;
+            }
+
+            if(u->floodprot) strcpy(buf,"Flood protect protect: enabled\n");
+            else strcpy(buf,"Flood protect protect: disabled\n");
+            q_print(u,buf);
+        }
+#ifdef RLPACK
+        else if(!memicmp(s,".pac",4))
+        { // pack
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                u->classifypacks=0;
+            }
+
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                u->classifypacks=atoi(p);
+                if(u->classifypacks<=0) u->classifypacks=0;
+                if(u->classifypacks>1) u->classifypacks=1;
+                break;
+            }
+
+            if(u->classifypacks) strcpy(buf,"Packs: detect rl\n");
+            else strcpy(buf,"Packs: normal\n");
+            q_print(u,buf);
+        }
+#endif
+        else if(!memicmp(s,".cpp",4))
+        { // cpps
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                u->cpps=0;
+            }
+
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                u->cpps=atoi(p);
+                if(u->cpps<=0) u->cpps=0;
+                if(u->cpps>100) u->cpps=100;
+
+                break;
+            }
+
+            u->time_nextcpps=0;
+            if(u->cpps) u->time_cppsdelta=1000/u->cpps;
+            else u->time_cppsdelta=0;
+            u->cppsdrop=1;
+            u->cppspass=1;
+
+            if(u->cpps) sprintf(buf,"Client to server: %i packets/sec\n",u->cpps);
+            else strcpy(buf,"Client to server: no limit\n");
+            q_print(u,buf);
+        }
+#ifdef COMPRESS
+        else if(!memicmp(s,".com",4))
+        { // compress
+            uchar *p;
+            int compwas=u->compress_client;
+
+            nextparam(&s);
+            if(*s)
+            {
+                u->compress_client=0;
+
+                for(;;)
+                {
+                    p=nextparam(&s);
+                    if(!*p) break;
+
+                    u->compress_client=atoi(p);
+                    break;
+                }
+
+                if(u->compress_client && !u->linkedserver)
+                {
+                    u->compress_client=0;
+                    q_print(u,"Cannot compress: not connected\n"
+                              "to a compatible serverside proxy.\n");
+                }
+
+                if(compwas!=u->compress_client)
+                {
+                    if(u->compress_client) sprintf(buf,"Client compress: %s\n",u->proxyhostname);
+                    else                   sprintf(buf,"Client compress: disabled\n",u->proxyhostname);
+                    q_print(u,buf);
+                }
+
+                if(u->compress_client) q_print(u,"Compression: enabled\n");
+                else                   q_print(u,"Compression: disabled\n");
+            }
+            else
+            {
+                int i,c1=0,c2=0,s1=0,s2=0,cr,sr;
+                for(i=0;i<16;i++)
+                {
+                    c1+=u->packetclientinsize[i];
+                    c2+=u->packetclientoutsize[i];
+                    s1+=u->packetserverinsize[i];
+                    s2+=u->packetserveroutsize[i];
+                }
+
+                if(!c1) cr=0;
+                else cr=100*c2/c1;
+                c1>>=4;
+                c2>>=4;
+
+                if(!s1) sr=0;
+                else sr=100*s2/s1;
+                s1>>=4;
+                s2>>=4;
+
+                if(u->compress_client) q_print(u,"Compression: enabled\n");
+                else                   q_print(u,"Compression: disabled\n");
+
+                /*
+                q_print(u,"Delta counts:\n");
+                for(i=0;i<8;i++)
+                {
+                    sprintf(buf,"e%i: %i\n",i,expcount[i]);
+                    q_print(u,buf);
+                }
+                */
+
+                sprintf(buf,"Client ratio: %i%% (%i to %i)\n",cr,c1,c2);
+                q_print(u,buf);
+                sprintf(buf,"Server ratio: %i%% (%i to %i)\n",sr,s1,s2);
+                q_print(u,buf);
+            }
+        }
+#endif
+        else if(!memicmp(s,".rep",4))
+        { // autoreport
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                u->autofreport=0;
+                u->autoihave=0;
+                u->autoitook=0;
+                u->autoqpicked=0;
+            }
+
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                if(!memicmp(p,"rep",3))
+                {
+                    u->autofreport=1;
+                }
+#ifdef POWERDROP
+                if(!memicmp(p,"power2",6))
+                {
+                    u->autoihave|=2;
+                }
+#endif
+                if(!memicmp(p,"pow",3))
+                {
+                    u->autoihave|=1;
+                }
+                if(!memicmp(p,"too",3))
+                {
+                    u->autoitook=1;
+                }
+#ifdef TIMERS
+                if(!memicmp(p,"syn",3))
+                {
+                    if(!u->autoqpicked) u->autoqpicked=1;
+                }
+                if(!memicmp(p,"sho",3))
+                {
+                    u->autoqpicked=2;
+                }
+#endif
+            }
+
+            {
+                int x=0;
+                sprintf(buf,"Reporting: ");
+                if(u->autofreport) { x=1; strcat(buf,"reply_report "); }
+                if(u->autoihave&2) { x=1; strcat(buf,"powerup_2 "); }
+                if(u->autoihave&1) { x=1; strcat(buf,"powerup_have "); }
+                if(u->autoitook) { x=1; strcat(buf,"took_item "); }
+                if(u->autoqpicked==2) { x=1; strcat(buf,"show&sync_powerup "); }
+                else if(u->autoqpicked) { x=1; strcat(buf,"sync_powerup "); }
+                if(!x) strcat(buf,"nothing");
+                strcat(buf,"\n");
+                q_print(u,buf);
+            }
+        }
+#if 0
+        else if(!memicmp(s,".tim",4))
+        { // timer settings
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                u->secondcamp=0;
+                u->remindcamp=0;
+            }
+
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                if(!memicmp(p,"gue",3))
+                { // guess
+                    u->secondcamp=1;
+                }
+                else if(!memicmp(p,"syn",3))
+                { // sync
+                    u->internalsync=1;
+                }
+                else if(!memicmp(p,"rem",3))
+                { // remind
+                    p=nextparam(&s);
+                    u->remindcamp=atoi(p);
+                    if(u->remindcamp<=0) u->remindcamp=10;
+                }
+            }
+
+            {
+                int x=0;
+                sprintf(buf,"Active timer options:\n");
+                if(u->remindcamp>0) { x=1; sprintf(buf+strlen(buf),"- remind at %i seconds\n",u->remindcamp);   }
+                if(u->secondcamp)   { x=1; strcat(buf,"- guess collected at timeout\n");            }
+                if(u->internalsync) { x=1; strcat(buf,"- syncronize with players in same proxy\n"); }
+                if(!x) strcat(buf,"none\n");
+                q_print(u,buf);
+            }
+        }
+#endif
+#ifdef SPEED
+        else if(!memicmp(s,".sho",4))
+        { // show
+            int a=0;
+            char *p;
+
+            nextparam(&s);
+            if(*s>32) a=atoi(s);
+
+            u->showspeed=0;
+            u->showtime=0;
+            u->showdist=0;
+
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                if(*p=='s')
+                {
+                    u->showspeed=1;
+                }
+                if(*p=='t')
+                {
+                    u->showtime=1;
+                }
+                if(*p=='h')
+                {
+                    u->showhud=1;
+                }
+                if(*p=='d') { u->showdist=1; } }
+                u->showtimebase=u->time; u->showdistbase[0]=u->pos[0];
+                u->showdistbase[1]=u->pos[1];
+                u->showdistbase[2]=u->pos[2];
+
+            if(!u->showspeed && !u->showtime && !u->showdist && !u->showhud)
+            {
+                q_print(u,"Show: nothing\n(options: speed dist time hug)\n");
+            }
+            else
+            {
+                strcpy(buf,"Show: ");
+                if(u->showspeed) strcat(buf,"speed ");
+                if(u->showtime) strcat(buf,"time ");
+                if(u->showdist) strcat(buf,"distance ");
+                if(u->showhud) strcat(buf,"hud ");
+                strcat(buf,"\n");
+                q_print(u,buf);
+            }
+            u->overlayactive=0;
+        }
+#endif
+#ifdef PROMODE
+        else if(!memicmp(s,".deb",4))
+        {
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                *u->msgline=0;
+                u->showtime=0;
+                u->showspeed=0;
+                u->hideaxemen=0;
+            }
+
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                if(!memicmp(p,"tim",3))
+                { // time
+                    u->showtime=1;
+                    q_print(u,"Debug: show time\n");
+                }
+                else if(!memicmp(p,"spe",3))
+                { // speed
+                    u->showspeed=1;
+                    q_print(u,"Debug: show time\n");
+                }
+                else if(!memicmp(p,"dip",3))
+                { // dynamic ip test
+                    int i;
+                    for(i=0;i<MAXUSERS;i++)
+                    {
+                        memset(&user[i].toclientaddr,0,sizeof(SOCKADDR_IN));
+                    }
+                }
+                else if(!memicmp(p,"mov",3))
+                { // speed
+                    u->moveoverlay=1;
+                    q_print(u,"Debug: move overlay\n");
+                }
+                else if(!memicmp(p,"com",3))
+                { // compress
+                    int a;
+                    p=nextparam(&s);
+                    a=atoi(p);
+                    u->compress_client=a&1;
+                    u->compress_server=(a&2)>>1;
+
+                    q_printf(u,"Debug: compress cli %i ser %i\n",
+                        u->compress_client,u->compress_server);
+                }
+                else if(!memicmp(p,"cte",3))
+                { // compress test
+                    int a;
+
+                    count_load(u);
+                    count_save(u);
+
+                    p=nextparam(&s);
+                    a=atoi(p);
+                    u->compress_test=a;
+
+                    q_printf(u,"Debug: compress test %i\n",
+                        u->compress_test);
+                }
+                else if(!memicmp(p,"ser",3))
+                {
+                    sprintf(buf,"Debug: servercmd %s sent\n",s);
+                    q_print(u,buf);
+                    q_servercmd(u,s);
+                }
+                else if(!memicmp(p,"axe",3))
+                { // speed
+                    u->hideaxemen=1;
+                    q_print(u,"Debug: hide axe men (rocket arena)\n");
+                }
+                else if(!memicmp(p,"fpd",3))
+                { // fpd clear
+                    u->fpd=0;
+                    u->fpdcopy=0;
+                    q_print(u,"Debug: fpd cleared\n");
+                }
+                else if(!memicmp(p,"beg",3))
+                { // restart
+                    q_print(u,"Debug: restarting with begin\n");
+                    q_printf(u,"begin %i\n",u->lastbegin);
+                    q_servercmd(u,tmpbuf);
+                }
+                else if(!memicmp(p,"pin",3))
+                { // ping fake
+                    p=nextparam(&s);
+                    u->fakeping=atoi(p);
+                    q_printf(u,"Debug: ping test %i\n",u->fakeping);
+                }
+                else if(!memicmp(p,"fv1",3))
+                { // yes f version
+                    if(u->state==STATE_HERE)
+                    {
+                        char ver[16];
+                        sprintf(ver,"%s",version);
+                        cl_setinfo(u,"Cheapo",ver);
+                    }
+                    else
+                    {
+                        sprintf(tmpbuf,"setinfo Cheapo \"%s\"",version);
+                        q_stufftext(u,tmpbuf);
+                    }
+                    u->nofversion=0;
+                    q_print(u,"Debug: f_version enabled\n");
+                }
+                else if(!memicmp(p,"fv",2)) // also fv0
+                { // no f version
+                    if(u->state==STATE_HERE)
+                    {
+                        cl_setinfo(u,"Cheapo","");
+                    }
+                    else
+                    {
+                        sprintf(tmpbuf,"setinfo Cheapo \"\"");
+                        q_stufftext(u,tmpbuf);
+                    }
+                    u->nofversion=1;
+                    q_print(u,"Debug: f_version disabled\n");
+                }
+                else if(!memicmp(p,"poi",3))
+                { // point
+                    findpoint(u,u->pos[0],u->pos[1],u->pos[2],u->ang[0],u->ang[1],u->ang[2],-1,0,NULL);
+                }
+            }
+        }
+#endif
+        else if(!memicmp(s,".sou",4))
+        {
+            uchar *p;
+            nextparam(&s);
+            if(u->fpd&FPD_SKIN)
+            {
+                *s=0;
+                q_print(u,"Soundtrigger disabled.\n");
+            }
+            if(*s)
+            {
+                u->soundtrigger=0;
+            }
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                if((*p>='A' && *p<='Z') || (*p>='a' && *p<='z'))
+                { // do not allow letters
+                    ;
+                }
+                else
+                { // best
+                    u->soundtrigger=*p;
+                }
+            }
+
+            {
+                if(u->soundtrigger) sprintf(buf,"Soundtrigger: %c",u->soundtrigger);
+                else strcpy(buf,"Soundtrigger: disabled\n");
+                strcat(buf,"\n");
+
+                q_print(u,buf);
+            }
+        }
+        else if(!memicmp(s,".wea",4))
+        {
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                u->bestweapon=0;
+            }
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                if(!memicmp(p,"bes",3))
+                { // best
+                    u->bestweapon=1;
+                }
+                else if(p[strlen(p)-1]==':')
+                { // define/undefine own
+                    int i,seq,imp,imp2;
+
+                    imp=atoi(p);
+                    if(imp<9)
+                    {
+                        q_print(u,"Cannot redefine sequences\n"
+                                  "for impulses 1..8\n");
+                        break;
+                    }
+
+                    // find if already defined
+                    for(seq=0;seq<SEQUENCES;seq++)
+                    {
+                        if(u->impulseorder[seq][0]==imp) break;
+                    }
+                    if(seq==SEQUENCES)
+                    {
+                        for(seq=0;seq<SEQUENCES;seq++)
+                        {
+                            if(u->impulseorder[seq][0]==0)
+                            {
+                                u->impulseorder[seq][0]=imp;
+                                break;
+                            }
+                        }
+                    }
+                    if(seq==SEQUENCES)
+                    {
+                        q_printf(u,"All %i sequences are defined.\n"
+                                  "You have to delete an existing\n"
+                                  "one with '.weapon #:' where #\n"
+                                  "is the impulse to delete\n",SEQUENCES);
+                        break;
+                    }
+
+                    for(i=1;i<15;i++) u->impulseorder[seq][i]=0;
+
+                    for(i=1;i<10;i++) // limit to 10 impulses
+                    {
+                        p=nextparam(&s);
+                        if(!*p) break;
+                        imp2=atoi(p);
+                        if(!p) break;
+
+                        u->impulseorder[seq][i]=imp2;
+                    }
+                    if(i==1) u->impulseorder[seq][0]=0;
+                }
+                else if(atoi(p)==1)
+                { // enable
+                    u->bestweapon=1;
+                }
+
+                break;
+            }
+
+            {
+                int seq,i,def=0;
+                strcpy(buf,"Weapon: ");
+
+                if(u->bestweapon) strcat(buf,"select best with ammo");
+                else strcat(buf,"normal impulses");
+                strcat(buf,"\n");
+                q_print(u,buf);
+
+                for(seq=0;seq<SEQUENCES;seq++)
+                {
+                    if(u->impulseorder[seq][0])
+                    {
+                        q_printf(u,"Sequence %i: ",u->impulseorder[seq][0]);
+                        for(i=1;i<14;i++)
+                        {
+                            if(!u->impulseorder[seq][i]) break;
+                            q_printf(u,"%i ",u->impulseorder[seq][i]);
+                        }
+                        q_print(u,"\n");
+                        def=1;
+                    }
+                }
+            }
+        }
+        else if(!memicmp(s,".ski",4))
+        {
+            uchar *p;
+            nextparam(&s);
+            if(u->fpd&FPD_SKIN)
+            {
+                q_print(u,"Skin override disabled.\n");
+                u->skinoverride=0;
+            }
+            else if(u->version<22)
+            {
+                q_print(u,"Skin command requires QW 2.21 or newer\n");
+                u->skinoverride=0;
+            }
+            else if(u->fortress)
+            {
+                q_print(u,"Skin override disabled in TF.\n");
+                u->skinoverride=0;
+            }
+            else
+            {
+                if(*s)
+                {
+                    u->skinoverride=1;
+
+                    p=nextparam(&s);
+                    strncpy(u->skinname,p,15);
+
+                    p=nextparam(&s);
+                    if(*p>=32) strncpy(u->skinname2,p,15);
+                    else       strcpy(u->skinname2,"base");
+
+                    sprintf(buf,"Skin override: us \'%s\' them \'%s\'\n",u->skinname,u->skinname2);
+                    q_print(u,buf);
+
+                    u->skinoverridepos=0;
+                }
+                else
+                {
+                    if(!(u->fpd&FPD_SKIN)) q_print(u,"Skin override: off\n");
+
+                    u->skinoverride=-1; // clear
+                    u->skinoverridepos=0;
+                }
+
+                //u->time_reskinall=u->time;
+                if(u->time_reskinall==0) u->time_reskinall=u->time;
+            }
+        }
+        else if(!memicmp(s,".col",4))
+        {
+            uchar *p;
+            nextparam(&s);
+            if(u->fpd&FPD_COLOR)
+            {
+                q_print(u,"Color override disabled.\n");
+                u->coloroverride=0;
+            }
+            else if(u->version<22)
+            {
+                q_print(u,"Color command requires QW 2.21 or newer\n");
+                u->coloroverride=0;
+            }
+            else if(u->fortress)
+            {
+                q_print(u,"Color override disabled in TF.\n");
+                u->coloroverride=0;
+            }
+            else
+            {
+                if(*s)
+                {
+                    char buf[64];
+
+                    u->coloroverride=1;
+
+                    p=nextparam(&s);
+                    u->color=text2color(p);
+
+                    p=nextparam(&s);
+                    u->color2=text2color(p);
+
+                    if(u->color2==-2) u->color=-1;
+
+                    strcpy(buf,"Color override: us ");
+                    strcat(buf,color2text(u->color));
+                    strcat(buf,"them ");
+                    strcat(buf,color2text(u->color2));
+                    strcat(buf,"\n");
+                    q_print(u,buf);
+
+                    u->skinoverridepos=0;
+                }
+                else
+                {
+                    if(!(u->fpd&FPD_COLOR)) q_print(u,"Color override: off\n");
+
+                    u->coloroverride=-1; // clear
+                    u->skinoverridepos=0;
+                }
+
+                //u->time_reskinall=u->time;
+                if(u->time_reskinall==0) u->time_reskinall=u->time;
+            }
+        }
+        else if(!memicmp(s,".fps",4))
+        { // fps enhancement
+            {
+                while(*s>32) s++;
+                while(*s<=32 && *s) s++;
+                if(*s)
+                {
+                    u->fps_explosion=0;
+                    u->fps_powerup=0;
+                    u->fps_muzzle=0;
+                    u->fps_gib=0;
+                    u->fps_rocket=0;
+                    u->fps_setinfo=0;
+                    u->fps_damage=0;
+                    u->fps_nails=0;
+                }
+
+                {
+                    if(*s>='0' && *s<='9') u->fps_explosion=atoi(s);
+                    while(*s>32) s++;
+                    while(*s<=32 && *s) s++;
+                    if(*s>='0' && *s<='9') u->fps_powerup=atoi(s);
+                    while(*s>32) s++;
+                    while(*s<=32 && *s) s++;
+                    if(*s>='0' && *s<='9') u->fps_muzzle=atoi(s);
+                    while(*s>32) s++;
+                    while(*s<=32 && *s) s++;
+                    if(*s>='0' && *s<='9') u->fps_gib=atoi(s);
+                    while(*s>32) s++;
+                    while(*s<=32 && *s) s++;
+                    if(*s>='0' && *s<='9') u->fps_rocket=atoi(s);
+                    while(*s>32) s++;
+                    while(*s<=32 && *s) s++;
+                    if(*s>='0' && *s<='9') u->fps_setinfo=atoi(s);
+                    while(*s>32) s++;
+                    while(*s<=32 && *s) s++;
+                    if(*s>='0' && *s<='9') u->fps_nails=atoi(s);
+                    while(*s>32) s++;
+                    while(*s<=32 && *s) s++;
+                    if(*s>='0' && *s<='9') u->fps_damage=atoi(s);
+                }
+            }
+            sprintf(buf,"FPS enhancement: %i %i %i %i %i %i %i %i\n",
+                u->fps_explosion,u->fps_powerup,u->fps_muzzle,
+                u->fps_gib,u->fps_rocket,
+                u->fps_setinfo,u->fps_nails,u->fps_damage);
+            q_print(u,buf);
+            switch(u->fps_explosion) // explosion
+            {
+                default:
+                    u->fps_explosion=0;
+                case 0: // normal
+                    q_print(u,"- Explosions:    normal\n");
+                    break;
+                case 1: // tarbaby
+                    q_print(u,"- Explosions:    tarbaby\n");
+                    break;
+                case 2: // teleport
+                    q_print(u,"- Explosions:    teleport\n");
+                    break;
+                case 3: // blood
+                    q_print(u,"- Explosions:    blood\n");
+                    break;
+                case 4: // nothing
+                    q_print(u,"- Explosions:    nothing\n");
+                    break;
+                case 5: // shotgun
+                    q_print(u,"- Explosions:    shotgun\n");
+                    break;
+            }
+            switch(u->fps_powerup) // powerupp glow
+            {
+                default:
+                    u->fps_powerup=0;
+                case 0: // normal
+                    q_print(u,"- Powerup glow:  normal\n");
+                    break;
+                case 1: // oma pois
+                    q_print(u,"- Powerup glow:  own off\n");
+                    break;
+                case 2: // kaikki pois
+                    q_print(u,"- Powerup glow:  all off\n");
+                    break;
+            }
+            switch(u->fps_muzzle) // muzzle
+            {
+                default:
+                    u->fps_muzzle=0;
+                case 0: // normal
+                    q_print(u,"- Muzzle flash:  normal\n");
+                    break;
+                case 1: // oma pois
+                    q_print(u,"- Muzzle flash:  own off\n");
+                    break;
+                case 2: // kaikki pois
+                    q_print(u,"- Muzzle flash:  all off\n");
+                    break;
+            }
+            switch(u->fps_gib) // gib filter
+            {
+                default:
+                    u->fps_gib=0;
+                case 0: // normal (off)
+                    q_print(u,"- Gibs/bodies:   normal\n");
+                    break;
+                case 1: // on
+                    q_print(u,"- Gibs/bodies:   removed\n");
+                    break;
+                case 2: // on
+                    q_print(u,"- Gibs/bodies:   only bodies\n");
+                    break;
+            }
+            switch(u->fps_rocket) // rocket cchange
+            {
+                default:
+                    u->fps_muzzle=0;
+                case 0: // normal
+                    q_print(u,"- Rockets:       normal\n");
+                    break;
+                case 1: // no glow
+                    q_print(u,"- Rockets:       grenade (no glow)\n");
+                    break;
+            }
+            switch(u->fps_setinfo) // setinfoo filter
+            {
+                default:
+                    u->fps_setinfo=0;
+                case 0: // normal
+                    q_print(u,"- Setinfo:       not supported\n");
+                    break;
+            }
+            switch(u->fps_nails) // nails fillter
+            {
+                default:
+                    u->fps_nails=0;
+                case 0:
+                    q_print(u,"- Nails:         normal\n");
+                    break;
+                case 1:
+                    q_print(u,"- Nails:         normal\n");
+                    break;
+                case 2:
+                    q_print(u,"- Nails:         1/2\n");
+                    break;
+                case 3:
+                    q_print(u,"- Nails:         2/3\n");
+                    break;
+                case 4:
+                    q_print(u,"- Nails:         3/4\n");
+                    break;
+            }
+            switch(u->fps_damage) // damage ffilter
+            {
+                default:
+                    u->fps_damage=0;
+                case 0: // normal
+                    q_print(u,"- Damage flash:  normal\n");
+                    break;
+                case 1: // normal
+                    q_print(u,"- Damage flash:  filtered out\n");
+                    break;
+            }
+            // reset entity table
+            memset(u->entitygib,0,MAXENTITIES);
+        }
+        else if(!memicmp(s,".lin",4))
+        { // line
+            while(*s>32) s++;
+            while(*s<=32 && *s) s++;
+            u->linesright=36;
+            if(!memicmp(s,"640",3) || !memicmp(s,"gl",2))
+            {
+                u->linesdown=45;
+            }
+            else if(!memicmp(s,"512",3))
+            {
+                u->linesdown=33;
+            }
+            else if(!memicmp(s,"320",3) || !memicmp(s,"vg",2))
+            {
+                u->linesdown=11;
+            }
+            else if(*s>='0' && *s<'9')
+            {
+                u->linesdown=atoi(s);
+            }
+            while(*s>32) s++;
+            while(*s<=32 && *s) s++;
+
+            if(!memicmp(s,"cen",3)) u->linecenter=1;
+            else u->linecenter=0;
+
+            if(u->linesdown>50) u->linesdown=50;
+            if(u->linesright>50) u->linesright=50;
+
+            if(u->linecenter) sprintf(buf,"Screen lines: %i, center timers\n",u->linesdown);
+            else sprintf(buf,"Screen lines: %i\n",u->linesdown);
+            q_print(u,buf);
+        }
+        else if(!memicmp(s,".ite",4))
+        { // items
+            int i,c;
+            int intlist;
+
+            nextparam(&s);
+            intlist=atoi(s);
+#ifndef PROMODE
+            intlist=0;
+#endif
+            if(intlist<=0) intlist=0;
+
+            if(intlist==2)
+            {
+                q_print(u,"[visible item]\n");
+                for(i=0;i<MAXENTITIES;i++) if(u->entityvisible[i])
+                {
+                    sprintf(buf,"%-8s (%i,%i,%i)\n",
+                                u->entityname[i],
+                                u->entitypos[i][0],
+                                u->entitypos[i][1],
+                                u->entitypos[i][2]);
+                    q_print(u,buf);
+                }
+            }
+            else
+            {
+                q_print(u,"[item]  [nearest mark]\n");
+                for(c=CAMP_LASTAUTOMARK;c>=1;c>>=1)
+                {
+                    if(c==CAMP_LASTPOWER ||
+                       c==CAMP_LASTARMOR ||
+                       c==CAMP_LAST)
+                    {
+                        uchar tmp[36];
+                        memset(tmp,0x9e,31);
+                        tmp[0]=0x9d;
+                        tmp[30]=0x9f;
+                        tmp[31]='\n';
+                        tmp[32]=0;
+                        q_print(u,tmp);
+                    }
+                    for(i=0;i<u->camps;i++)
+                    {
+                        Camp  *ca;
+                        uchar *text;
+                        ca=&u->camp[i];
+                        if(ca->category&c)
+                        {
+                            char name[16];
+
+                            strcpy(name,ca->genericname);
+                            name[strlen(name)+1]=0;
+                            name[strlen(name)]=ca->name[strlen(ca->name)-1];
+
+                            text=findnear(u,ca->pos[0],ca->pos[1],ca->pos[2],NULL);
+                            if(!intlist) sprintf(buf,"%-8s near %s\n",name,text);
+                            else         sprintf(buf,"%-8s %s (%i,%i,%i)\n",
+                                           name,text,ca->pos[0],ca->pos[1],ca->pos[2]);
+                            q_print(u,buf);
+                        }
+                    }
+                }
+            }
+
+            if(intlist)
+            {
+                sprintf(buf,"you (%i,%i,%i) -> (%i,%i,%i)\n",
+                    u->pos[0],u->pos[1],u->pos[2],
+                    u->ang[0],u->ang[1],u->ang[2]);
+                q_print(u,buf);
+            }
+        }
+        else if(!memicmp(s,".sta",4))
+        {
+#if !defined(__APPLE__) && !defined(MACOSX)
+            int firstround=1;
+            int pingcnt=0;
+#endif /* !__APPLE__ && !MACOSX */
+            int i,n,loss=0,ping=0,avgs=0,avgc=0,ratio;
+            uchar *cmd,*p;
+
+            if(u->state==STATE_HERE)
+            {
+                q_print(u,"not connected\n");
+                return;
+            }
+
+            { // init stuff
+                for(n=0;n<STATNUM/2;n++)
+                {
+                    i=(u->lastpacketrecv+4+n)&(STATNUM-1);
+                    if(!u->packetrecvtime[i]) loss++;
+                }
+                loss=200*loss/STATNUM;
+
+                for(n=0;n<16;n++)
+                {
+                    avgs+=u->packetserversize[n];
+                    avgc+=u->packetclientsize[n];
+                }
+                avgs=(avgs+8)/16;
+                avgc=(avgc+8)/16;
+
+                ping=u->server_ping;
+
+                if(u->cpps) ratio=100*u->cppsdrop/(u->cppsdrop+u->cppspass);
+                else ratio=0;
+
+            }
+
+            cmd=s;
+            p=nextparam(&s);
+            if(*s<=32) s="pro ser net";
+            else s=cmd;
+
+            for(;;)
+            {
+                p=nextparam(&s);
+                if(!*p) break;
+
+                if(!memicmp(p,"all",3)) p=NULL;
+
+                if(!p || !memicmp(p,"pro",3))
+                { // proxies
+                    if(u->linkedclient) q_print(u,"Client side proxy present.\n");
+                    if(u->linkedserver) q_print(u,"Server side proxy present.\n");
+                }
+
+                if(!p || !memicmp(p,"ser",3))
+                { // default stats
+                    q_printf(u,"Serverside statistics:\n"
+                               "data to server  : %i bytes/sec\n"
+                               "data to client  : %i bytes/sec\n"
+                               ".cpps dropping  : %i%%\n"
+                               "packet loss     : %i%%\n"
+                               "average ping    : %i ms\n",
+                               u->bytestoserversec,
+                               u->bytestoclientsec,
+                               ratio,loss,ping);
+                }
+
+                if(!p || !memicmp(p,"ext",3))
+                { // extended stats
+                    q_printf(u,
+                           "Extended statistics:\n"
+                           "out of order-pkt: %i\n"
+                           "data to server  : %i KB\n"
+                           "data to client  : %i KB\n"
+                           "aver server-pkt : %i bytes\n"
+                           "aver client-pkt : %i bytes\n"
+                           "peak server-pkt : %i bytes\n"
+                           "peak client-pkt : %i bytes\n",
+                           u->outoforder,
+                           u->bytestoserver/1024,
+                           u->bytestoclient/1024,
+                           avgs,
+                           avgc,
+                           u->bytestoserversecmax,
+                           u->bytestoclientsecmax);
+                }
+
+                if(!p || !memicmp(p,"pin",3))
+                { // packet pings
+                    strcpy(buf,"Pings for last 8 packets:\n");
+                    for(n=0;n<8;n++)
+                    {
+                        i=(STATNUM+u->lastpacketrecv-1-n)&(STATNUM-1);
+                        if(!u->packetrecvtime[i]) sprintf(buf+strlen(buf),"  L ");
+                        else                      sprintf(buf+strlen(buf),"%3i ",u->packetrecvtime[i]-u->packetsendtime[i]);
+                    }
+                    strcat(buf,"\n");
+                    q_print(u,buf);
+                }
+
+                if(!p || !memicmp(p,"net",3))
+                { // netgraph
+                    strcpy(buf,"Netgraph for last 30 packets:\n");
+                    q_print(u,buf);
+
+                    for(n=0;n<30;n++)
+                    {
+                        i=(STATNUM+u->lastpacketrecv-1-n)&(STATNUM-1);
+                        if(!u->packetrecvtime[i])  buf[n]='L';
+                        else if(u->packetchoke[i]) buf[n]='=';
+                        else                       buf[n]='-';
+                    }
+                    buf[n]='\n';
+                    buf[n+1]=0;
+                    q_print(u,buf);
+                }
+
+                if(p && !memicmp(p,"res",3))
+                { // reset
+                    u->bytestoserver=0;
+                    u->bytestoclient=0;
+                    u->bytestoserversecmax=0;
+                    u->bytestoclientsecmax=0;
+                    u->outoforder=0;
+                }
+            }
+        }
+        else if(!memicmp(s,".dmm",4))
+        {
+            nextparam(&s);
+            if(*s<=32) ;
+            else u->forcedmm=atoi(s);
+            if(u->forcedmm<0) u->forcedmm=0;
+            if(u->forcedmm>3) u->forcedmm=3;
+
+            if(u->forcedmm) q_printf(u,"Dm-mode: assume %i\n",u->forcedmm);
+            else q_printf(u,"Dm-mode: autodetect\n");
+
+            dmmchange(u,u->originaldmm);
+        }
+        else if(!memicmp(s,".qui",4))
+        {
+            nextparam(&s);
+            if(*s<=32)
+            {
+                u->quiet=10; // for 10 packets
+            }
+            else
+            {
+                if(atoi(s)) u->quiet=0x7fffffff;
+                else u->quiet=0;
+            }
+        }
+        else if(!memicmp(s,".who",4))
+        {
+            int  i;
+            int  mode;
+
+            nextparam(&s);
+            if(*s=='f') mode=2;
+            else if(*s=='p') mode=1;
+            else mode=0;
+
+            if(mode==2)      sprintf(buf,"[name]            [fps]\n");
+            else if(mode==1) sprintf(buf,"[name]            [proxy]\n");
+            else             sprintf(buf,"[name]            [team]  [skin]\n");
+            q_print(u,buf);
+
+            for(i=0;i<34;i++) if(u->playeractive[i])
+            {
+                if(mode==2)
+                {
+                    if(u->playernear[i])
+                    {
+                        sprintf(buf,"%-17s  %3i\n",
+                                    u->playername[i],u->playerfps[i]);
+                    }
+                    else
+                    {
+                        if(u->playerfps[i])
+                        {
+                            sprintf(buf,"%-17s (%3i)\n",
+                                        u->playername[i],u->playerfps[i]);
+                        }
+                        else
+                        {
+                            sprintf(buf,"%-17s ( - )\n",
+                                        u->playername[i]);
+                        }
+                    }
+                }
+                else if(mode==1)
+                {
+                    sprintf(buf,"%-17s %s\n",u->playername[i],u->playerproxy[i]);
+                }
+                else
+                {
+                    sprintf(buf,"%-17s %-7s %-7s\n",
+                                u->playername[i],u->playerteamtext[i],u->playerskin[i]);
+                }
+                q_print(u,buf);
+            }
+        }
+        else if(!memicmp(s,".fil",4))
+        { // message filter
+            uchar *p;
+            nextparam(&s);
+            if(*s)
+            {
+                int i,j=0;
+                for(i=0;i<15;i++)
+                {
+                    p=nextparam(&s);
+                    if(!*p) break;
+                    if(*p!='#')
+                    {
+                        q_print(u,"group must start with '#'\n");
+                        i=0;
+                        break;
+                    }
+                    else
+                    {
+                        strncpy(u->filter[j],p,8);
+                        u->filter[j][8]=0;
+                        j++;
+                    }
+                }
+                u->filternum=i;
+            }
+
+            q_print(u,"Your message filter groups:\n");
+            if(!u->filternum) q_print(u,"none\n");
+            else
+            {
+                int i;
+                for(i=0;i<u->filternum;i++)
+                {
+                    q_printf(u,"%-10s",u->filter[i]);
+                    if((i%3)==2) q_print(u,"\n");
+                }
+                if((i%3)!=0) q_print(u,"\n");
+            }
+        }
+#ifdef IDENT
+        else if(!memicmp(s,".ide",4))
+        {
+            Userinfo ui;
+            int      num;
+
+            nextparam(&s);
+            if(*s=='0')
+            {
+                num=u->entity;
+            }
+            else if(*s=='f')
+            {
+                nextparam(&s);
+                strncpy(u->requestformat,s,63);
+                q_printf(u,"Ident format: %s\n",u->requestformat);
+                return;
+            }
+            else
+            {
+                num=atoi(findpoint(u,u->pos[0],u->pos[1],u->pos[2],u->ang[0],u->ang[1],u->ang[2],CAMP_PLAYER,POINT_TEAMMATENUMBER));
+            }
+
+//            sprintf(tmpbuf,"num=%i act=%i team:%08X==%08X\n",
+//                num,u->playeractive[num],u->playerteam[num],u->team);
+
+            if(num && u->playeractive[num] && u->playerteam[num]==u->team)
+            {
+                if(u->playerproxy[num]>=10000 || u->playerproxy[num]<211)
+                {
+                    // user doesn't have a proxy with this feature
+                    strcpy(u->msgline,u->playername[num]);
+                    stror80(u->msgline);
+                    u->time_nextcamp=u->time;
+                }
+                else
+                {
+                    ui.type=USERINFO_REQUEST;
+                    ui.requestfrom=num;
+                    ui.requestlong=0;
+                    u->time_request=u->time;
+                    u->requestfrom=ui.requestfrom;
+
+                    // create a setinfo command
+                    sendinfomessage(u,encryptinfo(&ui));
+                }
+            }
+        }
+#endif
+        else
+        {
+            q_printf(u,"Unknown proxy command: %s\n",s);
+        }
+    }
+}
+
+
diff --git a/compress.c b/compress.c
new file mode 100755 (executable)
index 0000000..f5cb716
--- /dev/null
@@ -0,0 +1,313 @@
+// Cheapo - a QuakeWorld Proxy\r
+// Copyright (C) 1998 Sami Tammilehto\r
+//\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+//\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\r
+// See the GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software\r
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\r
+\r
+\r
+//----------------------------- compress.c ---------------------------\r
+\r
+#include "proxy.h"\r
+\r
+#ifdef COMPRESS\r
+\r
+static uchar *rw_bitp;\r
+static int    rw_bit;\r
+\r
+void writebit(int bit)\r
+{ // bit must be 0 / 1\r
+    rw_bit|=bit;\r
+    if(rw_bit<0)\r
+    {\r
+        *rw_bitp++=rw_bit;\r
+        rw_bit=0x01000000;\r
+    }\r
+    else\r
+    {\r
+        rw_bit=rw_bit+rw_bit;\r
+    }\r
+}\r
+\r
+void writebits(int value,int bits)\r
+{\r
+    while(bits--)\r
+    {\r
+        writebit((value>>bits)&1);\r
+    }\r
+}\r
+\r
+void writebit_begin(uchar *d)\r
+{\r
+    rw_bitp=d;\r
+    rw_bit=0x01000000;\r
+}\r
+\r
+uchar *writebit_end(void)\r
+{ // returns first unused byte in stream\r
+    while(rw_bit!=0x01000000) writebit(0);\r
+    return(rw_bitp);\r
+}\r
+\r
+int readbit(void)\r
+{\r
+    if(rw_bit<0)\r
+    {\r
+        rw_bit=0x00800000|(*rw_bitp++);\r
+    }\r
+    rw_bit=rw_bit+rw_bit;\r
+    return( (rw_bit>>8) & 1);\r
+}\r
+\r
+int readbits(int bits)\r
+{\r
+    int value=0;\r
+    while(bits--)\r
+    {\r
+        value<<=1;\r
+        value|=readbit();\r
+    }\r
+    return(value);\r
+}\r
+\r
+void readbit_begin(uchar *d)\r
+{\r
+    rw_bitp=d;\r
+    rw_bit=-1;\r
+}\r
+\r
+uchar *readbit_pos(void)\r
+{ // returns first unused byte in stream\r
+    return(rw_bitp);\r
+}\r
+\r
+int compress_raw(uchar *d,uchar *s,int inbytes)\r
+{\r
+    memcpy(d,s,inbytes);\r
+    return(inbytes);\r
+}\r
+\r
+int uncompress_raw(uchar *d,uchar *s,int inbytes)\r
+{\r
+    memcpy(d,s,inbytes);\r
+    return(inbytes);\r
+}\r
+\r
+/**/\r
+\r
+int compress_header(User *u,uchar *d,uchar *s,int server)\r
+{\r
+    int seqthis,seqrecv,bitthis,bitrecv;\r
+\r
+    readheader(s,&seqthis,&seqrecv,&bitthis,&bitrecv);\r
+    *d++=(seqthis&0xff);\r
+    *d++=((seqthis>>8)&0x3f)|0x40|(bitthis?0x80:0);\r
+    *d++=(seqrecv&0xff);\r
+    *d++=((seqrecv>>8)&0x3f)|0x40|(bitrecv?0x80:0);\r
+\r
+    return(4);\r
+}\r
+\r
+int uncompress_header(User *u,uchar *d,uchar *s,int server)\r
+{\r
+    int seqthis,seqrecv,bitthis,bitrecv;\r
+    int flip=0;\r
+\r
+    seqthis = s[0];\r
+    seqthis|=(s[1]&0x3f)<<8;\r
+    bitthis = s[1]&0x80;\r
+\r
+    seqrecv = s[2];\r
+    seqrecv|=(s[3]&0x3f)<<8;\r
+    bitrecv = s[3]&0x80;\r
+\r
+    if(server)\r
+    {\r
+        seqthis|=u->server_lastthis&0xffffc000;\r
+        if((seqthis&0x3fff)<(u->server_lastthis&0x3fff))\r
+        {\r
+            flip=1;\r
+            seqthis+=0x4000;\r
+        }\r
+        seqrecv|=u->server_lastrecv&0xffffc000;\r
+        if((seqrecv&0x3fff)<(u->server_lastrecv&0x3fff))\r
+        {\r
+            flip=1;\r
+            seqrecv+=0x4000;\r
+        }\r
+    }\r
+    else\r
+    {\r
+        seqthis|=u->client_lastthis&0xffffc000;\r
+        if((seqthis&0x3fff)<(u->client_lastthis&0x3fff))\r
+        {\r
+            flip=1;\r
+            seqthis+=0x4000;\r
+        }\r
+        seqrecv|=u->client_lastrecv&0xffffc000;\r
+        if((seqrecv&0x3fff)<(u->client_lastrecv&0x3fff))\r
+        {\r
+            flip=1;\r
+            seqrecv+=0x4000;\r
+        }\r
+    }\r
+\r
+    writeheader(d,&seqthis,&seqrecv,&bitthis,&bitrecv);\r
+\r
+    return(8);\r
+}\r
+\r
+int compress_client(User *u,uchar *d,uchar *s,int inbytes)\r
+{\r
+    inbytes=compress_raw(d,s+2,inbytes-2);\r
+    return(inbytes);\r
+}\r
+\r
+int uncompress_client(User *u,uchar *d,uchar *s,int inbytes)\r
+{\r
+    inbytes=uncompress_raw(d+2,s,inbytes);\r
+    d[0]=u->response_b&255;\r
+    d[1]=u->response_b>>8;\r
+    inbytes+=2;\r
+    return(inbytes);\r
+}\r
+\r
+int compress_server(User *u,uchar *d,uchar *s,int inbytes)\r
+{\r
+    compress_count(s,inbytes);\r
+    inbytes=compress_raw(d,s,inbytes);\r
+    return(inbytes);\r
+}\r
+\r
+int uncompress_server(User *u,uchar *d,uchar *s,int inbytes)\r
+{\r
+    inbytes=uncompress_raw(d,s,inbytes);\r
+    return(inbytes);\r
+}\r
+\r
+void uncompress(User *u,uchar *inbuf,int *inbytes,int server)\r
+{\r
+    int bytes=0,n;\r
+    int showcompressstatus=0;\r
+\r
+    if(intat(inbuf)==-1) return;\r
+\r
+    if(!(inbuf[3]&0x40))\r
+    {\r
+        if(!server && u->compress_server)\r
+        { // received an uncompressed client packet, stop packing\r
+            showcompressstatus=1;\r
+            u->compress_server=0;\r
+        }\r
+    }\r
+    else\r
+    {\r
+        // uncompress\r
+        if(server)\r
+        { // server packet\r
+            bytes+=uncompress_header(u,tmpbuf,inbuf,1);\r
+            n=uncompress_server(u,tmpbuf+bytes,inbuf+4,*inbytes-4);\r
+            if(n<0)\r
+            {\r
+                n=0;\r
+                u->compress_server=0;\r
+                u->compress_client=0;\r
+                q_print(u,"Server packet uncompress error. Compression disabled.\n");\r
+            }\r
+            bytes+=n;\r
+        }\r
+        else\r
+        { // client packet\r
+            // received a compressed client packet, start packing\r
+            if(!u->compress_server && !u->compress_test)\r
+            {\r
+                showcompressstatus=1;\r
+                u->compress_server=1;\r
+            }\r
+            bytes+=uncompress_header(u,tmpbuf,inbuf,0);\r
+            n=uncompress_client(u,tmpbuf+bytes,inbuf+4,*inbytes-4);\r
+            if(n<0)\r
+            {\r
+                n=0;\r
+                u->compress_server=0;\r
+                u->compress_client=0;\r
+                q_print(u,"Client packet uncompress error. Compression disabled.\n");\r
+            }\r
+            bytes+=n;\r
+        }\r
+\r
+        if(loglevel>=LOG_PACKETS)\r
+        {\r
+            if(server) logx(LOG_PACKETS,"uncompress-server");\r
+            else       logx(LOG_PACKETS,"uncompress-client");\r
+            logx(LOG_PACKETS," %i -> %i bytes\n",\r
+                *inbytes,bytes);\r
+        }\r
+\r
+        memcpy(inbuf,tmpbuf,bytes);\r
+        *inbytes=bytes;\r
+        u->dumpsentpacket=1;\r
+    }\r
+\r
+    if(showcompressstatus)\r
+    {\r
+        if(u->compress_server) sprintf(tmpbuf,"Server compress: %s\n",u->proxyhostname);\r
+        else                   sprintf(tmpbuf,"Server compress: disabled\n",u->proxyhostname);\r
+        q_print(u,tmpbuf);\r
+    }\r
+}\r
+\r
+void compress(User *u,uchar *inbuf,int *inbytes,int server)\r
+{ // uses tmpbuf\r
+    int bytes=0;\r
+\r
+    if(intat(inbuf)==-1) return;\r
+\r
+    if(server)\r
+    { // server packet\r
+        if(!u->compress_server) return;\r
+        bytes+=compress_header(u,tmpbuf,inbuf,1);\r
+        bytes+=compress_server(u,tmpbuf+bytes,inbuf+8,*inbytes-8);\r
+    }\r
+    else\r
+    { // client packet\r
+        if(!u->compress_client) return;\r
+        bytes+=compress_header(u,tmpbuf,inbuf,0);\r
+        bytes+=compress_client(u,tmpbuf+bytes,inbuf+8,*inbytes-8);\r
+    }\r
+\r
+    if(loglevel>=LOG_PACKETS)\r
+    {\r
+        if(server) logx(LOG_PACKETS,"compress-server");\r
+        else       logx(LOG_PACKETS,"compress-client");\r
+        logx(LOG_PACKETS," %i -> %i bytes\n",\r
+            *inbytes,bytes);\r
+    }\r
+\r
+    memcpy(inbuf,tmpbuf,bytes);\r
+    *inbytes=bytes;\r
+    u->dumpsentpacket=1;\r
+}\r
+\r
+#else\r
+\r
+void compress(User *u,uchar *inbuf,int *inbytes,int server)\r
+{\r
+}\r
+\r
+void uncompress(User *u,uchar *inbuf,int *inbytes,int server)\r
+{\r
+}\r
+\r
+#endif\r
+\r
diff --git a/main.c b/main.c
new file mode 100755 (executable)
index 0000000..144ee84
--- /dev/null
+++ b/main.c
@@ -0,0 +1,1655 @@
+// Cheapo - a QuakeWorld Proxy
+// Copyright (C) 1998 Sami Tammilehto
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+#include "proxy.h"
+
+#define  WS_VERSION_REQUIRED   0x0101
+#define  MIN_SOCKETS_REQUIRED  (MAXUSERS*2+4)
+
+int    newsocketmode=0;
+uchar   versionbuf[64];
+uchar   versionnamebuf[64];
+uchar  *version=versionbuf;
+uchar  *versionname=versionnamebuf;
+
+SOCKADDR_IN proxy_serverip;
+SOCKADDR_IN proxy_clientip;
+
+#ifdef WIN32
+int    useselect=0;
+#else
+int    useselect=1;
+#endif
+
+int    noautoexit=0;
+int    litemode=0;
+int    autotimeout=1;
+int    loglevel=0;
+int    lograwpackets=0;
+int    originalloglevel=0;
+int    lognpackets=0;
+int    oldloglevel=0;
+int    hpymode=0;
+
+int    priority=0;
+int    prioritytoggle=0;
+
+FILE   *lograwc;
+FILE   *lograws;
+
+int    stopping=0;
+
+DWORD  timeout=0x7fffffff;
+DWORD  timeouttime=1000*60*240; // 240 minutes
+
+FILE  *logfile;
+
+SOCKET consocket;
+
+int    usernum=0;
+int    maxusernum=MAXPLAYERS;
+User   user[MAXUSERS];
+
+#ifdef EXPIRETIME
+uchar  *versiontype="";
+char   *betanotes=
+"\n"
+"release notes:\n"
+"\n";
+#else
+uchar  *versiontype="";
+#endif
+
+void cleanup(int type);
+void setversions(void);
+
+#ifndef WIN32
+
+void Sleep(int ms)
+{
+    struct timeval delay;
+    delay.tv_sec=0;
+    delay.tv_usec=ms*1000;
+    select(0,NULL,NULL,NULL,&delay);
+}
+
+int memicmp(uchar *a,uchar *b,int len)
+{
+    while(len>0)
+    {
+        if(((*a)|0x20)!=((*b)|0x20)) return(1);
+        a++; b++; len--;
+    }
+    return(0);
+}
+
+int stricmp(uchar *a,uchar *b)
+{
+    int al,bl;
+    al=strlen(a);
+    bl=strlen(b);
+    if(al!=bl) return(1);
+    return(memicmp(a,b,al));
+}
+
+int strlwr(uchar *a)
+{
+    while(*a)
+    {
+        *a=tolower(*a);
+        a++;
+    }
+#if defined(__APPLE__) || defined(MACOSX)
+    return (0);
+#endif /* __APPLE__ || MACOSX */
+}
+
+#endif
+
+int timeus(void)
+{ // return microseconds (win95 only)
+#ifdef PERFTIME
+    LONGLONG t;
+    LARGE_INTEGER pc;
+    static int initdone;
+    static LARGE_INTEGER perf_freq,perf_counter;
+    if(!initdone)
+    {
+        if(!QueryPerformanceFrequency(&perf_freq))
+        {
+            printf("This computer does not support Pentium Performance Counter\n");
+            exit(3);
+        }
+        QueryPerformanceCounter(&perf_counter);
+        initdone=1;
+    }
+    QueryPerformanceCounter(&pc);
+    t=1000000*(pc.QuadPart-perf_counter.QuadPart)/perf_freq.QuadPart;
+    return((int)t);
+#else
+    return(0);
+#endif
+}
+
+int timems(void)
+{ // return milliseconds
+#ifdef GETTIMEOFDAY
+    struct timeval  tv;
+    struct timezone tz;
+    static int initdone;
+    static int seconds0;
+    gettimeofday(&tv,&tz);
+    if(!initdone)
+    {
+        seconds0=tv.tv_sec;
+        initdone=1;
+    }
+    return((tv.tv_sec-seconds0)*1000+tv.tv_usec/1000);
+#endif
+#ifdef PERFTIME
+    LONGLONG t;
+    LARGE_INTEGER pc;
+    static int initdone;
+    static LARGE_INTEGER perf_freq,perf_counter;
+    if(!initdone)
+    {
+        if(!QueryPerformanceFrequency(&perf_freq))
+        {
+            printf("This computer does not support Pentium Performance Counter\n");
+            exit(3);
+        }
+        QueryPerformanceCounter(&perf_counter);
+        initdone=1;
+    }
+    QueryPerformanceCounter(&pc);
+    t=1000*(pc.QuadPart-perf_counter.QuadPart)/perf_freq.QuadPart;
+    return((int)t);
+#endif
+#ifdef HRTIME
+    hrtime_t tim;
+    static hrtime_t tim0;
+    static int initdone;
+    tim=gethrtime();
+    if(!initdone)
+    {
+        tim0=tim;
+        initdone=1;
+    }
+    tim-=tim0;
+    return((int)(tim/1000000));
+#endif
+#ifdef FTIME
+    struct timeb tim;
+    static int   seconds0;
+    static int   initdone;
+    int ms;
+    ftime(&tim);
+    if(!initdone)
+    {
+        seconds0=tim.time;
+        initdone=1;
+    }
+    ms=(tim.time-seconds0)*1000+tim.millitm;
+    return(ms);
+#endif
+#ifdef WINTIME
+    return(GetCurrentTime());
+#endif
+}
+
+#ifdef EXPIRETIME
+static int expires_time;
+#endif
+
+void expires_gettime(void)
+{
+#ifdef EXPIRETIME
+    time(&expires_time);
+#endif
+}
+
+void log_open(void)
+{
+    if(loglevel>0) logfile=fopen("proxy.log","wt");
+}
+
+void log_close(void)
+{
+    if(loglevel>0) fclose(logfile);
+}
+
+void log_flush(void)
+{
+    if(loglevel>0)
+    {
+        fflush(logfile);
+    }
+}
+
+static int dontflush;
+
+void logx(int level,uchar *txt,...)
+{
+    static uchar buf[BUFSIZE];
+    va_list argp;
+
+    if(loglevel<0) return;
+    if(level>LOG_CONSOLE && level>loglevel) return;
+    va_start(argp,txt);
+    vsprintf(buf,txt,argp);
+    if(level<=LOG_CONSOLE)
+    {
+        fputs(buf,stdout);
+    }
+    if(level<=loglevel && logfile)
+    {
+        fputs(buf,logfile);
+        if(level==LOG_CONSOLE) fflush(logfile);
+    }
+    if(!dontflush && loglevel==LOG_FLUSH) fflush(logfile);
+}
+
+void *mymalloc(int size)
+{
+    static void *safe;
+    void *p;
+
+    if(!size) size=1;
+
+    if(!safe)
+    { // allocate some safety (get core for us)
+        safe=malloc(256*1024); // 256K safety
+        if(!safe)
+        {
+            logx(LOG_CONSOLE,"Out of memory!\n");
+            printf("Out of memory!\n");
+            exit(3);
+        }
+    }
+
+    p=malloc(size);
+
+    if(!p)
+    {
+        logx(LOG_CONSOLE,"Running low on memory!\n");
+        // free safety and try to alloc again
+        if(safe)
+        {
+            free(safe);
+        }
+
+        p=malloc(size);
+        if(!p)
+        {
+            logx(LOG_CONSOLE,"Out of memory!\n");
+            printf("Out of memory!\n");
+            exit(3);
+        }
+    }
+
+    return(p);
+}
+
+void myfree(void *p)
+{
+    if(p) free(p);
+}
+
+void expires_check(void)
+{
+#ifdef EXPIRETIME
+    if(expires_time>EXPIRETIME+172800)
+    {
+        exit(3);
+    }
+#endif
+}
+
+void ws_init(void)
+{
+#ifdef WIN32
+    WSADATA      wsaData;
+    int          error;
+
+    error=WSAStartup(WS_VERSION_REQUIRED,&wsaData);
+    if (error !=0 ) {
+          logx(LOG_CONSOLE,"error: Windows Sockets not responding or compatible");
+          exit(3);
+    }
+
+    if (wsaData.wVersion < WS_VERSION_REQUIRED)
+    {
+          logx(LOG_CONSOLE,"error: Windows Sockets version %d.%d not supported",
+                 LOBYTE (wsaData.wVersion), HIBYTE (wsaData.wVersion));
+          exit(3);
+    }
+
+    if (wsaData.iMaxSockets < MIN_SOCKETS_REQUIRED ) {
+          logx(LOG_CONSOLE,"error: Windows Sockets could not support %d sockets",
+                 MIN_SOCKETS_REQUIRED);
+          exit(3);
+    }
+#endif
+}
+
+void ws_deinit(void)
+{
+#ifdef WIN32
+    int   error;
+    error=WSACleanup();
+    if (error !=0 ) {
+          logx(LOG_CONSOLE,"error: Windows Sockets cleanup failed.");
+          exit(3);
+    }
+#endif
+}
+
+void ws_showerror(User *u,uchar *when)
+{
+#ifdef WIN32
+    int err=WSAGetLastError();
+    if(err==WSAENOTSOCK)
+    {
+        static int reportonce=0;
+        if(!reportonce)
+        {
+            logx(LOG_VERBOSE,"error: Windows Sockets error %d calling %s [once]\n",err,when);
+            reportonce=1;
+        }
+        return;
+    }
+    logx(LOG_VERBOSE,"error: Windows Sockets error %d calling %s\n",err,when);
+#endif
+    if(u) u->errors++;
+}
+
+int ws_openserver(int port)
+{
+//    unsigned long flag;
+
+    consocket=socket(PF_INET,SOCK_DGRAM,0);
+    if(consocket==INVALID_SOCKET) ws_showerror(NULL,"socket");
+
+    if(bind(consocket,(LPSOCKADDR)&proxy_serverip,sizeof(proxy_serverip))==SOCKET_ERROR)
+    {
+        ws_showerror(NULL,"bind");
+        logx(LOG_CONSOLE,"Unable to bind to port %i\n",port);
+        return(1);
+    }
+
+//    flag=1; // nonblocking
+//    ioctlsocket(consocket,FIONBIO,&flag);
+    return(0);
+}
+
+void c_hexdump(uchar *msg,uchar *buffer0,int bytes,User *u)
+{
+    uchar *buffer=(uchar *)buffer0;
+    int i,j,n,s0,s1,a;
+
+    dontflush=1;
+    s0=intat(buffer+0);
+    s1=intat(buffer+4);
+    if(u)
+    {
+        logx(LOG_PACKETS,"\n%s [%s] (%i bytes): %i%c %i%c\n",
+            msg,u->name,bytes,
+            s0==0xffffffff?0:s0&0x7fffffff,s0&0x80000000?'x':'.',
+            s1==0xffffffff?0:s1&0x7fffffff,s1&0x80000000?'x':'.');
+    }
+    else
+    {
+        logx(LOG_PACKETS,"\n%s [] (%i bytes): %i%c %i%c\n",
+            msg,bytes,
+            s0==0xffffffff?0:s0&0x7fffffff,s0&0x80000000?'x':'.',
+            s1==0xffffffff?0:s1&0x7fffffff,s1&0x80000000?'x':'.');
+    }
+
+    n=(bytes+15)/16*16;
+    for(i=0;i<n;i+=16)
+    {
+        for(j=i;j<i+16;j++)
+        {
+            if(j<bytes) logx(LOG_PACKETS,"%02X ",buffer[j]);
+            else logx(LOG_PACKETS,"   ");
+        }
+        logx(LOG_PACKETS," ");
+        for(j=i;j<i+16;j++)
+        {
+            if(j<bytes)
+            {
+                a=buffer[j]&127;
+                if(a<32) logx(LOG_PACKETS,".");
+                else logx(LOG_PACKETS,"%c",a);
+            }
+            else logx(LOG_PACKETS," ");
+        }
+        logx(LOG_PACKETS,"\n");
+    }
+    if(lognpackets>0)
+    {
+        lognpackets--;
+        if(!lognpackets)
+        {
+            loglevel=oldloglevel;
+        }
+    }
+    dontflush=0;
+}
+
+User *c_finduser(SOCKADDR_IN *from)
+{
+    int   i;
+
+    for(i=0;i<usernum;i++)
+    {
+        if(c_sameaddress(from,&user[i].toclientaddr)) return(user+i);
+    }
+    return(NULL);
+}
+
+void c_reopenserver(User *u)
+{
+    SOCKET news;
+    SOCKADDR_IN tmp;
+
+    if(newsocketmode) return;
+
+    news=socket(PF_INET,SOCK_DGRAM,0);
+    if(news==INVALID_SOCKET) ws_showerror(NULL,"socket2");
+    closesocket(u->toserver);
+    u->toserver=news;
+
+    memcpy(&tmp,&proxy_clientip,sizeof(SOCKADDR_IN));
+    tmp.sin_port=htons(0);
+    bind(u->toserver,(LPSOCKADDR)&tmp,sizeof(tmp));
+
+    {
+        char buf[64];
+        SOCKADDR_IN addr;
+        unsigned int addrlen=sizeof(SOCKADDR_IN);
+        memset(&addr,0,sizeof(addr));
+        getsockname(u->toserver,(void *)&addr,&addrlen);
+        c_textaddress(buf,&addr);
+        logx(LOG_PACKETS,"Toserver socket %s\n",buf);
+    }
+}
+
+User *c_newuser(SOCKADDR_IN *from)
+{
+    User *u;
+
+    if(usernum==MAXUSERS) return(NULL);
+
+    u=user+usernum;
+    memset(u,0,sizeof(User));
+
+    if(newsocketmode) u->toserver=consocket;
+    else
+    {
+        u->toserver=socket(PF_INET,SOCK_DGRAM,0);
+        if(u->toserver==INVALID_SOCKET) ws_showerror(NULL,"socket3");
+    }
+    u->toclientaddr=*from;
+
+    /*
+    {
+        SOCKADDR_IN tmp;
+        tmp.sin_family=AF_INET;
+        tmp.sin_addr.s_addr=htonl(INADDR_ANY);
+        tmp.sin_port=htons(0);
+        bind(u->toclient,(LPSOCKADDR)&tmp,sizeof(tmp));
+    }
+
+    {
+        char buf[64];
+        SOCKADDR_IN addr;
+        int addrlen=sizeof(SOCKADDR_IN);
+        memset(&addr,0,sizeof(addr));
+        getsockname(u->toclient,(void *)&addr,&addrlen);
+        c_textaddress(buf,&addr);
+        logx(LOG_PACKETS,"Toclient socket %s\n",buf);
+    }
+    */
+
+//    flag=1; // nonblocking io
+//    ioctlsocket(u->toserver,FIONBIO,&flag);
+//    ioctlsocket(u->toclient,FIONBIO,&flag);
+
+    u->locations=0;
+    u->locationsmax=256;
+    u->loc=mymalloc(sizeof(Location)*u->locationsmax);
+    memset(u->loc,0,sizeof(Location)*u->locationsmax);
+
+    usernum++;
+    return(u);
+}
+
+void c_killuser(User *u)
+{
+    int   i,uid;
+
+    uid=u-user;
+    if(uid<0 || uid>=MAXUSERS) return;
+
+    if(!newsocketmode)
+    {
+        closesocket(u->toserver);
+        //closesocket(u->toclient);
+    }
+
+    for(i=0;i<MAXMODELS;i++)
+    {
+        if(u->model[i]) free(u->model[i]);
+    }
+    for(i=0;i<MAXSOUNDS;i++)
+    {
+        if(u->sound[i]) free(u->sound[i]);
+    }
+    for(i=0;i<u->locationsmax;i++)
+    {
+        if(u->loc[i].name) free(u->loc[i].name);
+    }
+    free(u->loc);
+    u->loc=NULL;
+
+    memcpy(user+uid,user+usernum-1,sizeof(User));
+    usernum--;
+}
+
+void c_killserver(User *u)
+{
+    SOCKET news;
+//    int flag;
+
+    if(!newsocketmode)
+    {
+        news=socket(PF_INET,SOCK_DGRAM,0);
+
+        closesocket(u->toserver);
+        u->toserver=news;
+    }
+
+    u->time_lastserver=0;
+
+    u->servermainaddr.sin_port=0;
+
+//    flag=1; // nonblocking io
+//    ioctlsocket(u->toserver,FIONBIO,&flag);
+}
+
+void c_textaddress(uchar *text,SOCKADDR_IN *a)
+{
+    if(a->sin_port)
+    {
+        strcpy(text,inet_ntoa(a->sin_addr));
+        sprintf(text+strlen(text),":%i",ntohs(a->sin_port));
+    }
+    else strcpy(text,"-");
+}
+
+int c_sameaddress(SOCKADDR_IN *a,SOCKADDR_IN *b)
+{
+   if(a->sin_addr.s_addr!=b->sin_addr.s_addr) return(0);
+   if(a->sin_port!=b->sin_port) return(0);
+   return(1);
+}
+
+int c_islocalconnect(SOCKADDR_IN *a)
+{
+    unsigned int lh,lhm;
+    if(a->sin_port!=htons(27001)) return(0);
+    lh =htonl(0x7f000000);
+    lhm=htonl(0xff000000);
+    if((a->sin_addr.s_addr&lhm)==lh) return(1);
+    return(0);
+}
+
+void c_setaddress(SOCKADDR_IN *a,uchar *text)
+{
+    uchar addr[256];
+    uchar port[8];
+    uchar *p;
+    struct hostent *he;
+
+    logx(LOG_DEBUG,"Namelookup '%s'\n",text);
+
+    p=addr;
+    while(*text && *text>32 && *text!=':' && p<addr+255)
+    {
+        *p++=*text++;
+    }
+    if(*text==':') text++;
+    *p=0;
+
+    p=port;
+    while(*text && p<port+7)
+    {
+        *p++=*text++;
+    }
+    *p=0;
+
+    a->sin_family=AF_INET;
+    a->sin_port=htons((unsigned short)atoi(port));
+    if(!a->sin_port) a->sin_port=htons(27500);
+
+        a->sin_addr.s_addr=inet_addr(addr);
+    if(a->sin_addr.s_addr==-1)
+    { // not xxx.xxx.xxx.xxx, do lookup
+        he=gethostbyname(addr);
+        if(!he)
+        { // dns failed
+            a->sin_addr.s_addr=0;
+        }
+        else
+        {
+            memcpy(&a->sin_addr,he->h_addr_list[0],sizeof(struct in_addr));
+        }
+    }
+
+    if(a->sin_addr.s_addr==0xffffffff || a->sin_addr.s_addr==0x0)
+    {
+        a->sin_addr.s_addr=0;
+        a->sin_port=0;
+    }
+
+    logx(LOG_DEBUG,"Namelookup result %08X:%i\n",a->sin_addr.s_addr,atoi(port));
+}
+
+void c_sendservermain(User *u,uchar *buffer,int bytes)
+{
+    int error;
+    if(loglevel>=LOG_PACKETS) c_hexdump("Proxy->ServerConnect",buffer,bytes,u);
+    error=sendto(u->toserver,buffer,bytes,0,(LPSOCKADDR)&u->servermainaddr,sizeof(SOCKADDR_IN));
+    if(error==SOCKET_ERROR)
+    {
+        uchar tmp[64];
+        c_textaddress(tmp,&u->servermainaddr);
+        ws_showerror(u,"sendservermain");
+        logx(LOG_CONSOLE,"servermainaddr: %s",tmp);
+    }
+}
+
+void c_sendserver(User *u,uchar *buffer,int bytes)
+{
+    int error;
+    if(lograwpackets && u->state==STATE_CONNECTED)
+    {
+        fwrite(&bytes,1,4,lograwc);
+        fwrite(buffer,1,bytes,lograwc);
+    }
+    u->bytestoserversec1+=bytes;
+    u->bytestoserver+=bytes;
+    if(u->dumpsentpacket) if(loglevel>=LOG_PACKETS) c_hexdump("Proxy->Server",buffer,bytes,u);
+    u->dumpsentpacket=0;
+    error=sendto(u->toserver,buffer,bytes,0,(LPSOCKADDR)&u->toserveraddr,sizeof(SOCKADDR_IN));
+    if(error==SOCKET_ERROR) ws_showerror(u,"sendserver");
+}
+
+void c_sendclient(User *u,uchar *buffer,int bytes)
+{
+    int error;
+    if(lograwpackets && u->state==STATE_CONNECTED)
+    {
+        fwrite(&bytes,1,4,lograws);
+        fwrite(buffer,1,bytes,lograws);
+    }
+    u->bytestoclientsec1+=bytes;
+    u->bytestoclient+=bytes;
+    if(u->dumpsentpacket) if(loglevel>=LOG_PACKETS) c_hexdump("Proxy->Client",buffer,bytes,u);
+    u->dumpsentpacket=0;
+    error=sendto(consocket,buffer,bytes,0,(LPSOCKADDR)&u->toclientaddr,sizeof(SOCKADDR_IN));
+    if(error==SOCKET_ERROR) ws_showerror(u,"sendclient");
+}
+
+void c_sendto(SOCKADDR_IN *to,uchar *buffer,int bytes)
+{
+    int error;
+    error=sendto(consocket,buffer,bytes,0,(LPSOCKADDR)to,sizeof(SOCKADDR_IN));
+    if(error==SOCKET_ERROR) ws_showerror(NULL,"sendto");
+}
+
+int startqwcl_nochecks(char *cmd, char *cmdline)
+{
+#ifdef WIN32
+       char execmdline[2048];
+       char exename[1024];
+       char exepath[1024];
+       char exedir[1024];
+    char *exepathname;
+       STARTUPINFO si;
+    PROCESS_INFORMATION pi;
+
+    if(strlen(cmd)>256) return(5);
+    if(strlen(cmdline)>1024) return(5);
+
+    strcpy(exename,cmd);
+    if(strlen(exename)<4 || stricmp(exename+strlen(exename)-4,".exe") )
+    {
+        strcat(exename,".EXE");
+    }
+       if(!GetFullPathName(exename, 1024, exepath, &exepathname))
+    {
+           return(1);
+    }
+    strcpy(exedir,exepath);
+    exedir[exepathname-exepath]=0;
+
+    strcpy(execmdline,exepath);
+    strcat(execmdline," ");
+    strcat(execmdline,cmdline);
+
+       memset(&si, 0, sizeof(si));
+       si.cb = sizeof(si);
+
+       if(!CreateProcess(exepath,execmdline,0,0,TRUE,0,0,exedir,&si,&pi))
+       {
+               return 4;
+       }
+
+    WaitForSingleObject(pi.hProcess,INFINITE);
+#endif
+       return 0;
+}
+
+int mainloop_startqw(int doonce)
+{ // low priority
+#ifdef WIN32
+    int ret;
+#endif /* WIN32 */
+
+    logx(LOG_DEBUG,"mainloop_startqw started\n");
+#ifdef WIN32
+    if(!*quakepath) return(0);
+
+    sprintf(quakeoptions+strlen(quakeoptions)," +connect 127.1:%i",proxy_port);
+
+    if(gamespy)
+    {
+        //strcat(quakeoptions," +exec GameSpyC.cfg");
+        printf("Cheapo started from GameSpy.\n");
+    }
+
+    printf("Starting QWCL (%s)\n",quakeoptions);
+#ifdef PROTECT
+    ret=startqwcl(quakepath,quakeoptions);
+#else
+    ret=startqwcl_nochecks(quakepath,quakeoptions);
+#endif
+    if(ret)
+    {
+        if(ret==1) printf("error: Could not find executable.\n");
+        else if(ret==2) printf("error: The executable is modified.\n");
+        else printf("error: Could not start executable\n",ret);
+        cleanup(0);
+        return(0);
+    }
+#ifdef PROTECT
+    if(quakemodified)
+    {
+        printf("warning: Executable is modified (f_modified will report this).\n");
+    }
+#endif
+
+    if(!noautoexit)
+    {
+        printf("QWCL has been closed: ");
+        cleanup(0);
+    }
+#endif
+    return(0);
+}
+
+void setpriorityx(int fast)
+{
+#ifdef WIN32
+    HANDLE thread; // mainloop thread
+    HANDLE process; // mainloop thread
+    process=GetCurrentProcess();
+    thread=GetCurrentThread();
+
+    if(fast)
+    {
+        SetPriorityClass(process,HIGH_PRIORITY_CLASS);
+        if(!priority)
+        {
+            SetThreadPriority(thread,THREAD_PRIORITY_HIGHEST);
+        }
+        else
+        {
+            SetThreadPriority(thread,priority-13);
+        }
+    }
+    else
+    {
+        SetPriorityClass(process,NORMAL_PRIORITY_CLASS);
+        SetThreadPriority(thread,THREAD_PRIORITY_BELOW_NORMAL);
+    }
+#endif
+}
+
+int mainloop_low(int doonce)
+{ // low priority
+    int     i;
+    DWORD   time;
+    User   *u;
+
+    logx(LOG_DEBUG,"mainloop_low started\n");
+
+#ifdef WIN32
+    SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_LOWEST);
+#endif
+
+    while(!stopping)
+    {
+        // handle autotimeout
+        time=timems();
+        if(autotimeout && time>timeout) stopping=1;
+
+        for(i=0;i<usernum;i++)
+        {
+            u=user+i;
+            if(u->state==STATE_NAMELOOKUP)
+            {
+                logx(LOG_DEBUG,"main lookup\n");
+                c_setaddress(&u->servermainaddr,u->connecthere);
+                u->state=STATE_TRYING;
+            }
+        }
+        if(doonce) return(0);
+
+        Sleep(250); // 0.25 sec
+    }
+    return(0);
+}
+
+int mainloop_high(int value)
+{ // high priority
+#if defined(__APPLE__) || defined(MACOSX)
+    User        *u = NULL;
+#else
+    User        *u;
+#endif /* __APPLE__ || MACOSX */
+    int          bytes;
+    SOCKADDR_IN  from;
+    unsigned int fromsize=sizeof(from);
+    int          error,i;
+#if !defined(__APPLE__) && !defined(MACOSX)
+    int          cliopen=0;
+#endif /* !__APPLE__ && !MACOSX */
+    fd_set       readset;
+    struct timeval delay;
+    DWORD        time;
+    static uchar  buffer[BUFSIZE];
+    int          hh;
+    // stats
+#ifdef MAINPROFILE
+    DWORD        time1,time2;
+    DWORD        nextprinttime=0;
+    DWORD        timesum=0,timecnt=0;
+    DWORD        timemax=0;
+#endif
+
+    logx(LOG_DEBUG,"mainloop_high started\n");
+
+    setpriorityx(2);
+
+    timeout=timems()+timeouttime;
+
+    while(!stopping)
+    {
+        hh=0;
+        FD_ZERO(&readset);
+        FD_SET(consocket,&readset);
+#ifndef WIN32
+        hh=consocket+1;
+#endif
+        for(i=0;i<usernum;i++)
+        {
+            if(user[i].state!=STATE_HERE)
+            {
+                FD_SET(user[i].toserver,&readset);
+#ifndef WIN32
+                if(user[i].toserver>=hh) hh=user[i].toserver+1;
+#endif
+            }
+        }
+
+        delay.tv_sec=1;
+        delay.tv_usec=0;
+
+        if(prioritytoggle) setpriorityx(0);
+        error=select(hh,&readset,NULL,NULL,&delay);
+        if(prioritytoggle) setpriorityx(1);
+//        if(loglevel>=LOG_PACKETS) logx(LOG_PACKETS,"\n\n\nSelect returns with %i\n",error);
+        if(error==SOCKET_ERROR)
+        {
+            ws_showerror(u,"select");
+        }
+
+        time=timems();  // milliseconds
+#ifdef MAINPROFILE
+        time1=timeus();
+#endif
+        if(FD_ISSET(consocket,&readset))
+        {
+            int i;
+            timeout=time+timeouttime;
+            // contact to server port
+            bytes=recvfrom(consocket,buffer,sizeof(buffer)-1,0,
+                           (LPSOCKADDR)&from,&fromsize);
+            buffer[bytes]=0;
+
+            if(bytes==SOCKET_ERROR)
+            {
+                ws_showerror(u,"connect");
+            }
+            else
+            {
+                // check if it is from a connected client
+                for(i=0;i<usernum;i++)
+                {
+                    globalu=u=user+i;
+                    u->time=time;
+
+                    if(c_sameaddress(&u->toclientaddr,&from))
+                    {
+                        if(u->time>u->time_bytestosec)
+                        {
+                            u->bytestoserversec=u->bytestoserversec1;
+                            u->bytestoclientsec=u->bytestoclientsec1;
+                            u->bytestoserversec1=0;
+                            u->bytestoclientsec1=0;
+                            if(u->bytestoserversec>u->bytestoserversecmax) u->bytestoserversecmax=u->bytestoserversec;
+                            if(u->bytestoclientsec>u->bytestoclientsecmax) u->bytestoclientsecmax=u->bytestoclientsec;
+                            u->time_bytestosec=u->time+1000;
+                        }
+
+                        u->time_lastclient=time;
+
+                        if(u->quiet>0) u->quiet--;
+                        else u->quiet=0;
+
+                        if(!memcmp(buffer,"\xff\xff\xff\xffgetcha",10))
+                        {
+                            u->state=STATE_DROPPING;
+                            break;
+                        }
+
+                        if(u->state==STATE_DISCONNECTING)
+                        {
+                            q_command(u,"!sdrop");
+                        }
+                        else if(u->state==STATE_TRYING)
+                        {
+                            if(u->time>u->time_nexttry) q_command(u,"!try");
+                        }
+                        else if(u->state==STATE_INIT)
+                        {
+                            q_command(u,"!init");
+                        }
+                        else if(u->state==STATE_HERE)
+                        {
+                            if(u->doconnect)
+                            {
+                                        if(u->doconnect>1) u->doconnect--;
+                                        else
+                                        {
+                                            q_command(u,"!connect");
+                                            u->doconnect=0;
+                                        }
+                            }
+                        }
+                        else
+                        {
+                            if(u->donew)
+                            {
+                                if(u->donew>1) u->donew--;
+                                else
+                                {
+                                    q_command(u,"!sendnew");
+                                    //u->donew=25; // send again in 25 cycles if not cancelled by fullserverinfo
+                                    u->donew=0;
+                                }
+                            }
+                        }
+
+                        if(loglevel>=LOG_PACKETS) c_hexdump("\n\nClient->Proxy",buffer,bytes,u);
+                        if(u->state>=STATE_CONNECTED)
+                        {
+                            q_client(u,buffer,bytes);
+                        }
+                        else
+                        {
+                            q_proxy(u,buffer,bytes);
+                        }
+
+                        break;
+                    }
+                }
+
+                if(i==usernum)
+                { // user not found
+                    if(buffer[0]==0xff && buffer[1]==0xff && buffer[2]==0xff && buffer[3]==0xff)
+                    {
+                        if(loglevel>=LOG_PACKETS) c_hexdump("Someone->ProxyConnect",buffer,bytes,NULL);
+                        q_connect(buffer,bytes,&from);
+                    }
+                    else
+                    { // unrecognized packet from someone
+                        if(hpymode)
+                        {
+                            int besti,bestv,value;
+                            bestv=0x10000000;
+                            besti=-1;
+                            // find best match
+                            for(i=0;i<usernum;i++)
+                            {
+                                value=packetmatch(user+i,buffer);
+                                if(value<bestv)
+                                {
+                                    bestv=value;
+                                    besti=i;
+                                }
+                            }
+                            if(besti>=0)
+                            {
+                                u=user+besti;
+
+                                memcpy(&u->toclientaddr,&from,sizeof(SOCKADDR_IN));
+                                //q_print(u,"proxy: dynamic ip change detected\n");
+                                q_printf(u,"proxy: ip change for %s\n",u->name);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        for(i=0;i<usernum;i++)
+        {
+            globalu=u=user+i;
+
+            u->time=time;
+
+            if(FD_ISSET(u->toserver,&readset))
+            {
+                timeout=time+timeouttime;
+                bytes=recvfrom(u->toserver,buffer,sizeof(buffer)-1,0,(LPSOCKADDR)&from,&fromsize);
+                u->time_lastserver=time;
+                buffer[bytes]=0;
+                if(bytes==SOCKET_ERROR)
+                {
+                    ws_showerror(u,"receiveserver");
+                }
+                else
+                {
+                    if(loglevel>=LOG_PACKETS) c_hexdump("\n\nServer->Proxy",buffer,bytes,u);
+                    u->toserveraddr=from;
+
+                    q_server(u,buffer,bytes);
+                }
+            }
+        }
+
+        // process dead users (also timeouts)
+        // process dropping
+        for(i=0;i<usernum;i++)
+        {
+#if !defined(__APPLE__) && !defined(MACOSX)
+            static DWORD lasttime;
+#endif /* !__APPLE__ && !MACOSX */
+            globalu=u=user+i;
+
+            if(u->errors>MAXERRORS)
+            { // too many errors (handle as timeout)
+                q_command(u,"!errors");
+                i=0; // user order has changed
+            }
+            if(u->time_lastclient>0 && u->time>u->time_lastclient+TIMEOUT)
+            { // client timeout
+                q_command(u,"!timeout");
+                i=0; // user order has changed
+            }
+            if(u->state==STATE_DROPPING)
+            {
+                q_command(u,"!kill");
+                i=0; // user order has changed
+            }
+        }
+
+#ifdef MAINPROFILE
+        time2=timeus();
+        time2-=time1;
+        if(time2>timemax) timemax=time2;
+        timesum+=time2;
+        timecnt++;
+
+        if(time>nextprinttime)
+        {
+            nextprinttime=time+1000;
+
+            if(timecnt>0) timesum/=timecnt;
+            printf("Process time: avg %2ius max %2ius\n",timesum,timemax);
+
+            timemax=0;
+            timecnt=0;
+            timesum=0;
+        }
+#endif
+
+        if(autotimeout && time>timeout) stopping=1;
+    }
+    return(0);
+}
+
+int startup(int port)
+{
+    log_open();
+    logx(LOG_DEBUG,"startup %i\n",port);
+    ws_init();
+    if(ws_openserver(port)) return(1);
+    return(0);
+}
+
+void cleanup(int type)
+{
+    static int cleanupdone=0;
+    if(cleanupdone) return;
+    cleanupdone=1;
+    stopping=1;
+    log_flush();
+    printf("Terminating...\n");
+    Sleep(500);
+    ws_deinit();
+    log_close();
+    if(lograwpackets)
+    {
+       fclose(lograwc);
+       fclose(lograws);
+    }
+#ifdef WIN32
+    ExitProcess(0);
+#endif
+}
+
+void expires_startup(void)
+{
+#ifdef EXPIRETIME
+    int tim;
+
+//    printf("\nTHIS IS A SPECIAL NONPUBLIC TEST VERSION (Sir-BETA).\n");
+    printf("\nTHIS IS A SPECIAL NONPUBLIC TEST VERSION, DO NOT DISTRIBUTE.\n");
+
+    time(&tim);
+    if(tim>EXPIRETIME+0)
+    {
+        printf("This version has expired. A newer version should be available.\n");
+        exit(3);
+    }
+
+#endif
+}
+
+void setversions(void)
+{
+    int    versionnamesrc[16];
+    int    versionsrc[16];
+    int i,j;
+    i=0;
+    j=120+0x40800000;
+    versionnamesrc[i++]=(j++)+'C';
+    versionnamesrc[i++]=(j++)+'h';
+    versionnamesrc[i++]=(j++)+'e';
+    versionnamesrc[i++]=(j++)+'a';
+    versionnamesrc[i++]=(j++)+'p';
+    versionnamesrc[i++]=(j++)+'o';
+    versionnamesrc[i++]=0;
+    i=0;
+    j=120+0x40800000;
+    versionsrc[i++]=(j++)+'2';
+    versionsrc[i++]=(j++)+'.';
+    versionsrc[i++]=(j++)+'6';
+    versionsrc[i++]=(j++)+'1';
+    versionsrc[i++]=0;
+    {
+        int i;
+        char *d;
+
+        d=version;
+        for(i=0;i<16;i++)
+        {
+            if(!versionsrc[i]) break;
+            *d++=(versionsrc[i]&255)-(120+i);
+        }
+        *d=0;
+
+        d=versionname;
+        for(i=0;i<16;i++)
+        {
+            if(!versionnamesrc[i]) break;
+            *d++=(versionnamesrc[i]&255)-(120+i);
+        }
+        *d=0;
+    }
+}
+
+void convertspyconfig(char *fin,char *fout)
+{
+    FILE *f1,*f2;
+    char buf[256];
+
+    f1=fopen(fin,"rb");
+    if(!f1) return;
+    f2=fopen(fout,"wb");
+    if(!f2)
+    {
+        fclose(f1);
+        return;
+    }
+
+    fprintf(f2,"wait\n");
+    while(!feof(f1))
+    {
+        *buf=0;
+        fgets(buf,255,f1);
+        if(!memcmp(buf,"connect ",8))
+        {
+            putc('s',f2);
+            memcpy(buf,"ay .con ",8);
+        }
+        fputs(buf,f2);
+    }
+
+    fclose(f2);
+    fclose(f1);
+}
+
+int main2(int argc,uchar *argv[])
+{
+    int port;
+    int error=0,a,showinternal=0;
+    int mset=0,aset=0,csset=0,nset=0,pwset=0;
+    uchar *p;
+
+    proxy_serverip.sin_family=AF_INET;
+    proxy_serverip.sin_addr.s_addr=INADDR_ANY;
+    proxy_serverip.sin_port=27500;
+
+    proxy_clientip.sin_family=AF_INET;
+    proxy_clientip.sin_addr.s_addr=INADDR_ANY;
+    proxy_clientip.sin_port=0;
+
+    port=27500;
+    loglevel=-1;
+
+    setversions();
+    for(a=1;a<argc;a++)
+    {
+        if(*(argv[a])=='/' || *(argv[a])=='-') switch(*(argv[a]+1))
+        {
+            case 'h' :
+            case '?' : if(argv[a][2]=='p') error=2;
+                       else error=1;
+                       break;
+
+            case '!' : showinternal=1; break;
+
+            case 'p' : p=argv[++a];
+                       if(!p) break;
+                       port=atoi(p);
+                       break;
+
+            case 'm' : p=argv[++a];
+                       if(!p) break;
+                       maxusernum=atoi(p);
+                       if(maxusernum>MAXPLAYERS) maxusernum=MAXPLAYERS;
+                       if(maxusernum<1) maxusernum=1;
+                       mset=1;
+                       break;
+
+            case 'a' : p=argv[++a];
+                       if(!p || !*p) break;
+                       strncpy(proxy_allowip,p,MAXSTR-1);
+                       aset=1;
+                       break;
+
+            case 'd' : p=argv[++a];
+                       if(!p || !*p) break;
+                       if(proxy_denyipnum<16)
+                       {
+                            strncpy(proxy_denyip[proxy_denyipnum],p,MAXSTR-1);
+                            proxy_denyipnum++;
+                       }
+                       aset=1;
+                       break;
+
+            case 'c' : p=argv[++a];
+                       if(!p || !*p) break;
+                       c_setaddress(&proxy_clientip,p);
+                       csset=1;
+                       break;
+
+            case 'o' : p=argv[++a];
+                       if(!p || !*p) break;
+                       strcpy(proxy_name,p);
+                       nset=1;
+                       break;
+
+            case 'w' : p=argv[++a];
+                       if(!p || !*p) break;
+                       strcpy(proxy_password,p);
+                       pwset=1;
+                       break;
+
+            case 's' : p=argv[++a];
+                       if(!p || !*p) break;
+                       c_setaddress(&proxy_serverip,p);
+                       csset=1;
+                       break;
+
+            case 'q' : loglevel=-1;
+                       break;
+
+            case 'x' : noautoexit=1;
+                       break;
+
+            case 'n':  autotimeout=0;
+                       break;
+
+    //        case 'z':  newsocketmode=0;
+    //                   break;
+
+            case 'y':  hpymode=1;
+                       break;
+
+            case 'j':  prioritytoggle=1;
+                       priority=13;
+            case 'i' : p=argv[++a];
+                       if(!p) break;
+                       priority=atoi(p);
+                       if(priority<11 || priority>15)
+                       {
+                            printf("WARNING: -i parameter should be in range 11..15\n");
+                       }
+                       break;
+
+    //    case 'i':  litemode=FPD_TIMERS|FPD_PERCENTE|FPD_SKIN;
+    //           break;
+
+            case 'l' : p=argv[++a];
+                       if(!p) break;
+                       loglevel=atoi(p);
+    #ifdef PROMODE
+                       if(loglevel==-1)
+                       {
+                           loglevel=2;
+                           lograwpackets=1;
+                           lograwc=fopen("client.raw","wb");
+                           lograws=fopen("server.raw","wb");
+                           printf("Logging raw packets to CLIENT.RAW, SERVER.RAW\n");
+                           if(!lograwc) exit(1);
+                           if(!lograws) exit(1);
+                       }
+    #endif
+                       break;
+            default :
+                    error=1;
+                    break;
+        }
+        else
+        {
+#ifdef WIN32
+            char *p;
+            strcpy(quakepath,argv[a]);
+            strcpy(quakeoptions,strstr(GetCommandLine(), argv[a]));
+            if(p=strstr(quakeoptions,"+exec GameSpy.cfg"))
+            {
+                *p=0;
+                gamespy=1;
+                convertspyconfig("id1\\GameSpy.cfg","id1\\GameSpyC.cfg");
+            }
+#else
+            error=1;
+#endif
+            break;
+        }
+    }
+
+    if(litemode)
+    {
+        strcat(version," lite");
+    }
+
+    proxy_serverip.sin_port=htons((unsigned short)port);
+
+#ifdef PROMODE
+    versiontype=" open-pro";
+#else
+    versiontype=" open";
+#endif
+
+    printf("\n"
+           "%s V%s%s - Freeware QuakeWorld Proxy - (C) 1998-1999 Sami Tammilehto.\n"
+           "Unsupported software. Absolutely no warranty. Use at your own risk.\n"
+           ,versionname,version,versiontype);
+
+    expires_startup();
+    if(error)
+    {
+        printf("\n"
+#ifdef WIN32
+               "usage: cheapo [options...] [qwcl.exe path] [qwcl options]\n"
+#else
+               "usage: cheapo [options...]\n"
+#endif
+               "options:\n"
+               "-p <port>   port to listen to (default 27500)\n"
+               "-m <num>    set maxclients (1-16)\n"
+               "-o <name>   server host name\n"
+               "-w <pass>   password required to connect\n"
+               "-s <ip>     server interface (which ip proxy listens)\n"
+               "-c <ip>     client interface (which ip proxy uses to connect)\n"
+               "-a <ip>     only accept connections with ip addresses starting like this\n"
+               "            (so -a 192.168 would accept connections only from 192.168.x.x)\n"
+               "-d <ip>     deny connections from ip addresses (like -a). Multiple allowed.\n"
+#ifdef WIN32
+               "-i <pri>    set windows priority (11-15)\n"
+               "-j          drop priority for select\n"
+#endif
+               "-l <level>  log to file proxy.log, level is 0..2\n"
+               "-q          quiet; no screen output after startup\n"
+               "            (now default, use '-l 0' to enable connect/disconnect reports)\n"
+               "-n          no timeout (otherwise timeouts after 4 hours of inactivity)\n"
+               /*
+               "-z          use older TCP/IP routines (like in V2.4) instead of the new ones\n"
+               "            (if you get problems or more lag with this new version)\n"
+               */
+               "-x          no proxy exit when QW exits (and has been started by the Proxy)\n"
+               "-y          allow client IP changes (so that a reconnect using dynamic\n"
+               "            IP numbers doesn't disconnect you from the proxy)\n"
+               "\n"
+               "You can automatically start QuakeWorld by specifying the QWCL path and options\n"
+               "like 'cheapo c:\\quake\\qwcl.exe'. This is required for f_modified to work.\n"
+               "When you quit QWCL in this mode, the proxy also exits (unless -x is used).\n"
+               "\n");
+
+        return(0);
+    }
+
+    printf("\n");
+
+    if(csset)
+    {
+        c_textaddress(tmpbuf,&proxy_serverip);
+        if(*tmpbuf!='-') printf("Server Interface %s. ",tmpbuf);
+        c_textaddress(tmpbuf,&proxy_clientip);
+        if(*tmpbuf!='-') printf("Client Interface %s. ",tmpbuf);
+        printf("\n");
+    }
+    if(nset) printf("Proxy hostname '%s'.\n",proxy_name);
+    if(hpymode)
+    {
+        if(maxusernum>1) printf("Dynamic client IP changes allowed.\n");
+        else printf("Dynamic client IP changes allowed.\n");
+    }
+    if(pwset) printf("Proxy password set.\n");
+    if(mset) printf("Max %i users allowed to connect.\n",maxusernum);
+    if(aset) printf("IP checking (-a/-d) enabled.\n");
+    if(priority) printf("Using adjusted windows priority.\n");
+    if(autotimeout) printf("Proxy exits after 4 hours of inactivity (-n disables autoexit)\n");
+#ifndef PACKETLOG
+    if(loglevel>2)
+    {
+        loglevel=2;
+        printf("NOTE: maximum loglevel is 2\n");
+    }
+#endif
+    if(loglevel>0) printf("Logging to \'proxy.log\' (loglevel %i).\n",loglevel);
+
+#ifdef PROMODE
+    printf("compiled with: PROMODE\n");
+#endif
+#ifdef COMPRESSION
+    printf("compiled with: COMPRESSION\n");
+#endif
+#ifdef PACKETLOG
+    printf("compiled with: PACKETLOG\n");
+#endif
+#ifdef IDENT
+    printf("compiled with: IDENT\n");
+#endif
+#ifdef SPEED
+    printf("compiled with: .SHOW\n");
+#endif
+
+#ifdef EXPIRETIME
+    printf(betanotes);
+#endif
+
+    if(startup(port))
+    {
+        printf("Could not bind to port %i.\n\n",port);
+        return(1);
+    }
+
+    printf("Listening to port %i (press CTRL-C to stop).\n",port);
+    proxy_port=port;
+    fflush(stdout);
+
+    originalloglevel=loglevel;
+
+    timeout=timems()+timeouttime;
+
+#ifdef WIN32
+    {
+        HANDLE thread; // mainloop thread
+        DWORD threadid;
+
+        SetConsoleCtrlHandler((PHANDLER_ROUTINE)cleanup,TRUE);
+
+        if(newsocketmode)
+        {
+/*
+            thread=CreateThread(NULL,        // security settings
+                                0,           // stack size
+                                (LPTHREAD_START_ROUTINE)mainloop_high_newbug, // routine
+                                NULL,        // parameter (not used)
+                                0,           // creationflags
+                                &threadid);  // thread id
+*/
+        }
+        else
+        {
+            thread=CreateThread(NULL,        // security settings
+                                0,           // stack size
+                                (LPTHREAD_START_ROUTINE)mainloop_high, // routine
+                                NULL,        // parameter (not used)
+                                0,           // creationflags
+                                &threadid);  // thread id
+        }
+//        SetThreadPriority(thread,THREAD_PRIORITY_HIGHEST);
+
+        if(*quakepath)
+        {
+            thread=CreateThread(NULL,        // security settings
+                                0,           // stack size
+                                (LPTHREAD_START_ROUTINE)mainloop_startqw, // routine
+                                NULL,        // parameter (not used)
+                                0,           // creationflags
+                                &threadid);  // thread id
+        }
+
+        mainloop_low(0);
+    }
+#else
+    mainloop_high(0);
+#endif
+
+    Sleep(1000);
+    cleanup(0);
+
+    return(0);
+}
+
+/******************************************/
+
+void statecrc(User *u,char *result)
+{
+#ifdef PROTECT
+    // REMOVED: 3 letter version/connection checksum
+#else
+    strcpy(result,"<open>");
+#endif
+}
+
+void codecrc(char *salt,char *result)
+{
+#ifdef PROTECT
+    // REMOVED: code/fmodified checksum
+#else
+    strcpy(result,"<open>");
+#endif
+}
+
+#if defined(__APPLE__) || defined(MACOSX)
+
+int main(int argc,char **argv)
+
+#else
+
+int main(int argc,uchar *argv[])
+
+#endif /* __APPLE__ || MACOSX */
+
+{
+#ifdef PROTECT
+    // REMOVED: exe integrity check
+#endif
+
+#if defined(__APPLE__) || defined(MACOSX)
+
+    return(main2(argc,(uchar **) argv));
+
+#else
+
+    return(main2(argc,argv));
+
+#endif /* __APPLE__ || MACOSX */
+
+}
+
diff --git a/misc.c b/misc.c
new file mode 100755 (executable)
index 0000000..72ac632
--- /dev/null
+++ b/misc.c
@@ -0,0 +1,2076 @@
+// Cheapo - a QuakeWorld Proxy
+// Copyright (C) 1998 Sami Tammilehto
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+#include "proxy.h"
+
+//-------------------------------------------------------
+void rankchange(User *u,int pl,int dooverride)
+{
+    int newcol;
+    int frags,ratio,time;
+    int best,besti,bestratio,besttime;
+    int i;
+
+    if(pl<1 || pl>32) return;
+
+    // find leader
+    best=0;
+    besti=0;
+    for(i=1;i<=32;i++)
+    {
+        if(u->playerfrags[i]>best)
+        {
+            besti=i;
+            best=u->playerfrags[i];
+        }
+    }
+    u->playerfragsbest=best;
+
+    if(!besti)
+    {
+        besttime=0;
+        bestratio=1;
+    }
+    else
+    {
+        i=u->playertime[besti]+u->time;
+        if(i<1000) i=1000;
+        besttime=i;
+        bestratio=1000000*best/i;
+    }
+
+    frags=u->playerfrags[pl];
+
+    i=u->playertime[pl]+u->time;
+    if(i<1000) i=1000;
+    time=i;
+    ratio=1000000*frags/i;
+
+//    printf("pl %2i: time %6i frags %3i ratio %6i  (best %3i ratio %6i time %6i)\n",
+//        pl,i,frags,ratio,best,bestratio,besttime);
+
+    if(frags<=0 || time<60000)    newcol=0;  // <=0 frags or <=1 min (WHITE)
+    else if(frags>=best || ratio>=bestratio) newcol=4 ; // leader(s)      RED
+    else if(ratio>=bestratio*3/4) newcol=12; // ratio over 75% YELLOW
+    else if(ratio>=bestratio*2/4) newcol=13; // ratio over 50% BLUE
+    else if(ratio>=bestratio*1/4) newcol=3 ; // ratio over 25% GREEN
+    else                          newcol=0 ; // the rest       WHITE
+
+    if(newcol!=u->playerrcolor[pl])
+    {
+        u->playerrcolor[pl]=newcol;
+        if(dooverride) skinoverride(u,pl);
+    }
+}
+
+void skinoverride(User *u,int i)  // also coloroverride
+{
+    char *skin = NULL;
+    char *color = NULL,*color2 = NULL;
+    char  colortext[4];
+    char  colortext2[4];
+    //char buf2[32],*p2;
+    //char buf[80],*p;
+
+    skin=NULL;
+
+/*
+    // check and cut long skin names
+    if(strlen(u->playerskin[i])>19)
+    {
+        u->playerskin[i][19]=0;
+        skin=u->playerskin[i];
+        q_printf(u,"DEBUG: %s longskin\n",u->playername[i]);
+    }
+*/
+
+    for(;;)
+    {
+        if(u->fortress) break;
+        if(u->fpd&FPD_SKIN) break;
+        if(!u->skinoverride) break;
+        if(!u->playeractive[i]) break;
+        if(u->time_reskinall) break;
+
+        skin=(SAMETEAM(i))?u->skinname:u->skinname2;
+        if(*skin=='*' || u->skinoverride==-1) skin=u->playerskin[i];
+
+        /*
+        p=u->playerskin[i];
+        p2=buf2;
+        while(*p)
+        {
+            if(*p=='.') break;
+            *p2++=*p++;
+            if(p2>buf2+15) break;
+        }
+        *p2=0;
+        */
+
+        if(u->skinoverride==1)
+        {
+//            q_printf(u,"DEBUG: %15s %s\n",u->playername[i],skin);
+        }
+
+        break;
+    }
+
+    if(skin)
+    {
+        setinfo(u,i,"skin",skin);
+    }
+
+    //-------- color ------------
+
+    color=NULL;
+
+    for(;;)
+    {
+        if(u->fortress) break;
+        if(u->fpd&FPD_COLOR) break;
+        if(!u->coloroverride) break;
+        if(!u->playeractive[i]) break;
+        if(u->time_reskinall) break;
+
+        {
+            int c;
+            if(SAMETEAM(i)) c=u->color;
+            else            c=u->color2;
+
+            if(c==-3)
+            {
+                color=color2=colortext;
+                if(u->playerrcolor[i]<0) *color='-';
+                else sprintf(colortext,"%i",u->playerrcolor[i]);
+            }
+            else if(c==-2)
+            {
+                color=color2=colortext;
+                if(u->playerxcolor[i]<0) *color='-';
+                else *color='+';
+            }
+            else if(c<0)
+            {
+                color=color2=colortext;
+                *color='-';
+            }
+            else if(c<256)
+            {
+                color=color2=colortext;
+                sprintf(color,"%i",c&15);
+            }
+            else
+            {
+                color=colortext;
+                color2=colortext2;
+                sprintf(colortext,"%i",(c>>4)&15);
+                sprintf(colortext2,"%i",c&15);
+            }
+        }
+
+        if(u->coloroverride==-1 || *color=='-')
+        {
+            color=colortext;
+            color2=colortext2;
+            sprintf(colortext,"%i",u->playertcolor[i]);
+            sprintf(colortext2,"%i",u->playerbcolor[i]);
+        }
+        else if(*color=='+')
+        {
+            color=colortext;
+            color2=colortext2;
+            sprintf(colortext,"%i",(u->playerxcolor[i]>>4)&15);
+            sprintf(colortext2,"%i",(u->playerxcolor[i])&15);
+        }
+
+        break;
+    }
+
+    if(color)
+    {
+        setinfo(u,i,"topcolor",color);
+        setinfo(u,i,"bottomcolor",color2);
+    }
+}
+
+//-------------------------------------------------------
+
+uchar *findnear(User *u,int x,int y,int z,int *ind)
+{
+    int i,ibest=-1;
+    int d,dbest=9999999;
+    for(i=0;i<u->locations;i++)
+    {
+        d=abs(x-u->loc[i].x)+
+          abs(y-u->loc[i].y)+
+          abs(z-u->loc[i].z);
+        if(d<dbest)
+        {
+            dbest=d;
+            ibest=i;
+        }
+    }
+    if(ind) *ind=ibest;
+    if(ibest<0) return("someplace");
+    else return(u->loc[ibest].name);
+}
+
+char *playerlocdirection(User *u,int pl)
+{
+    char *name1,*name2;
+    static char buf[512];
+    int i;
+
+    i=u->playerlastloc[pl];
+    if(i<0 || i>=u->locations) name1="";
+    else name1=u->loc[i].name;
+
+    i=u->playerloc[pl];
+    if(i<0 || i>=u->locations) name2="";
+    else name2=u->loc[i].name;
+
+    if(strcmp(name1,name2))
+    {
+        strcpy(buf,name1);
+        strcat(buf,"-\x8d");
+        strcat(buf,name2);
+        return(buf);
+    }
+    else return(NULL);
+}
+
+// pointing globals
+static float px,py,pz; // player
+static float dx,dy,dz; // lookdir
+static float best;
+static int   showdebug;
+
+int pointcheck(User *u,int x,int y,int z,char *name)
+{ // returns 1 if this object should replace last
+    float jx,jy,jz;
+    float kx,ky,kz;
+    float zdist; // depth distance
+    float pdist; // perpendicular distance
+    float dist;  // dist used for evaluation
+
+    jx=(float)x-px;
+    jy=(float)y-py;
+    jz=(float)z-pz;
+    zdist=(jx*dx+jy*dy+jz*dz);
+
+    if(zdist<100) return(0);
+
+    kx=dx*zdist-jx;
+    ky=dy*zdist-jy;
+    kz=dz*zdist-jz;
+    pdist=(float)sqrt(kx*kx+ky*ky+kz*kz);
+
+    if(pdist>2000) return(0);
+
+    if(pdist>zdist) return(0); // aim over 45 degrees off
+
+    if(zdist<3000)
+    {
+        dist=pdist*(zdist*0.0002f+0.3f);
+    }
+    else dist=pdist;
+
+    if(showdebug)
+    {
+        char buf[64];
+        sprintf(buf,"- z:%5.0f p:%5.0f d:%5.0f %s\n",zdist,pdist,dist,name);
+        q_print(u,buf);
+    }
+
+    if(dist<best)
+    {
+        best=dist;
+        return(1);
+    }
+    return(0);
+}
+
+uchar *findpoint(User *u,int x,int y,int z,int ax,int ay,int az,int targetmask,int flags,int *entnum)
+{
+    static char buf[256];
+    uchar *name;
+    int    namex = 0,namey = 0,namez = 0;
+    int    gotplayer=0; // 1=teammate
+    int    gotplayernum=0;
+    int    i;
+
+    if(entnum) *entnum=0;
+
+    // parameters for pointcheck
+    dx=(float)(cos((double)ay/32768.0*3.141592)* cos((double)ax/32768.0*3.141592));
+    dy=(float)(sin((double)ay/32768.0*3.141592)* cos((double)ax/32768.0*3.141592));
+    dz=(float)(                                 -sin((double)ax/32768.0*3.141592));
+    px=(float)x;
+    py=(float)y;
+    pz=(float)z;
+    best=99999999.0f;
+    showdebug=(targetmask==-1);
+
+    // result clear
+    *buf=0;
+    name=NULL;
+
+    if(flags&POINT_SELF)
+    {
+        gotplayernum=u->entity;
+        gotplayer=1;
+        name="self";
+        namex=u->playerpos[gotplayernum][0];
+        namey=u->playerpos[gotplayernum][1];
+        namez=u->playerpos[gotplayernum][2];
+    }
+    else
+    {
+        if(targetmask&(CAMP_PLAYER|CAMP_ENEMY))
+        { // players
+            for(i=1;i<=32;i++) if(u->playeractive[i] && u->playernear[i] && i!=u->entity)
+            {
+                if(pointcheck(u,u->playerpos[i][0],u->playerpos[i][1],u->playerpos[i][2],"player"))
+                {
+                    int lo,hi;
+
+                    *buf=0;
+
+                    namex=u->playerpos[i][0];
+                    namey=u->playerpos[i][1];
+                    namez=u->playerpos[i][2];
+
+                    lo=u->playerglow[i]&0x0f;
+                    hi=u->playerglow[i]&0xf0;
+
+                    if(u->playerglow[i]&0x100)
+                    {
+                        if(hi&0x40)
+                        {
+                            strcat(buf,"quaded ");
+                        }
+                        if(hi&0x80)
+                        {
+                            strcat(buf,"penta ");
+                        }
+
+                        strcat(buf,"eyes");
+                        gotplayer=3;
+                    }
+                    else
+                    {
+                        if(hi&0x10) strcat(buf,"red FLAG on ");
+                        else if(hi&0x20) strcat(buf,"blue FLAG on ");
+
+                        if(hi&0x40)
+                        {
+                            strcat(buf,"quaded ");
+                        }
+                        if(hi&0x80)
+                        {
+                            strcat(buf,"penta ");
+                        }
+
+                        if(SAMETEAM(i))
+                        {
+                            strcat(buf,u->playername[i]);
+                            gotplayer=1;
+                        }
+                        else
+                        {
+                            strcat(buf,"enemy");
+                            if((flags&POINT_ADDSKIN) && u->fortress)
+                            {
+                                char *s;
+                                s=u->playerskin[i];
+                                if(s[0]=='t' && s[1]=='f' && s[2]=='_')
+                                {
+                                    s+=3;
+                                    if(!strcmp(s,"demo.pcx")) strcat(buf," demoman");
+                                    if(!strcmp(s,"eng.pcx")) strcat(buf," engineer");
+                                    if(!strcmp(s,"hwguy.pcx")) strcat(buf," hw-guy");
+                                    if(!strcmp(s,"medic.pcx")) strcat(buf," medic");
+                                    if(!strcmp(s,"pyro.pcx")) strcat(buf," pyro");
+                                    if(!strcmp(s,"scout.pcx")) strcat(buf," scout");
+                                    if(!strcmp(s,"snipe.pcx")) strcat(buf," sniper");
+                                    if(!strcmp(s,"sold.pcx")) strcat(buf," soldier");
+                                    if(!strcmp(s,"spy.pcx")) strcat(buf," spy");
+                                }
+                            }
+                            gotplayer=2;
+                        }
+                    }
+
+                    if(entnum) *entnum=i;
+
+                    name=buf;
+                    gotplayernum=i;
+                }
+            }
+        }
+
+        if(gotplayer==1 && !(targetmask&CAMP_PLAYER))
+        { // got own player, and only CAMP_ENEMY set
+            gotplayer=0;
+            name=NULL;
+            best=99999999.0f;
+        }
+
+        if(targetmask&(~(CAMP_PLAYER|CAMP_ENEMY)))
+        {
+            for(i=0;i<MAXENTITIES;i++) if(u->entityvisible[i] && (u->entitymask[i]&targetmask))
+            {
+                if(pointcheck(u,u->entitypos[i][0],u->entitypos[i][1],u->entitypos[i][2],u->entityname[i]))
+                {
+                    gotplayer=0;
+                    namex=u->entitypos[i][0];
+                    namey=u->entitypos[i][1];
+                    namez=u->entitypos[i][2];
+                    name=u->entityname[i];
+                    if(entnum) *entnum=i;
+                }
+            }
+        }
+    }
+
+    if(flags&POINT_TEAMMATENUMBER)
+    {
+        sprintf(buf,"%i",gotplayernum);
+        return(buf);
+    }
+    else if(name==NULL)
+    {
+        // do nothing
+        return("."); // nothing found
+    }
+    else
+    {
+        // add location
+        if(flags&POINT_ONLYNAME)
+        {
+            strcpy(buf,name);
+        }
+        else if(flags&POINT_ONLYPOS)
+        {
+            strcpy(buf,findnear(u,namex,namey,namez,NULL));
+        }
+        else
+        {
+            strcpy(buf,name);
+            strcat(buf," at ");
+            if(gotplayer && (flags&POINT_DIRECTION))
+            {
+                char *p;
+                p=playerlocdirection(u,gotplayernum);
+                if(p) strcat(buf,p);
+                else strcat(buf,findnear(u,namex,namey,namez,NULL));
+            }
+            else
+            {
+                strcat(buf,findnear(u,namex,namey,namez,NULL));
+            }
+        }
+
+        return(buf);
+    }
+}
+
+uchar *findnearestitem(User *u,int x,int y,int z,int targetmask)
+{ // onlynamepos: 1=name, 2=pos
+    static char buf[256];
+    char *name;
+    int   namex,namey,namez;
+    int   ix,iy,iz,i;
+    float jx,jy,jz;
+    float dist;
+    float namedist=50000.0f;
+#if !defined(__APPLE__) && !defined(MACOSX)
+    int   gotplayer=0; // 1=teammate
+    int   gotplayernum=0;
+    float distlimit=10000.0f,zdistlimit=3500.0f;
+#endif /* !__APPLE__ && !MACOSX */
+
+    *buf=0;
+    name=NULL;
+    namex=0;
+    namey=0;
+    namez=0;
+
+    for(i=0;i<MAXENTITIES;i++) if(u->entityvisible[i] && (u->entitymask[i]&targetmask))
+    {
+        ix=u->entitypos[i][0]; jx=(float)(ix-x);
+        iy=u->entitypos[i][1]; jy=(float)(iy-y);
+        iz=u->entitypos[i][2]; jz=(float)(iz-z);
+        dist=(float)sqrt(jx*jx+jy*jy+jz*jz);
+
+        if(dist<namedist)
+        {
+            namedist=dist;
+            namex=ix;
+            namey=iy;
+            namez=iz;
+            name=u->entityname[i];
+        }
+    }
+
+    if(name==NULL)
+    {
+        strcpy(buf,".");
+        return(buf);
+    }
+    else
+    {
+        strcat(buf,name);
+        strcat(buf," at ");
+        strcat(buf,findnear(u,namex,namey,namez,NULL));
+
+        return(buf);
+    }
+}
+
+
+//-------------------------------------------------------
+
+#define SHOWINFOMESSAGES
+
+void userinfofromuser(Userinfo *ui,User *u)
+{
+    ui->own=1;
+    ui->name=u->name;
+
+    ui->armor=u->armor;
+    ui->health=u->health;
+
+    if(0) { }
+    else if(u->weapon&1) {
+        ui->weapon=2-1;
+        ui->ammo=u->ammo_shells;
+    }
+    else if(u->weapon&2) {
+        ui->weapon=3-1;
+        ui->ammo=u->ammo_shells;
+    }
+    else if(u->weapon&4) {
+        ui->weapon=4-1;
+        ui->ammo=u->ammo_nails;
+    }
+    else if(u->weapon&8) {
+        ui->weapon=5-1;
+        ui->ammo=u->ammo_nails;
+    }
+    else if(u->weapon&16) {
+        ui->weapon=6-1;
+        ui->ammo=u->ammo_rockets;
+    }
+    else if(u->weapon&32) {
+        ui->weapon=7-1;
+        ui->ammo=u->ammo_rockets;
+    }
+    else if(u->weapon&64) {
+        ui->weapon=8-1;
+        ui->ammo=u->ammo_cells;
+    }
+    else
+    {
+        ui->weapon=1-1;
+        ui->ammo=0;
+    }
+
+    if(0) { }
+    else if((u->items&32) && u->ammo_rockets>=0) {
+        ui->bestweapon=7-1;
+        ui->bestammo=u->ammo_rockets;
+    }
+    else if((u->items&64) && u->ammo_cells>=0) {
+        ui->bestweapon=8-1;
+        ui->bestammo=u->ammo_cells;
+    }
+    else if((u->items&16) && u->ammo_rockets>=0) {
+        ui->bestweapon=6-1;
+        ui->bestammo=u->ammo_rockets;
+    }
+    else if((u->items&8) && u->ammo_nails>=0) {
+        ui->bestweapon=5-1;
+        ui->bestammo=u->ammo_nails;
+    }
+    else if((u->items&4) && u->ammo_nails>=0) {
+        ui->bestweapon=4-1;
+        ui->bestammo=u->ammo_nails;
+    }
+    else if((u->items&2) && u->ammo_shells>=0) {
+        ui->bestweapon=3-1;
+        ui->bestammo=u->ammo_shells;
+    }
+    else if((u->items&1) && u->ammo_shells>=0) {
+        ui->bestweapon=2-1;
+        ui->bestammo=u->ammo_shells;
+    }
+    else
+    {
+        ui->bestweapon=1-1;
+        ui->bestammo=0;
+    }
+
+    ui->items=u->items;
+    ui->power=u->power;
+
+    ui->x=u->pos[0];
+    ui->y=u->pos[1];
+    ui->z=u->pos[2];
+}
+
+void sendsetinfo(User *u,uchar *msg)
+{
+    uchar *p;
+
+    sprintf(tmpbuf,"setinfo %s",msg);
+    u->appendclient[u->appendclientbytes++]=0x04;
+    p=tmpbuf; while(*p) u->appendclient[u->appendclientbytes++]=*p++;
+    u->appendclient[u->appendclientbytes++]=0;
+}
+
+void strxor80(char *t)
+{
+    while(*t)
+    {
+        if((*t&0x7f)>=0x20)
+        {
+            *t^=0x80;
+        }
+        t++;
+    }
+}
+
+void stror80(char *t)
+{
+    while(*t)
+    {
+        if((*t&0x7f)>=0x20)
+        {
+            *t|=0x80;
+        }
+        t++;
+    }
+}
+
+int memcmp7f(char *a,char *b,int len)
+{
+    while(len--)
+    {
+        if((*a&0x7f)!=(*b&0x7f)) return(1);
+        a++;
+        b++;
+    }
+    return(0);
+}
+
+void strand80(char *t)
+{
+    while(*t)
+    {
+        if((*t&0x7f)>=0x20)
+        {
+            *t&=~0x80;
+        }
+        t++;
+    }
+}
+
+int teamint(uchar *a)
+{
+    unsigned int b,x,i;
+    b=0;
+    for(i=0;i<16;i++)
+    {
+        x=*a++;
+        if(!x) return(b);
+
+        b=(b<<8)|(b>>24);
+        b^=x;
+    }
+    return(b);
+}
+
+int is_version(uchar *x)
+{
+    x--;
+    if(x[1]=='_' && x[3]=='e' && x[2]=='v' &&
+       x[4]=='r' && x[5]=='s' && x[6]=='i' && x[7]=='o' && x[8]=='n')
+    {
+        return(1);
+    }
+    else
+    {
+        return(0);
+    }
+}
+
+int is_timer(uchar *x)
+{
+    x--;
+    if(x[1]=='_' && x[3]=='i' && x[2]=='t' &&
+       x[4]=='m' && x[5]=='e' && x[6]=='r')
+    {
+        return(1);
+    }
+    else
+    {
+        return(0);
+    }
+}
+
+int is_modified(uchar *x)
+{
+    x--;
+    if(x[1]=='_' && x[3]=='o' && x[2]=='m' &&
+       x[4]=='d' && x[5]=='i' && x[6]=='f' && x[7]=='i' && x[8]=='e' && x[9]=='d')
+    {
+        return(1);
+    }
+    else
+    {
+        return(0);
+    }
+}
+
+void createversiontext(User *u,uchar *txt)
+{
+    int flags=0;
+#ifdef PROMODE
+    flags|=0x01;
+#endif
+    if(u->nofversion) flags|=2;
+    if(u->campthese) flags|=0x10;
+    if(u->campthese&(~CAMP_POWER)) flags|=0x20;
+    sprintf(txt+4,"c%s (%02X)",version,flags);
+    txt[0]='s';
+    txt[1]='a';
+    txt[2]='y';
+    txt[3]=' ';
+}
+
+int weaponok(User *u,int weapon)
+{
+    switch(weapon)
+    {
+    case 1: return(1);
+    case 2: return( (u->items & 1 ) && u->ammo_shells >=1 );
+    case 3: return( (u->items & 2 ) && u->ammo_shells >=2 );
+    case 4: return( (u->items & 4 ) && u->ammo_nails  >=1 );
+    case 5: return( (u->items & 8 ) && u->ammo_nails  >=1 );
+    case 6: return( (u->items & 16) && u->ammo_rockets>=1 );
+    case 7: return( (u->items & 32) && u->ammo_rockets>=1 );
+    case 8: return( (u->items & 64) && u->ammo_cells  >=1 );
+    default: return(1);
+    }
+}
+
+int modifyimpulse(User *u,int *impulse)
+{
+    int i,i0,seq,new,j;
+    i=i0=*impulse;
+
+    for(seq=0;seq<SEQUENCES;seq++)
+    {
+        if(i0==u->impulseorder[seq][0])
+        {
+            for(j=1;j<14;j++)
+            {
+                new=u->impulseorder[seq][j];
+                if(!new) break;
+                if(weaponok(u,new)) i=new;
+            }
+
+            if(i!=i0)
+            {
+                *impulse=i;
+                return(1);
+            }
+            else return(0);
+        }
+    }
+
+    if(u->bestweapon)
+    {
+        if(i0==4 && (u->items & 4 ) && u->ammo_nails  >=1) i=4;
+        else if(i0>8) i=i0;
+        else if(i0==8 && (u->items & 64) && u->ammo_cells  >=1) i=8;
+        else if(i0==7 && (u->items & 32) && u->ammo_rockets>=1) i=7;
+        else if(i0==6 && (u->items & 16) && u->ammo_rockets>=1) i=6;
+        else if(i0>=5 && (u->items & 8 ) && u->ammo_nails  >=1) i=5;
+        else if(i0>=3 && (u->items & 2 ) && u->ammo_shells >=2) i=3;
+        else if(i0>=4 && (u->items & 4 ) && u->ammo_nails  >=1) i=4;
+        else if(i0>=2 && (u->items & 1 ) && u->ammo_shells >=1) i=2;
+        else i=i0; // if axe just use the original impulse
+
+        if(i!=i0)
+        {
+            *impulse=i;
+            return(1);
+        }
+        else return(0);
+    }
+
+    return(0);
+}
+
+void setinfo(User *u,int i,char *attr,char *value)
+{
+    char *p;
+    u->appendserver[u->appendserverbytes++]=0x33;
+    u->appendserver[u->appendserverbytes++]=i-1;
+    p=attr;
+    for(;;)
+    {
+        u->appendserver[u->appendserverbytes++]=*p;
+        if(!*p) break;
+        p++;
+    }
+    p=value;
+    for(;;)
+    {
+        u->appendserver[u->appendserverbytes++]=*p;
+        if(!*p) break;
+        p++;
+    }
+}
+
+//-------------------------------------------------------
+
+void spectatenext(User *u,uchar *findname)
+{ // spectator change
+    int i,j;
+
+    if(findname)
+    {
+        uchar buf1[32],buf2[32];
+        memset(buf1,0,32);
+        memset(buf2,0,32);
+        strncpy(buf1,findname,31);
+        strlwr(buf1);
+
+        for(i=0;i<u->numplayers;i++)
+        {
+            u->specentity++;
+            if(u->specentity>31) u->specentity=1;
+
+            if(u->playeractive[u->specentity] && u->specentity!=u->entity)
+            { // player with this index exists!
+                strncpy(buf2,u->playername[u->specentity],31);
+                for(j=strlen(buf2)-1;j>=0;j--) buf2[j]&=0x7f;
+                strlwr(buf2);
+
+                if(strstr(buf2,buf1))
+                {
+                    break;
+                }
+            }
+        }
+        if(i>=33)
+        {
+            u->specentity=0;
+            return;
+        }
+    }
+    else
+    {
+        for(i=0;i<u->numplayers;i++)
+        {
+            u->specentity++;
+
+            if(u->specentity>31) u->specentity=1;
+            if(u->playeractive[u->specentity] && u->specentity!=u->entity)
+            { // player with this index exists!
+                break;
+            }
+        }
+        if(i>=33)
+        {
+            u->specentity=0;
+            sprintf(tmpbuf,"Nobody to track!\n");
+            q_print(u,tmpbuf);
+            return;
+        }
+    }
+
+    sprintf(tmpbuf,"ptrack %i",u->specentity-1);
+    q_servercmd(u,tmpbuf);
+
+    u->time_nextcamp=0; // update text
+}
+
+void cl_clearstats(User *u)
+{
+    u->health=100;
+    u->armor=0;
+    u->ammo=0;
+    u->ammo_shells=0;
+    u->ammo_nails=0;
+    u->ammo_rockets=0;
+    u->ammo_cells=0;
+    u->weapon=1;
+    u->items=1;
+}
+
+void cl_setinfo(User *u,uchar *attr,uchar *value)
+{
+    uchar *p,*s2,*d2;
+    int   found=0;
+    static char tmpbuf[CLIENTINFOSIZE];
+
+    logx(LOG_DEBUG,"setinfo %s to %s\n",attr,value);
+
+    if(!strcmp(attr,"name"))
+    {
+        uchar buf[80];
+        if(strcmp(u->name,value))
+        {
+            sprintf(buf,"%s is now %s\n",u->name,value);
+            strncpy(u->name,value,MAXSTR-1);
+            q_print(u,buf);
+        }
+    }
+    if(!strcmp(attr,"team"))
+    {
+        u->team=teamint(value);
+    }
+    if(!strcmp(attr,"topcolor"))
+    {
+        u->tcolor=atoi(value);
+    }
+    if(!strcmp(attr,"bottomcolor"))
+    {
+        u->bcolor=atoi(value);
+    }
+
+    memcpy(tmpbuf,u->clientinfo,CLIENTINFOSIZE);
+    d2=u->clientinfo;
+    s2=tmpbuf;
+    if(*s2) *d2++=*s2++;
+    if(*s2) *d2++=*s2++;
+    while(*s2)
+    {
+//        logx(LOG_DEBUG,"Compare <%s> to %s",attr,s2);
+        if(*s2=='\"')
+        { // end of list
+            if(d2[-1]=='\\') d2--;
+            if(!found && *value)
+            {
+                sprintf(d2,"\\%s\\%s\"\n",attr,value);
+                d2+=strlen(d2);
+            }
+            else
+            {
+                *d2++='"';
+                *d2++='\n';
+                *d2++=0;
+            }
+            break;
+        }
+        else if(!memicmp(attr,s2,strlen(attr)))
+        { // matching attribute
+            if(*value==0)
+            { // remove
+                // skip name
+                while(*s2!='\\' && *s2) s2++;
+                // skip slash
+                if(*s2=='\\') s2++;
+                // skip value
+                while(*s2!='\\' && *s2!='\"' && *s2) s2++;
+                // skip slash
+                if(*s2=='\\') s2++;
+            }
+            else
+            { // change
+                // copy name
+                while(*s2!='\\' && *s2) *d2++=*s2++;
+                // copy slash
+                if(*s2=='\\') *d2++=*s2++;
+
+                // skip original value
+                while(*s2!='\\' && *s2!='\"' && *s2) s2++;
+                // insert new value
+                p=value;
+                while(*p) *d2++=*p++;
+                // copy slash
+                if(*s2=='\\') *d2++=*s2++;
+            }
+
+            found=1;
+        }
+        else
+        { // other attribute, just copy
+            while(*s2!='\\' && *s2) *d2++=*s2++;
+            if(*s2=='\\') *d2++=*s2++;
+            while(*s2!='\\' && *s2!='\"' && *s2) *d2++=*s2++;
+            if(*s2=='\\') *d2++=*s2++;
+        }
+    }
+
+    logx(LOG_DEBUG,"clientinfo=%s",u->clientinfo);
+}
+
+Camp *campadd(User *u,int entity,int *pos,int modelindex,int skin)
+{
+    Camp *ca;
+
+    ca=&u->camp[u->camps];
+    ca->entity=entity;
+    ca->pos[0]=pos[0];
+    ca->pos[1]=pos[1];
+    ca->pos[2]=pos[2];
+
+    if(strstr(u->model[modelindex],"armor"))
+    {
+        if(skin==2) ca->name[0]='r';
+        else if(skin==1) ca->name[0]='y';
+        else ca->name[0]='g';
+        ca->name[1]='a';
+        ca->name[2]='0'+(u->campcnt[15-skin]++);
+        ca->name[3]=0;
+        ca->delay=20000;
+        ca->pickupsound="armor1.wav";
+        if(skin==2)      ca->category=CAMP_RA;
+        else if(skin==1) ca->category=CAMP_YA;
+        else             ca->category=CAMP_GA;
+        ca->genericname[0]=ca->name[0];
+        ca->genericname[1]=ca->name[1];
+        ca->genericname[2]=0;
+    }
+    else if(strstr(u->model[modelindex],"b_bh100"))
+    {
+        ca->name[0]='m';
+        ca->name[1]='h';
+        ca->name[2]='0'+(u->campcnt[1]++);
+        ca->name[3]=0;
+        ca->delay=125000;
+        ca->category=CAMP_MH;
+        ca->pickupsound="r_item2.wav";
+        ca->genericname[0]='m';
+        ca->genericname[1]='e';
+        ca->genericname[2]='g';
+        ca->genericname[3]='a';
+        ca->genericname[4]=0;
+    }
+    else if(strstr(u->model[modelindex],"suit"))
+    {
+        ca->name[0]='s';
+        ca->name[1]='u';
+        ca->name[2]='0'+(u->campcnt[2]++);
+        ca->name[3]=0;
+        ca->delay=125000;
+        ca->category=CAMP_SUIT;
+        ca->pickupsound="?.wav";
+        ca->genericname[0]='s';
+        ca->genericname[1]='u';
+        ca->genericname[2]='i';
+        ca->genericname[3]='t';
+        ca->genericname[4]=0;
+    }
+    else if(strstr(u->model[modelindex],"invisib"))
+    {
+        ca->name[0]='r';
+        ca->name[1]='0'+(u->campcnt[3]++);
+        ca->name[2]=0;
+        ca->delay=5*60000;
+        ca->category=CAMP_R;
+        ca->pickupsound="inv1.wav";
+        ca->genericname[0]='r';
+        ca->genericname[1]='i';
+        ca->genericname[2]='n';
+        ca->genericname[3]='g';
+        ca->genericname[4]=0;
+    }
+    else if(strstr(u->model[modelindex],"invulne"))
+    {
+        ca->name[0]='p';
+        ca->name[1]='0'+(u->campcnt[4]++);
+        ca->name[2]=0;
+        ca->delay=5*60000;
+        ca->category=CAMP_P;
+        ca->pickupsound="protect.wav";
+        ca->genericname[0]='p';
+        ca->genericname[1]='e';
+        ca->genericname[2]='n';
+        ca->genericname[3]='t';
+        ca->genericname[4]=0;
+    }
+    else if(strstr(u->model[modelindex],"quaddama"))
+    {
+        ca->name[0]='q';
+        ca->name[1]='0'+(u->campcnt[5]++);
+        ca->name[2]=0;
+        ca->delay=60000;
+        ca->category=CAMP_Q;
+        ca->pickupsound="damage.wav";
+        ca->genericname[0]='q';
+        ca->genericname[1]='u';
+        ca->genericname[2]='a';
+        ca->genericname[3]='d';
+        ca->genericname[4]=0;
+    }
+    else if(strstr(u->model[modelindex],"g_rock2"))
+    {
+        ca->name[0]='r';
+        ca->name[1]='l';
+        ca->name[2]='0'+(u->campcnt[6]++);
+        ca->name[3]=0;
+        ca->delay=30000;
+        ca->category=CAMP_RL;
+        ca->pickupsound="pkup.wav";
+        ca->genericname[0]=ca->name[0];
+        ca->genericname[1]=ca->name[1];
+        ca->genericname[2]=0;
+    }
+    else if(strstr(u->model[modelindex],"g_rock"))
+    {
+        ca->name[0]='g';
+        ca->name[1]='l';
+        ca->name[2]='0'+(u->campcnt[7]++);
+        ca->name[3]=0;
+        ca->delay=30000;
+        ca->category=CAMP_GL;
+        ca->pickupsound="pkup.wav";
+        ca->genericname[0]=ca->name[0];
+        ca->genericname[1]=ca->name[1];
+        ca->genericname[2]=0;
+    }
+    else if(strstr(u->model[modelindex],"g_light"))
+    {
+        ca->name[0]='l';
+        ca->name[1]='g';
+        ca->name[2]='0'+(u->campcnt[8]++);
+        ca->name[3]=0;
+        ca->delay=30000;
+        ca->category=CAMP_LG;
+        ca->pickupsound="pkup.wav";
+        ca->genericname[0]=ca->name[0];
+        ca->genericname[1]=ca->name[1];
+        ca->genericname[2]=0;
+    }
+#if 0
+    else if(strstr(u->model[modelindex],"g_nail2"))
+    {
+        ca->name[0]='s';
+        ca->name[1]='n';
+        ca->name[2]='0'+(u->campcnt[9]++);
+        ca->name[3]=0;
+        ca->delay=30000;
+        ca->category=CAMP_SNG;
+        ca->pickupsound="pkup.wav";
+        ca->genericname[0]='s';
+        ca->genericname[1]='n';
+        ca->genericname[2]='g';
+        ca->genericname[3]=0;
+    }
+    else if(strstr(u->model[modelindex],"g_nail"))
+    {
+        ca->name[0]='n';
+        ca->name[1]='g';
+        ca->name[2]='0'+(u->campcnt[10]++);
+        ca->name[3]=0;
+        ca->delay=30000;
+        ca->category=CAMP_NG;
+        ca->pickupsound="pkup.wav";
+        ca->genericname[0]='n';
+        ca->genericname[1]='g';
+        ca->genericname[2]=0;
+    }
+    else if(strstr(u->model[modelindex],"g_shot"))
+    {
+        ca->name[0]='s';
+        ca->name[1]='s';
+        ca->name[2]='0'+(u->campcnt[12]++);
+        ca->name[3]=0;
+        ca->delay=30000;
+        ca->category=CAMP_SSG;
+        ca->pickupsound="pkup.wav";
+        ca->genericname[0]='s';
+        ca->genericname[1]='s';
+        ca->genericname[2]='g';
+        ca->genericname[3]=0;
+    }
+#endif
+    else if(strstr(u->model[modelindex],"flag") || strstr(u->model[modelindex],"tf_stan"))
+    {
+        int color='?';
+        // find color
+        if(strstr(u->model[modelindex],"tf_"))
+        {
+            // in tf:
+            // 0=?
+            // 1=blue
+            // 2=red
+            // 3,4=black?,yellow?
+            if(skin==1) color='b';
+            else if(skin==2) color='r';
+        }
+        else //if(u->ctf)
+        {
+            // in ctf:
+            // 0=red
+            // 1=blue
+            if(skin==1) color='b';
+            else if(skin==0) color='r';
+        }
+
+        ca->name[0]='!';
+        ca->name[1]=ca->name[2]=color;
+        ca->name[2]=0;
+        ca->name[3]=0;
+        ca->delay=30000;
+        ca->category=CAMP_RUNE;
+        ca->pickupsound="pkup.wav";
+        ca->genericname[0]='f';
+        ca->genericname[1]='l';
+        ca->genericname[2]='a';
+        ca->genericname[3]='g';
+        ca->genericname[4]=0;
+    }
+    else ca=NULL;
+
+    return(ca);
+}
+
+char *makeentityname(User *u,int modelindex,int skin,int *mask)
+{
+    if(strstr(u->model[modelindex],"backpack"))
+    {
+        *mask=CAMP_PACK;
+        return("pack");
+    }
+    else if(strstr(u->model[modelindex],"_key"))
+    { // also b_ key
+        *mask=CAMP_RUNE;
+        return("key");
+    }
+    else if(strstr(u->model[modelindex],"g_nail2"))
+    {
+        *mask=CAMP_SNG;
+        return("sng");
+    }
+    else if(strstr(u->model[modelindex],"g_nail"))
+    {
+        *mask=CAMP_NG;
+        return("ng");
+    }
+    else if(strstr(u->model[modelindex],"g_shot"))
+    {
+        *mask=CAMP_SSG;
+        return("ssg");
+    }
+    else if(strstr(u->model[modelindex],"b_"))
+    {
+        if(strstr(u->model[modelindex],"b_bh"))
+        {
+            *mask=CAMP_HEALTH;
+            return("health");
+        }
+        else if(strstr(u->model[modelindex],"b_nail"))
+        {
+            *mask=CAMP_NAILS;
+            return("nails");
+        }
+        else if(strstr(u->model[modelindex],"b_rock"))
+        {
+            *mask=CAMP_ROCKETS;
+            return("rockets");
+        }
+        else if(strstr(u->model[modelindex],"b_shell"))
+        {
+            *mask=CAMP_SHELLS;
+            return("shells");
+        }
+        else if(strstr(u->model[modelindex],"b_batt"))
+        {
+            *mask=CAMP_CELLS;
+            return("cells");
+        }
+    }
+    else if(strstr(u->model[modelindex],"tf_flag") || strstr(u->model[modelindex],"tf_stan"))
+    {
+        *mask=CAMP_RUNE;
+        // in tf:
+        // 0=?
+        // 1=blue
+        // 2=red
+        // 3,4=black?,yellow?
+        if(skin==1) return("blue flag");
+        else if(skin==2) return("red flag");
+        else return("flag");
+    }
+    else if(strstr(u->model[modelindex],"flag"))
+    { // after TF_FLAG has been checked
+        *mask=CAMP_RUNE;
+        // in ctf:
+        // 0=red
+        // 1=blue
+        if(skin==1) return("blue flag");
+        else if(skin==0) return("red flag");
+        else return("flag");
+    }
+    else if(strstr(u->model[modelindex],"detpack"))
+    {
+        *mask=CAMP_RUNE;
+        return("detpack");
+    }
+    else if(strstr(u->model[modelindex],"turrgun"))
+    {
+        *mask=CAMP_RUNE;
+        return("sentrygun");
+    }
+    else if(strstr(u->model[modelindex],"disp"))
+    {
+        *mask=CAMP_RUNE;
+        return("dispenser");
+    }
+    else if(strstr(u->model[modelindex],"turrbase"))
+    {
+        *mask=CAMP_RUNE;
+        return("sentrygun");
+    }
+    else if(strstr(u->model[modelindex],"end"))
+    {
+        if(strstr(u->model[modelindex],"end1"))
+        {
+            *mask=CAMP_RUNE;
+            return("resist");
+        }
+        if(strstr(u->model[modelindex],"end2"))
+        {
+            *mask=CAMP_RUNE;
+            return("strength");
+        }
+        if(strstr(u->model[modelindex],"end3"))
+        {
+            *mask=CAMP_RUNE;
+            return("haste");
+        }
+        if(strstr(u->model[modelindex],"end4"))
+        {
+            *mask=CAMP_RUNE;
+            return("regen");
+        }
+    }
+    *mask=0;
+    return("item");
+}
+
+void reportfpd(User *u,int fpd)
+{
+    int  f;
+
+    f=litemode;
+    if(f)
+    {
+        q_printf(u,"proxy: lite mode disables:\n");
+        if(f&1)    q_print(u,"proxy: - all % reporting\n");
+        if(f&2)    q_print(u,"proxy: - poweruptimers\n");
+        if(f&4)    q_print(u,"proxy: - soundtrigger\n");
+        if(f&8)    q_print(u,"proxy: - (lagfeature)\n");
+        if(f&16)   q_print(u,"proxy: - (report lagchange)\n");
+        if(f&32)   q_print(u,"proxy: - %e reporting\n");
+        if(f&64)   q_print(u,"proxy: - (spectator/player talk)\n");
+        if(f&128)  q_print(u,"proxy: - hide pointing (%txyjk)\n");
+        if(f&256)  q_print(u,"proxy: - skin forcing\n");
+        if(f&512)  q_print(u,"proxy: - color forcing\n");
+    }
+
+    f=fpd&~litemode;
+    if(f)
+    {
+        if(litemode) q_printf(u,"proxy: server disables (fpd=%i):\n",f);
+        else q_printf(u,"proxy: server disables (fpd=%i):\n",f);
+        if(f&1)    q_print(u,"proxy: - all % reporting\n");
+        if(u->notimers) q_print(u,"proxy: - poweruptimers (.notimers)\n");
+        else if(f&2)    q_print(u,"proxy: - poweruptimers\n");
+        if(f&4)    q_print(u,"proxy: - soundtrigger\n");
+        if(f&8)    q_print(u,"proxy: - (lagfeature)\n");
+        if(f&16)   q_print(u,"proxy: - (report lagchange)\n");
+        if(f&32)   q_print(u,"proxy: - %e reporting\n");
+        if(f&64)   q_print(u,"proxy: - (spectator/player talk)\n");
+        if(f&128)  q_print(u,"proxy: - hide pointing (%txyjk)\n");
+        if(f&256)  q_print(u,"proxy: - skin forcing\n");
+        if(f&512)  q_print(u,"proxy: - color forcing\n");
+    }
+    else q_print(u,"proxy: no features disabled\n");
+}
+
+//------------------------------
+
+void q_servercmd(User *u,uchar *text)
+{
+    uchar *p;
+    p=u->appendclient+u->appendclientbytes;
+    *p++=0x4;
+    while(*text)
+    {
+        *p++=*text++;
+    }
+    *p++=0;
+    u->appendclientbytes=p-u->appendclient;
+}
+
+void q_explode(User *u,int type,int x,int y,int z)
+{
+    u->appendserver[u->appendserverbytes++]=0x17;
+    u->appendserver[u->appendserverbytes++]=type;
+    if(type==12 || type==2) u->appendserver[u->appendserverbytes++]=16; // 10 particles
+    u->appendserver[u->appendserverbytes++]=x;
+    u->appendserver[u->appendserverbytes++]=x>>8;
+    u->appendserver[u->appendserverbytes++]=y;
+    u->appendserver[u->appendserverbytes++]=y>>8;
+    u->appendserver[u->appendserverbytes++]=z;
+    u->appendserver[u->appendserverbytes++]=z>>8;
+}
+
+void q_print1(User *u,uchar *text)
+{ // print text in white
+    uchar *p;
+    if(u->quiet) return;
+    p=u->appendserver+u->appendserverbytes;
+    *p++=0x8;
+    *p++=2;
+    while(*text)
+    {
+        *p++=*text++;
+    }
+    *p++=0;
+    u->appendserverbytes=p-u->appendserver;
+}
+
+void q_print(User *u,uchar *text)
+{ // print text in red
+    uchar *p;
+    int  a;
+    if(u->quiet) return;
+    p=u->appendserver+u->appendserverbytes;
+    *p++=0x8;
+    *p++=2;
+    while(*text)
+    {
+        a=*text++;
+        if((a&0x7f)>32) a^=0x80;
+        *p++=a;
+    }
+    *p++=0;
+    u->appendserverbytes=p-u->appendserver;
+}
+
+void q_printf(User *u,uchar *text,...)
+{ // print text in red
+    static uchar buf[BUFSIZE];
+    va_list argp;
+    va_start(argp,text);
+    vsprintf(buf,text,argp);
+    q_print(u,buf);
+}
+
+void q_centerprint(User *u,uchar *text)
+{
+    uchar *p;
+    p=u->appendserver+u->appendserverbytes;
+    *p++=0x1a;
+    while(*text)
+    {
+        *p++=*text++;
+    }
+    *p++=0;
+    u->appendserverbytes=p-u->appendserver;
+}
+
+void q_stufftext(User *u,uchar *text)
+{
+    uchar *p;
+    p=u->appendserver+u->appendserverbytes;
+    *p++=0x9;
+    while(*text)
+    {
+        *p++=*text++;
+    }
+    *p++=0;
+    u->appendserverbytes=p-u->appendserver;
+}
+
+//------------------------------
+
+void q_growlocmax(User *u,int num)
+{ // num<0 frees
+    Location *old;
+    if(num<u->locationsmax) return;
+    if(num<u->locationsmax+256) num=u->locationsmax+256;
+
+    old=u->loc;
+
+    u->loc=mymalloc(num*sizeof(Location));
+    memset(u->loc,0,num*sizeof(Location));
+    memcpy(u->loc,old,u->locations*sizeof(Location));
+    myfree(old);
+
+    logx(LOG_DEBUG,"max locations grown to %i\n",num);
+    u->locationsmax=num;
+}
+
+void q_setlocname(User *u,int i,char *name)
+{
+    Location *loc;
+    int len;
+
+    while(i>=u->locationsmax-16)
+    {
+        q_growlocmax(u,u->locationsmax*2);
+    }
+
+    loc=u->loc+i;
+
+    if(loc->name)
+    {
+        myfree(loc->name);
+    }
+
+    if(name==NULL)
+    {
+        loc->name=NULL;
+    }
+    else
+    {
+        len=strlen(name);
+        loc->name=mymalloc(len+1);
+        strcpy(loc->name,name);
+    }
+}
+
+void q_loadloc(User *u,uchar *mapname)
+{
+    FILE *f1;
+    uchar tmp[MAXSTR];
+    if(mapname==NULL)
+    {
+        strcpy(tmp,u->locname);
+    }
+    else
+    {
+        strcpy(tmp,mapname);
+        strcat(tmp,".loc");
+        strcpy(u->locname,tmp);
+    }
+    f1=fopen(tmp,"rb");
+    u->locations=0;
+    if(f1)
+    {
+        while(!feof(f1))
+        {
+            *tmp=0;
+            fgets(tmp,255,f1);
+            if(*tmp>32)
+            {
+                uchar *p,*p2;
+                p=tmp;
+
+                while(*p && *p<=32) p++;
+                u->loc[u->locations].x=atoi(p);
+                while(*p && *p>32) p++;
+
+                while(*p && *p<=32) p++;
+                u->loc[u->locations].y=atoi(p);
+                while(*p && *p>32) p++;
+
+                while(*p && *p<=32) p++;
+                u->loc[u->locations].z=atoi(p);
+                while(*p && *p>32) p++;
+
+                while(*p && *p<=32) p++;
+
+                p2=p;
+                while(*p2 && *p2!='\n' && *p2!='\r') p2++;
+                *p2=0;
+
+                q_setlocname(u,u->locations,p);
+
+                logx(LOG_DEBUG,"Location %s (%i,%i,%i)\n",
+                              u->loc[u->locations].name,
+                              u->loc[u->locations].x,
+                              u->loc[u->locations].y,
+                              u->loc[u->locations].z);
+
+                u->locations++;
+                if(u->locations>=u->locationsmax-16)
+                {
+                    q_growlocmax(u,u->locationsmax*2);
+                }
+            }
+        }
+        fclose(f1);
+        if(u->locations>0)
+        {
+            sprintf(tmp,"proxy: %i locations from %s\n",u->locations,u->locname);
+            q_print(u,tmp);
+        }
+    }
+}
+
+void q_savelocs(User *u)
+{
+   FILE *f1;
+   int i=0;
+   f1=fopen(u->locname,"wb");
+   if(f1)
+   {
+       while(i<u->locations)
+       {
+           fprintf(f1,"%i %i %i %s\n",
+               u->loc[i].x,
+               u->loc[i].y,
+               u->loc[i].z,
+               u->loc[i].name);
+           i++;
+       }
+       fclose(f1);
+   }
+}
+
+//------------------------------
+
+uchar *formatqstring(uchar *in)
+{
+    static uchar buf[MAXSTR];
+    uchar *p=buf;
+    while(*in && p<buf+MAXSTR/2)
+    {
+        if(*in=='\n')
+        {
+            //*p++='\\';
+            //*p++='n';
+        }
+        else *p++=*in;
+        in++;
+    }
+    *p++=0;
+    return(buf);
+}
+
+uchar *extractfield(uchar *dest,uchar *src,uchar *field)
+{
+    uchar *p=src,*p0;
+    uchar *dend=dest+MAXSTR-2;
+    int    fieldlen=strlen(field);
+    // skip first /
+    while(*p!='\\' && *p) p++; // skip end of value
+    if(*p) p++;
+    // find key
+    while(*p)
+    {
+        if(!memcmp(p,field,fieldlen))
+        {
+            p+=fieldlen;
+            p0=p;
+            while(*p && *p!='\\' && *p!='"' && dest<dend)
+            {
+                *dest++=*p++;
+            }
+            *dest=0;
+            return(p0);
+        }
+        while(*p!='\\' && *p) p++; // skip end of key
+        if(*p) p++;
+        while(*p!='\\' && *p) p++; // skip end of value
+        if(*p) p++;
+    }
+    *dest=0;
+    return(p);
+}
+
+void saveparseerror(char *text,int pos,uchar *buf,int bytes)
+{
+    FILE *f1;
+    int   i;
+    f1=fopen("parse.log","ab");
+    if(f1)
+    {
+        fprintf(f1,text,buf[pos],pos);
+        fwrite(buf,1,bytes,f1);
+        for(i=0;i<16;i++) putc(0xff,f1);
+        for(i=bytes&15;i<16;i++) putc(0x00,f1);
+        fclose(f1);
+    }
+}
+
+//------------------------------
+
+uchar funny[64]=
+//0123456789ABCDEF0123456789ABCDEF
+//aaaaaaaabbbbbbbbccccccccdddddddd
+//01234567012345670123456701234567
+ "(=)a  ghij_b c  []0123456789.<->"
+ "                            ,   ";
+int funny2[64]={
+0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
+0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
+0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
+0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
+0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
+0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
+0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
+0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f};
+
+void funnyhelp(User *u)
+{
+    int i,n=0;
+    uchar *p;
+    q_print(u,"Funny names helper:\n");
+    q_print(u,"names and messages containg\n"
+              "following character sequences\n"
+              "are converted.\n");
+
+    strcpy(tmpbuf,"#X:red version of X\n");
+    tmpbuf[0]^=0x80;
+    tmpbuf[1]^=0x80;
+    q_print(u,tmpbuf);
+
+    strcpy(tmpbuf,"$r:cr, $n:lf (team only)\n");
+    q_print(u,tmpbuf);
+
+    strcpy(tmpbuf,"$0..9:yellow number\n");
+    tmpbuf[0]^=0x80;
+    tmpbuf[1]^=0x80;
+    tmpbuf[4]^=0x80;
+    q_print(u,tmpbuf);
+    p=tmpbuf;
+    for(i=0;i<64;i++) if((funny[i]<'0' || funny[i]>'9') && funny[i]< 'A' && funny[i]!=' ')
+    {
+        sprintf(p,"$%c:%c ",funny[i],funny2[i]);
+        p[0]^=0x80;
+        p[1]^=0x80;
+        p+=strlen(p);
+        n++;
+        if(n>=7)
+        {
+            *p++='\n';
+            n=0;
+        }
+    }
+    for(i=0;i<64;i++) if((funny[i]<'0' || funny[i]>'9') && funny[i]>='A' && funny[i]!=' ')
+    {
+        sprintf(p,"$%c:%c ",funny[i],funny2[i]);
+        p[0]^=0x80;
+        p[1]^=0x80;
+        p+=strlen(p);
+        n++;
+        if(n>=7)
+        {
+            *p++='\n';
+            n=0;
+        }
+    }
+    *p++='\n';
+    n=0;
+    for(i=0;i<32;i++)
+    {
+        sprintf(p,"$x%02X:%c ",i+0x80,i+0x80);
+        p[0]^=0x80;
+        p[1]^=0x80;
+        p[2]^=0x80;
+        p[3]^=0x80;
+        p+=strlen(p);
+        n++;
+        if(n>=5)
+        {
+            *p++='\n';
+            n=0;
+        }
+    }
+    *p++='\n';
+    *p++=0;
+    q_print(u,tmpbuf);
+}
+
+int funnyconvert(uchar *d,uchar *s,int nobits)
+{
+    int chg=0;
+    int nocolor=(nobits&1);
+    int noenter=(nobits&2);
+    while(*s)
+    { // ##, $$
+        if(*s=='#' && !nocolor)
+        {
+            s++;
+            if(*s=='#')
+            {
+                *d++='#';
+                s++;
+            }
+            else if(!*s) *d++=' ';
+            else *d++=*s++^0x80;
+            chg=1;
+        }
+        else if(*s=='$')
+        {
+            int j,x;
+            s++;
+            if(*s=='$')
+            {
+                *d++='$';
+                s++;
+            }
+            else if(*s=='\\' && !noenter)
+            {
+                *d++='x';
+                *d++='\r';
+                s++;
+            }
+            else if(*s=='r' && !noenter)
+            {
+                *d++='\r';
+                s++;
+            }
+            else if(*s=='n' && !noenter)
+            {
+                *d++='\n';
+                s++;
+            }
+            else if(*s=='t' && !noenter)
+            {
+                *d++='\t';
+                s++;
+            }
+#ifdef PROMODE
+            else if(*s=='e')
+            {
+                *d++=0x7f;
+                s++;
+            }
+            else if(*s=='z')
+            {
+                *d++=0x80;
+                s++;
+            }
+#endif
+            else if(*s=='x' && !noenter)
+            {
+                int a=0;
+                s++;
+                sscanf(s,"%02X",&a);
+                *d++=a;
+                if(*s) s++;
+                if(*s) s++;
+            }
+            else if(*s=='\'')
+            {
+                *d++='\"';
+                s++;
+            }
+            else if(!*s) *d++=' ';
+            else
+            {
+                x=tolower(*s);
+                for(j=0;j<64;j++) if(funny[j]==x)
+                {
+                    *d++=funny2[j];
+                    break;
+                }
+                if(j==64)
+                {
+                    *d++='$';
+                    *d++=*s;
+                }
+                s++;
+            }
+            chg=1;
+        }
+        else
+        {
+            *d++=*s++;
+        }
+    }
+    *d++=0;
+    return(chg);
+}
+
+//-------------------------------------------------------
+
+unsigned char inittable[1024]={
+0x78,0xD2,0x94,0xE3,0x41,0xEC,0xD6,0xD5,0xCB,0xFC,0xDB,0x8A,0x4B,0xCC,0x85,0x01,
+0x23,0xD2,0xE5,0xF2,0x29,0xA7,0x45,0x94,0x4A,0x62,0xE3,0xA5,0x6F,0x3F,0xE1,0x7A,
+0x64,0xED,0x5C,0x99,0x29,0x87,0xA8,0x78,0x59,0x0D,0xAA,0x0F,0x25,0x0A,0x5C,0x58,
+0xFB,0x00,0xA7,0xA8,0x8A,0x1D,0x86,0x80,0xC5,0x1F,0xD2,0x28,0x69,0x71,0x58,0xC3,
+0x51,0x90,0xE1,0xF8,0x6A,0xF3,0x8F,0xB0,0x68,0xDF,0x95,0x40,0x5C,0xE4,0x24,0x6B,
+0x29,0x19,0x71,0x3F,0x42,0x63,0x6C,0x48,0xE7,0xAD,0xA8,0x4B,0x91,0x8F,0x42,0x36,
+0x34,0xE7,0x32,0x55,0x59,0x2D,0x36,0x38,0x38,0x59,0x9B,0x08,0x16,0x4D,0x8D,0xF8,
+0x0A,0xA4,0x52,0x01,0xBB,0x52,0xA9,0xFD,0x40,0x18,0x97,0x37,0xFF,0xC9,0x82,0x27,
+0xB2,0x64,0x60,0xCE,0x00,0xD9,0x04,0xF0,0x9E,0x99,0xBD,0xCE,0x8F,0x90,0x4A,0xDD,
+0xE1,0xEC,0x19,0x14,0xB1,0xFB,0xCA,0x1E,0x98,0x0F,0xD4,0xCB,0x80,0xD6,0x05,0x63,
+0xFD,0xA0,0x74,0xA6,0x86,0xF6,0x19,0x98,0x76,0x27,0x68,0xF7,0xE9,0x09,0x9A,0xF2,
+0x2E,0x42,0xE1,0xBE,0x64,0x48,0x2A,0x74,0x30,0xBB,0x07,0xCC,0x1F,0xD4,0x91,0x9D,
+0xAC,0x55,0x53,0x25,0xB9,0x64,0xF7,0x58,0x4C,0x34,0x16,0xBC,0xF6,0x12,0x2B,0x65,
+0x68,0x25,0x2E,0x29,0x1F,0xBB,0xB9,0xEE,0x6D,0x0C,0x8E,0xBB,0xD2,0x5F,0x1D,0x8F,
+0xC1,0x39,0xF9,0x8D,0xC0,0x39,0x75,0xCF,0x25,0x17,0xBE,0x96,0xAF,0x98,0x9F,0x5F,
+0x65,0x15,0xC4,0x62,0xF8,0x55,0xFC,0xAB,0x54,0xCF,0xDC,0x14,0x06,0xC8,0xFC,0x42,
+0xD3,0xF0,0xAD,0x10,0x08,0xCD,0xD4,0x11,0xBB,0xCA,0x67,0xC6,0x48,0x5F,0x9D,0x59,
+0xE3,0xE8,0x53,0x67,0x27,0x2D,0x34,0x9E,0x9E,0x24,0x29,0xDB,0x69,0x99,0x86,0xF9,
+0x20,0xB5,0xBB,0x5B,0xB0,0xF9,0xC3,0x67,0xAD,0x1C,0x9C,0xF7,0xCC,0xEF,0xCE,0x69,
+0xE0,0x26,0x8F,0x79,0xBD,0xCA,0x10,0x17,0xDA,0xA9,0x88,0x57,0x9B,0x15,0x24,0xBA,
+0x84,0xD0,0xEB,0x4D,0x14,0xF5,0xFC,0xE6,0x51,0x6C,0x6F,0x64,0x6B,0x73,0xEC,0x85,
+0xF1,0x6F,0xE1,0x67,0x25,0x10,0x77,0x32,0x9E,0x85,0x6E,0x69,0xB1,0x83,0x00,0xE4,
+0x13,0xA4,0x45,0x34,0x3B,0x40,0xFF,0x41,0x82,0x89,0x79,0x57,0xFD,0xD2,0x8E,0xE8,
+0xFC,0x1D,0x19,0x21,0x12,0x00,0xD7,0x66,0xE5,0xC7,0x10,0x1D,0xCB,0x75,0xE8,0xFA,
+0xB6,0xEE,0x7B,0x2F,0x1A,0x25,0x24,0xB9,0x9F,0x1D,0x78,0xFB,0x84,0xD0,0x17,0x05,
+0x71,0xB3,0xC8,0x18,0xFF,0x62,0xEE,0xED,0x53,0xAB,0x78,0xD3,0x65,0x2D,0xBB,0xC7,
+0xC1,0xE7,0x70,0xA2,0x43,0x2C,0x7C,0xC7,0x16,0x04,0xD2,0x45,0xD5,0x6B,0x6C,0x7A,
+0x5E,0xA1,0x50,0x2E,0x31,0x5B,0xCC,0xE8,0x65,0x8B,0x16,0x85,0xBF,0x82,0x83,0xFB,
+0xDE,0x9F,0x36,0x48,0x32,0x79,0xD6,0x9B,0xFB,0x52,0x45,0xBF,0x43,0xF7,0x0B,0x0B,
+0x19,0x19,0x31,0xC3,0x85,0xEC,0x1D,0x8C,0x20,0xF0,0x3A,0xFA,0x80,0x4D,0x2C,0x7D,
+0xAC,0x60,0x09,0xC0,0x40,0xEE,0xB9,0xEB,0x13,0x5B,0xE8,0x2B,0xB1,0x20,0xF0,0xCE,
+0x4C,0xBD,0xC6,0x04,0x86,0x70,0xC6,0x33,0xC3,0x15,0x0F,0x65,0x19,0xFD,0xC2,0xD3};
+
+unsigned int checktable[256]={
+0x0000,0x1021,0x2042,0x3063,0x4084,0x50A5,0x60C6,0x70E7,
+0x8108,0x9129,0xA14A,0xB16B,0xC18C,0xD1AD,0xE1CE,0xF1EF,
+0x1231,0x0210,0x3273,0x2252,0x52B5,0x4294,0x72F7,0x62D6,
+0x9339,0x8318,0xB37B,0xA35A,0xD3BD,0xC39C,0xF3FF,0xE3DE,
+0x2462,0x3443,0x0420,0x1401,0x64E6,0x74C7,0x44A4,0x5485,
+0xA56A,0xB54B,0x8528,0x9509,0xE5EE,0xF5CF,0xC5AC,0xD58D,
+0x3653,0x2672,0x1611,0x0630,0x76D7,0x66F6,0x5695,0x46B4,
+0xB75B,0xA77A,0x9719,0x8738,0xF7DF,0xE7FE,0xD79D,0xC7BC,
+0x48C4,0x58E5,0x6886,0x78A7,0x0840,0x1861,0x2802,0x3823,
+0xC9CC,0xD9ED,0xE98E,0xF9AF,0x8948,0x9969,0xA90A,0xB92B,
+0x5AF5,0x4AD4,0x7AB7,0x6A96,0x1A71,0x0A50,0x3A33,0x2A12,
+0xDBFD,0xCBDC,0xFBBF,0xEB9E,0x9B79,0x8B58,0xBB3B,0xAB1A,
+0x6CA6,0x7C87,0x4CE4,0x5CC5,0x2C22,0x3C03,0x0C60,0x1C41,
+0xEDAE,0xFD8F,0xCDEC,0xDDCD,0xAD2A,0xBD0B,0x8D68,0x9D49,
+0x7E97,0x6EB6,0x5ED5,0x4EF4,0x3E13,0x2E32,0x1E51,0x0E70,
+0xFF9F,0xEFBE,0xDFDD,0xCFFC,0xBF1B,0xAF3A,0x9F59,0x8F78,
+0x9188,0x81A9,0xB1CA,0xA1EB,0xD10C,0xC12D,0xF14E,0xE16F,
+0x1080,0x00A1,0x30C2,0x20E3,0x5004,0x4025,0x7046,0x6067,
+0x83B9,0x9398,0xA3FB,0xB3DA,0xC33D,0xD31C,0xE37F,0xF35E,
+0x02B1,0x1290,0x22F3,0x32D2,0x4235,0x5214,0x6277,0x7256,
+0xB5EA,0xA5CB,0x95A8,0x8589,0xF56E,0xE54F,0xD52C,0xC50D,
+0x34E2,0x24C3,0x14A0,0x0481,0x7466,0x6447,0x5424,0x4405,
+0xA7DB,0xB7FA,0x8799,0x97B8,0xE75F,0xF77E,0xC71D,0xD73C,
+0x26D3,0x36F2,0x0691,0x16B0,0x6657,0x7676,0x4615,0x5634,
+0xD94C,0xC96D,0xF90E,0xE92F,0x99C8,0x89E9,0xB98A,0xA9AB,
+0x5844,0x4865,0x7806,0x6827,0x18C0,0x08E1,0x3882,0x28A3,
+0xCB7D,0xDB5C,0xEB3F,0xFB1E,0x8BF9,0x9BD8,0xABBB,0xBB9A,
+0x4A75,0x5A54,0x6A37,0x7A16,0x0AF1,0x1AD0,0x2AB3,0x3A92,
+0xFD2E,0xED0F,0xDD6C,0xCD4D,0xBDAA,0xAD8B,0x9DE8,0x8DC9,
+0x7C26,0x6C07,0x5C64,0x4C45,0x3CA2,0x2C83,0x1CE0,0x0CC1,
+0xEF1F,0xFF3E,0xCF5D,0xDF7C,0xAF9B,0xBFBA,0x8FD9,0x9FF8,
+0x6E17,0x7E36,0x4E55,0x5E74,0x2E93,0x3EB2,0x0ED1,0x1EF0};
+
+int checksum(char *packetstart,char *data,int size)
+{
+    char buf[64];
+    int  sum,i,a,b;
+
+    if(size>0x3c) size=0x3c;
+    memcpy(buf,data,size);
+
+    b =(packetstart[0]&255);
+    b|=(packetstart[1]&255)<<8;
+    b|=(packetstart[2]&255)<<16;
+    b|=(packetstart[3]&255)<<24;
+    b&=0x7fffffff;
+    a=b%1020;
+
+    buf[size+0]=inittable[a+0]^(b);
+    buf[size+1]=inittable[a+1];
+    buf[size+2]=inittable[a+2]^(b>>8);
+    buf[size+3]=inittable[a+3];
+    size+=4;
+
+//    printf("b=%02X ebp=%02X %02X %02X %02X\n",b,buf[size-4],buf[size-3],buf[size-2],buf[size-1]);
+
+    sum=0xffff;
+    for(i=0;i<size;i++)
+    {
+        a=(sum&0xffff) >> 8;
+        a^=buf[i]&255;
+        b=(sum&0xff) << 8;
+        sum=checktable[a]^b;
+    }
+
+    return(sum&255);
+}
+
+int floodprot(User *u)
+{
+    if(u->floodprot && u->time<u->time_lastsay[0]+4100)
+    {
+        q_print(u,"proxy: flood protect protect\n");
+        return(1);
+    }
+
+    u->time_lastsay[0]=u->time_lastsay[1];
+    u->time_lastsay[1]=u->time_lastsay[2];
+    u->time_lastsay[2]=u->time_lastsay[3];
+    u->time_lastsay[3]=u->time;
+
+    return(0);
+}
+
+void dmmchange(User *u,int newdmm)
+{
+    u->originaldmm=newdmm;
+    u->dmm=newdmm;
+    if(u->dmm<1) u->dmm=1;
+    if(u->dmm>3) u->dmm=3;
+    if(u->forcedmm) u->dmm=u->forcedmm;
+
+    if(u->dmm==1) u->dmmitemmask=~0;
+    else u->dmmitemmask=~(CAMP_RL|CAMP_LG|CAMP_GL|CAMP_SNG|CAMP_NG|CAMP_SSG);
+
+//    q_printf(u,"dmm=%i, mask=%08X\n",u->dmm,u->dmmitemmask);
+}
+
+int text2color(char *p)
+{
+    int   c;
+    char *p2;
+
+    if(p[0]=='t' || p[0]=='T') return(-2);
+    else if(p[0]=='r' || p[0]=='R') return(-3);
+    else if(*p=='*' || !*p || *p<'0' || *p>'9') return(-1);
+
+    p2=p;
+    while(*p2 && *p2>32)
+    {
+        if(*p2==',') break;
+        p2++;
+    }
+
+    if(*p2==',')
+    {
+        c=(atoi(p2+1)&15)+16*(atoi(p)&15)+256;
+    }
+    else
+    {
+        c=(atoi(p)&15)+16*(atoi(p)&15)+256;
+    }
+
+    return(c);
+}
+
+char *color2text(int c)
+{
+    static char buf[64];
+    if(c==-1)      sprintf(buf,"* ");
+    else if(c==-2) sprintf(buf,"Team ");
+    else if(c==-3) sprintf(buf,"Rank ");
+    else if(c<255) sprintf(buf,"%i ",c);
+    else           sprintf(buf,"%i,%i ",(c>>4)&15,c&15);
+    return(buf);
+}
+
diff --git a/mkalpha b/mkalpha
new file mode 100755 (executable)
index 0000000..ea0b1fd
--- /dev/null
+++ b/mkalpha
@@ -0,0 +1 @@
+cc -ocheapo -DUNIXALPHA -lm -w1 -fast main.c proxy.c cmd.c misc.c compress.c
diff --git a/mklinux b/mklinux
new file mode 100755 (executable)
index 0000000..4d91f71
--- /dev/null
+++ b/mklinux
@@ -0,0 +1,3 @@
+#!/bin/sh
+# Legacy one-line build; prefer "make".
+gcc -O2 -o cheapo main.c proxy.c cmd.c misc.c compress.c -lm
diff --git a/mksparc b/mksparc
new file mode 100755 (executable)
index 0000000..f4bc31f
--- /dev/null
+++ b/mksparc
@@ -0,0 +1 @@
+gcc -O2 -o cheapo main.c proxy.c cmd.c misc.c compress.c -lm -lsocket -lnsl
diff --git a/mkwin.bat b/mkwin.bat
new file mode 100755 (executable)
index 0000000..20a8763
--- /dev/null
+++ b/mkwin.bat
@@ -0,0 +1 @@
+nmake /f Makefile.win\r
diff --git a/proxy.c b/proxy.c
new file mode 100755 (executable)
index 0000000..4f31fc1
--- /dev/null
+++ b/proxy.c
@@ -0,0 +1,4953 @@
+// Cheapo - a QuakeWorld Proxy
+// Copyright (C) 1998 Sami Tammilehto
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+#include "proxy.h"
+
+#define abs(x) ((x)<0?-(x):(x))
+
+uchar tmpbuf[BUFSIZE];
+uchar outbuf[BUFSIZE];
+
+User *globalu;
+int   proxy_port;
+char  quakepath[MAXSTR];
+char  quakeoptions[MAXSTR];
+int   gamespy;
+int   quakemodified;
+int   quakehandle; // process handle
+uchar proxy_name[MAXSTR];
+uchar proxy_allowip[MAXSTR];
+uchar proxy_denyip[16][MAXSTR];
+int   proxy_denyipnum;
+uchar proxy_password[MAXSTR];
+int   challenge=1634346416;
+int   globaltime;
+int   globaltime0;
+
+//#define RLPACKDEBUG
+
+#define DUP1     (*d++=*s++)
+#define DUPN(n)  { for(duptmp=0;duptmp<(n);duptmp++) *d++=*s++; }
+#define DUPTEXT  { while(*s) *d++=*s++;   *d++=*s++; }
+#define SKIPTEXT { while(*s) s++; s++; }
+
+//-------------------------------------------------------
+
+// framelist for player model (0..142)
+//  00 axrun1    20 axstand4   40 pain6     60 deatha11  80 deathc11  100 deathe7   100 deathe7   120 axatt2   140 axattd4
+//  01 axrun2    21 axstand5   41 axdeth1   61 deathb1   81 deathc12  101 deathe8   101 deathe8   121 axatt3   141 axattd5
+//  02 axrun3    22 axstand6   42 axdeth2   62 deathb2   82 deathc13  102 deathe9   102 deathe9   122 axatt4   142 axattd6
+//  03 axrun4    23 axstand7   43 axdeth3   63 deathb3   83 deathc14  103 nailatt1  103 nailatt1  123 axatt5   .         .
+//  04 axrun5    24 axstand8   44 axdeth4   64 deathb4   84 deathc15  104 nailatt2  104 nailatt2  124 axatt6   .         .
+//  05 axrun6    25 axstand9   45 axdeth5   65 deathb5   85 deathd1   105 light1    105 light1    125 axattb1  .         .
+//  06 rockrun1  26 axstand10  46 axdeth6   66 deathb6   86 deathd2   106 light2    106 light2    126 axattb2  .         .
+//  07 rockrun2  27 axstand11  47 axdeth7   67 deathb7   87 deathd3   107 rockatt1  107 rockatt1  127 axattb3  .         .
+//  08 rockrun3  28 axstand12  48 axdeth8   68 deathb8   88 deathd4   108 rockatt2  108 rockatt2  128 axattb4  .         .
+//  09 rockrun4  29 axpain1    49 axdeth9   69 deathb9   89 deathd5   109 rockatt3  109 rockatt3  129 axattb5  .         .
+//  10 rockrun5  30 axpain2    50 deatha1   70 deathc1   90 deathd6   110 rockatt4  110 rockatt4  130 axattb6  .         .
+//  11 rockrun6  31 axpain3    51 deatha2   71 deathc2   91 deathd7   111 rockatt5  111 rockatt5  131 axattc1  .         .
+//  12 stand1    32 axpain4    52 deatha3   72 deathc3   92 deathd8   112 rockatt6  112 rockatt6  132 axattc2  .         .
+//  13 stand2    33 axpain5    53 deatha4   73 deathc4   93 deathd9   113 shotatt1  113 shotatt1  133 axattc3  .         .
+//  14 stand3    34 axpain6    54 deatha5   74 deathc5   94 deathe1   114 shotatt2  114 shotatt2  134 axattc4  .         .
+//  15 stand4    35 pain1      55 deatha6   75 deathc6   95 deathe2   115 shotatt3  115 shotatt3  135 axattc5  .         .
+//  16 stand5    36 pain2      56 deatha7   76 deathc7   96 deathe3   116 shotatt4  116 shotatt4  136 axattc6  .         .
+//  17 axstand1  37 pain3      57 deatha8   77 deathc8   97 deathe4   117 shotatt5  117 shotatt5  137 axattd1  .         .
+//  18 axstand2  38 pain4      58 deatha9   78 deathc9   98 deathe5   118 shotatt6  118 shotatt6  138 axattd2  .         .
+//  19 axstand3  39 pain5      59 deatha10  79 deathc10  99 deathe6   119 axatt1    119 axatt1    139 axattd3  .         .
+
+#define LASTPLAYERFRAME 142
+char *playeranim[150]={
+"axrun1","axrun2","axrun3","axrun4","axrun5","axrun6","rockrun1","rockrun2",
+"rockrun3","rockrun4","rockrun5","rockrun6","stand1","stand2","stand3",
+"stand4","stand5","axstand1","axstand2","axstand3","axstand4","axstand5",
+"axstand6","axstand7","axstand8","axstand9","axstand10","axstand11",
+"axstand12","axpain1","axpain2","axpain3","axpain4","axpain5","axpain6",
+"pain1","pain2","pain3","pain4","pain5","pain6","axdeth1","axdeth2","axdeth3",
+"axdeth4","axdeth5","axdeth6","axdeth7","axdeth8","axdeth9","deatha1",
+"deatha2","deatha3","deatha4","deatha5","deatha6","deatha7","deatha8",
+"deatha9","deatha10","deatha11","deathb1","deathb2","deathb3","deathb4",
+"deathb5","deathb6","deathb7","deathb8","deathb9","deathc1","deathc2",
+"deathc3","deathc4","deathc5","deathc6","deathc7","deathc8","deathc9",
+"deathc10","deathc11","deathc12","deathc13","deathc14","deathc15","deathd1",
+"deathd2","deathd3","deathd4","deathd5","deathd6","deathd7","deathd8",
+"deathd9","deathe1","deathe2","deathe3","deathe4","deathe5","deathe6",
+"deathe7","deathe8","deathe9","nailatt1","nailatt2","light1","light2",
+"rockatt1","rockatt2","rockatt3","rockatt4","rockatt5","rockatt6","shotatt1",
+"shotatt2","shotatt3","shotatt4","shotatt5","shotatt6","axatt1","axatt2",
+"axatt3","axatt4","axatt5","axatt6","axattb1","axattb2","axattb3",
+"axattb4","axattb5","axattb6","axattc1","axattc2","axattc3","axattc4",
+"axattc5","axattc6","axattd1","axattd2","axattd3","axattd4","axattd5",
+"axattd6"};
+
+char *colornames[16]={
+"white","brown","lt.blue","green",
+"red","olive","pink","flesh",
+"lavender","purple","beige","aqua",
+"yellow","blue","blue","blue"};
+
+//-------------------------------------------------------hilo
+
+int intat(uchar *a)
+{
+    int b;
+    b =((uchar *)a)[0];
+    b|=((uchar *)a)[1]<<8;
+    b|=((uchar *)a)[2]<<16;
+    b|=((uchar *)a)[3]<<24;
+    return(b);
+}
+
+int shortat(uchar *a)
+{
+    int b;
+    b =((uchar *)a)[0];
+    b|=((uchar *)a)[1]<<8;
+    return(b);
+}
+
+void setintat(uchar *a,int b)
+{
+    ((uchar *)a)[0]=(b>>0 )&255;
+    ((uchar *)a)[1]=(b>>8 )&255;
+    ((uchar *)a)[2]=(b>>16)&255;
+    ((uchar *)a)[3]=(b>>24)&255;
+}
+
+#ifdef HILO
+
+int _dup2(uchar **d,uchar **s)
+{
+    int x;
+    (*d)[0]=(*s)[0];
+    (*d)[1]=(*s)[1];
+    x=(*s)[0];
+    x|=(*s)[1]<<8;
+    *s+=2;
+    *d+=2;
+    return((int)(short)x);
+}
+
+int _dup4(uchar **d,uchar **s)
+{
+    int x;
+    (*d)[0]=(*s)[0];
+    (*d)[1]=(*s)[1];
+    (*d)[2]=(*s)[2];
+    (*d)[3]=(*s)[3];
+    x=(*s)[0];
+    x|=(*s)[1]<<8;
+    x|=(*s)[2]<<16;
+    x|=(*s)[3]<<24;
+    *s+=4;
+    *d+=4;
+    return(x);
+}
+
+#define DUP2    _dup2(&d,&s)
+#define DUP4    _dup4(&d,&s)
+
+#else
+
+#define DUP2    (*(((short *)d)++)=*(((short *)s)++))
+#define DUP4    (*(((unsigned int *)d)++)=*(((unsigned int *)s)++))
+
+#endif
+
+//-------------------------------------------------------
+
+void checkpowers(User *u)
+{
+    int powers,changed,value;
+
+    powers=ITEMS_Q|ITEMS_P|ITEMS_R;
+    if(u->fortress) powers|=ITEMS_FLAG;
+
+    changed=u->power^u->lastpower;
+    value=u->power&powers;
+
+    if(u->amspectator || !(changed&powers)) return;
+    u->lastpower=u->power;
+
+    if((value&changed)==0)
+    {
+        // change to "got" power state
+        u->lastnonzeropower=changed;
+    }
+
+    if(u->autoihave&2)
+    {
+        if(changed&ITEMS_Q)
+        {
+            if(value&ITEMS_Q) q_stufftext(u,"f_powerup_quad\n");
+            else              q_stufftext(u,"f_powerup_noquad\n");
+        }
+        if(changed&ITEMS_R)
+        {
+            if(value&ITEMS_R) q_stufftext(u,"f_powerup_ring\n");
+            else              q_stufftext(u,"f_powerup_noring\n");
+        }
+        if(changed&ITEMS_P)
+        {
+            if(value&ITEMS_P) q_stufftext(u,"f_powerup_pent\n");
+            else              q_stufftext(u,"f_powerup_nopent\n");
+        }
+        if(changed&ITEMS_FLAG)
+        {
+            if(value&ITEMS_FLAG) q_stufftext(u,"f_powerup_flag\n");
+            else                 q_stufftext(u,"f_powerup_noflag\n");
+        }
+    }
+
+    if(u->autoihave&1)
+    {
+        if(value&powers) q_stufftext(u,"f_powerup\n");
+    }
+}
+
+void cl_updatestat(User *u,int stat,int value)
+{
+    if(stat==0)
+    {
+        if(u->health>0 && value<=0)
+        {
+            int i;
+            findnear(u,u->pos[0],u->pos[1],u->pos[2],&u->diedatloc);
+            u->time_died=u->time;
+
+            u->playerloc[u->entity]=0;
+            u->playerlastloc[u->entity]=0;
+
+            u->diedatown=-1;
+            u->diedatenemy=0;
+            for(i=0;i<u->numplayers;i++)
+            {
+                if(u->playernear[i] && u->playeractive[i])
+                {
+                    int dist;
+                    dist =abs(u->playerpos[i][0]-u->pos[0]);
+                    dist+=abs(u->playerpos[i][1]-u->pos[1]);
+                    dist+=abs(u->playerpos[i][2]-u->pos[2]);
+                    if(dist<NEARDIST)
+                    {
+                        if(SAMETEAM(i))
+                        {
+                            u->diedatown++;
+                        }
+                        else
+                        {
+                            u->diedatenemy++;
+                        }
+                    }
+                }
+            }
+        }
+        u->health=value;
+    }
+    if(stat==3)  u->ammo=value;
+    if(stat==4)  u->armor=value;
+    if(stat==6)  u->ammo_shells=value;
+    if(stat==7)  u->ammo_nails=value;
+    if(stat==8)  u->ammo_rockets=value;
+    if(stat==9)  u->ammo_cells=value;
+    if(stat==10) u->weapon=value;
+    if(stat==15)
+    {
+        u->items=value;
+        u->power=u->items;
+        checkpowers(u);
+    }
+}
+
+void campsound(User *u,uchar *sound,int entity,int x,int y,int z)
+{
+    Camp *ca;
+    int   i;
+/*    uchar *pname="action"; */
+
+    if(u->amspectator) return;
+
+/*
+    int    locindex;
+    uchar *pos;
+    if(entity>0 && entity<=32 && u->playeractive[entity])
+    {
+        static int cnt=0;
+        pname=u->playername[entity];
+        if(!memcmp(sound,"weapons/sgun1",13))
+        {
+#ifdef RLPACKDEBUG
+            q_printf(u,"<%i: %s launched>\n",cnt++,pname);
+#endif
+            u->time_playerrocket[entity]=u->time;
+        }
+        if(!memcmp(sound,"player/",7))
+        {
+            if(!memcmp(sound+7,"death",5) ||
+               !memcmp(sound+7,"gib",3) ||
+               !memcmp(sound+7,"teledth",7) ||
+               !memcmp(sound+7,"udeath",6) ||
+               !memcmp(sound+7,"h2odeat",7))
+            {
+#ifdef RLPACKDEBUG
+                q_printf(u,"<%i: %s died>\n",cnt++,pname);
+#endif
+                u->time_playerdied[entity]=u->time;
+            }
+        }
+    }
+
+    if(loglevel>LOG_DEBUG)
+    {
+        pos=findnear(u,x,y,z,&locindex);
+        logx(LOG_DEBUG,"Sound: %s near %s sound %s (e:%03X pos:%i,%i,%i)\n"
+                     ,pname,pos,sound,entity,x,y,z);
+    }
+*/
+
+    for(i=0;i<u->camps;i++)
+    {
+        ca=&u->camp[i];
+        if(abs(x-ca->pos[0])<TOOKRADIUS &&
+           abs(y-ca->pos[1])<TOOKRADIUS &&
+           abs(z-ca->pos[2])<TOOKRADIUS &&
+           strstr(sound,ca->pickupsound))
+        {
+#ifdef TIMERS
+            if(u->campthese && !(u->fpdcopy&FPD_TIMERS))
+            {
+                if(ca->category&CAMP_MH)
+                {
+                    if(u->entity==entity)
+                    {
+                        u->time_nextcamp=u->time;
+                        ca->appeartime=UNKNOWNTIME2; // other players will get this
+                        ca->appeartime=UNKNOWNTIME;
+                        ca->secondcamp=0;
+                    }
+                }
+                else
+                {
+                    ca->appeartime=u->time+ca->delay;
+                    ca->secondcamp=0;
+                    u->time_nextcamp=u->time;
+                }
+            }
+#endif
+
+#if 0
+            if(u->autoqpicked && !(u->fpd&FPD_TIMERS) && (ca->category&CAMP_POWER))
+            { // took an item
+                uchar buf[64];
+                *buf=0;
+                if(ca->category&CAMP_Q)
+                {
+                    sprintf(buf,"say_team Quad #%c picked up.%cq",ca->name[1],0x7f);
+                }
+                if(ca->category&CAMP_P)
+                {
+                    sprintf(buf,"say_team Pent #%c picked up.%cp",ca->name[1],0x7f);
+                }
+                if(ca->category&CAMP_R)
+                {
+                    sprintf(buf,"say_team Ring #%c picked up.%cr",ca->name[1],0x7f);
+                }
+                if(!floodprot(u))
+                {
+                    q_servercmd(u,buf);
+                }
+            }
+#endif
+
+            if(entity==u->entity)
+            {
+                sprintf(u->lasttook,"%s at %s",
+                    u->entityname[ca->entity],
+                    findnear(u,
+                             u->entitypos[ca->entity][0],
+                             u->entitypos[ca->entity][1],
+                             u->entitypos[ca->entity][2],
+                             NULL));
+                u->time_lasttook=u->time;
+                u->lastentitypicked=ca->entity;
+                if(!u->amspectator && u->autoitook)
+                {
+                    if((ca->category&u->autoitookitems&u->dmmitemmask) ||
+                       ((u->autoitookitems&CAMP_POINTED) && ca->entity==u->lastentitypointed) )
+                    { // took an item
+                        u->lastentitypointed=0;
+                        q_stufftext(u,"f_took\n");
+                    }
+                }
+            }
+
+            return;
+        }
+    }
+
+    // if not in camp list
+
+    if(entity==u->entity)
+    {
+        int lock=0;
+        if(strstr(sound,"lock")) lock=1;
+        if(strstr(sound,"pkup")) lock=1;
+        if(lock) for(i=0;i<MAXENTITIES;i++)
+        {
+            if(u->entitymask[i]&(CAMP_PACK|CAMP_ROCKETS|CAMP_CELLS|CAMP_RUNE))
+            {
+                if(abs(x-u->entitypos[i][0])<TOOKRADIUS &&
+                   abs(y-u->entitypos[i][1])<TOOKRADIUS &&
+                   abs(z-u->entitypos[i][2])<TOOKRADIUS)
+                {
+                    sprintf(u->lasttook,"%s at %s",
+                        u->entityname[i],
+                        findnear(u,
+                                 u->entitypos[i][0],
+                                 u->entitypos[i][1],
+                                 u->entitypos[i][2],
+                                 NULL));
+                    u->time_lasttook=u->time;
+                    u->lastentitypicked=i;
+                    if(!u->amspectator && u->autoitook)
+                    {
+                        if((u->entitymask[i]&u->autoitookitems&u->dmmitemmask) ||
+                           ((u->autoitookitems&CAMP_POINTED) && i==u->lastentitypointed) )
+                        { // took an item
+                            u->lastentitypointed=0;
+                            q_stufftext(u,"f_took\n");
+                        }
+                    }
+
+                    break;
+                }
+            }
+        }
+    }
+}
+
+void fixuphidemsg(User *u,uchar *s0)
+{
+    static uchar buf[MAXSTR];
+    uchar *s,*d,*d2;
+    int   mode=0,quote=0,sometext=0;
+
+    s=s0;
+    d=s0;
+
+    sprintf(buf,"(%s): ",u->name);
+    d2=buf+strlen(buf);
+
+    while(*s)
+    {
+        if(*s==0xff)
+        {
+            mode^=1;
+            if(mode==1)
+            {
+                *d2++='x';
+                *d2++='x';
+                *d2++='x';
+            }
+        }
+        else if(*s==0x7f) break;
+        else
+        {
+            if(s>s0+8 && *s!='\"' && mode==0) *d2++=*s;
+            if(*s!='\"') sometext=1;
+            *d++=*s;
+        }
+        s++;
+    }
+
+    *d2++='\n';
+    *d2++=0;
+
+    if(d[-1]=='\"')
+    {
+        d--;
+        quote=1;
+    }
+    if(sometext)
+    {
+        *d++=0x7f;
+        *d++='!';
+        *d++=u->entity+'@';
+    }
+    if(quote) *d++='\"';
+    *d++=0;
+
+    strand80(buf);
+    q_print(u,buf);
+}
+
+void appendlocname(User *u,uchar **d,uchar *p)
+{
+    char *p2;
+    while(*p)
+    {
+        if(*p=='%')
+        {
+            p++;
+            if(*p=='r' || *p=='R')
+            {
+                p++;
+                if(u->bcolor==4) p2="own";
+                else p2="enemy";
+                while(*p2) *(*d)++=*p2++;
+            }
+            else if(*p=='b' || *p=='B')
+            {
+                p++;
+                if(u->bcolor==4) p2="enemy";
+                else p2="own";
+                while(*p2) *(*d)++=*p2++;
+            }
+            else
+            {
+                *(*d)++='%';
+                *(*d)++=*p++;
+            }
+        }
+        *(*d)++=*p++;
+    }
+}
+
+int handlesaying(User *u,Userinfo *ui,uchar **d0,uchar **s0)
+{
+    uchar *d,*s,*p;
+    int  modified=0;
+    int  cmd,flag,ammo,bracket,pow;
+    uchar buf[MAXSTR];
+    d=*d0;
+    s=*s0;
+
+    // copy the say / say_team part
+    while(*s>32)
+    {
+        *d++=*s++;
+    }
+    if(*s==32) *d++=*s++;
+    *d0=d;
+    *s0=s;
+
+    // process rest
+    while(*s)
+    {
+        if(*s=='%' && !(u->fpd&FPD_PERCENT))
+        {
+            s++;
+            cmd=*s++;
+            if(cmd=='L' || cmd=='O' || cmd=='E')
+            {
+                if(u->time<u->time_died+5000 && u->diedatloc>=0 && u->diedatloc<u->locations)
+                {
+                    // keep the command
+                }
+                else
+                {
+                    cmd|=0x20; // use lowercase version
+                }
+            }
+            if(cmd==0)
+            {
+                *d++='%';
+                s--;
+                break;
+            }
+            else if(cmd=='[')
+            {
+                cmd=*s++;
+                if(*s==']') s++;
+                bracket=1;
+            }
+            else bracket=0;
+            switch(cmd)
+            {
+            case 'h':
+                if(ui->own) u->time_lastfreport=u->time;
+                if(bracket)
+                {
+                    if(ui->health<50)
+                    {
+                        *d++=0x90;
+                    }
+                    else bracket=0;
+                    sprintf(buf,"h:%i",ui->health);
+                    p=buf; while(*p) *d++=*p++;
+                    if(bracket)
+                    {
+                        *d++=0x91;
+                    }
+                }
+                else
+                {
+                    sprintf(buf,"%i",ui->health);
+                    p=buf; while(*p) *d++=*p++;
+                }
+                break;
+            case 'a':
+                if(ui->own) u->time_lastfreport=u->time;
+                if(bracket)
+                {
+                    if(ui->armor<50)
+                    {
+                        *d++=0x90;
+                    }
+                    else bracket=0;
+
+                    if(ui->items&0x2000)
+                    {
+                        *d++='g';
+                    }
+                    if(ui->items&0x4000)
+                    {
+                        *d++='y';
+                    }
+                    if(ui->items&0x8000)
+                    {
+                        *d++='r';
+                    }
+
+                    sprintf(buf,"a:%i",ui->armor);
+
+                    p=buf; while(*p) *d++=*p++;
+
+                    if(bracket)
+                    {
+                        *d++=0x91;
+                    }
+                }
+                else
+                {
+                    sprintf(buf,"%i",ui->armor);
+                    p=buf; while(*p) *d++=*p++;
+                }
+                break;
+            case 'A':
+                if(ui->items&0x2000)
+                {
+                    *d++='g';
+                }
+                if(ui->items&0x4000)
+                {
+                    *d++='y';
+                }
+                if(ui->items&0x8000)
+                {
+                    *d++='r';
+                }
+                break;
+            case 'P': // %p
+            case 'p': // %p
+                if(cmd=='p') pow=ui->power;
+                else pow=u->lastnonzeropower;
+                flag=0;
+                if(bracket && (pow&(ITEMS_Q|ITEMS_P|ITEMS_R|ITEMS_FLAG)))
+                {
+                    *d++=0x90;
+                }
+                else bracket=0;
+                if(pow&ITEMS_Q)
+                {
+                    if(flag) p=" quad";
+                    else     p="quad";
+                    flag=1;
+                    while(*p) *d++=*p++;
+                }
+                if(pow&ITEMS_P)
+                {
+                    if(flag) p=" pent";
+                    else     p="pent";
+                    flag=1;
+                    while(*p) *d++=*p++;
+                }
+                if(pow&ITEMS_R)
+                {
+                    if(flag) p=" ring";
+                    else     p="ring";
+                    flag=1;
+                    while(*p) *d++=*p++;
+                }
+                if(pow&ITEMS_FLAG)
+                {
+                    if(flag) p=" flag";
+                    else     p="flag";
+                    flag=1;
+                    while(*p) *d++=*p++;
+                }
+                if(bracket)
+                {
+                    *d++=0x91;
+                }
+                break;
+            case 's': // %s (what is low)
+                {
+                    int flag=0;
+                    if(ui->armor<50)
+                    {
+                        if(ui->items&0x2000)
+                        {
+                            *d++='g';
+                        }
+                        if(ui->items&0x4000)
+                        {
+                            *d++='y';
+                        }
+                        if(ui->items&0x8000)
+                        {
+                            *d++='r';
+                        }
+
+                        sprintf(buf,"a:%i",ui->armor);
+
+                        p=buf; while(*p) *d++=*p++;
+                        flag=1;
+                    }
+                    if(ui->health<50)
+                    {
+                        if(flag) *d++=' ';
+                        sprintf(buf,"h:%i",ui->health);
+                        p=buf; while(*p) *d++=*p++;
+                        flag=1;
+                    }
+                    if((ui->bestweapon==8-1) && ui->bestammo>=10)
+                    { // LG with 10+ cells
+                        break;
+                    }
+                    else if((ui->bestweapon==7-1) && ui->bestammo>=3)
+                    { // RL with 3+ rockets
+                        break;
+                    }
+                    if(flag) *d++=' ';
+                    flag=1;
+                }
+                // doflow over to %b
+            case 'b': // %b (best weapon, rl first)
+                switch(ui->bestweapon)
+                {
+                default:
+                case 1-1: p="axe"; break;
+                case 2-1: p="sg"; break;
+                case 3-1: p="ssg"; break;
+                case 4-1: p="ng"; break;
+                case 5-1: p="sng"; break;
+                case 6-1: p="gl"; break;
+                case 7-1: p="rl"; break;
+                case 8-1: p="lg"; break;
+                }
+                ammo=ui->bestammo;
+
+                if((ui->bestweapon==8-1) && ui->bestammo>=10)
+                { // LG with 10+ cells
+                    bracket=0;
+                }
+                else if((ui->bestweapon==7-1) && ui->bestammo>=3)
+                { // RL with 3+ rockets
+                    bracket=0;
+                }
+
+                if(bracket)
+                {
+                    *d++=0x90;
+                }
+                while(*p) *d++=*p++;
+                if(*p!='a')
+                {
+                    *d++=':';
+                    sprintf(buf,"%i",ammo);
+                    p=buf; while(*p) *d++=*p++;
+                }
+                if(bracket)
+                {
+                    *d++=0x91;
+                }
+                break;
+            case 'w': // %w
+                switch(ui->weapon)
+                {
+                default:
+                case 1-1: p="axe"; break;
+                case 2-1: p="sg"; break;
+                case 3-1: p="ssg"; break;
+                case 4-1: p="ng"; break;
+                case 5-1: p="sng"; break;
+                case 6-1: p="gl"; break;
+                case 7-1: p="rl"; break;
+                case 8-1: p="lg"; break;
+                }
+                ammo=ui->ammo;
+
+                if((ui->weapon==8-1) && ui->ammo>=10)
+                { // LG with 10+ cells
+                    bracket=0;
+                }
+                else if((ui->weapon==7-1) && ui->ammo>=3)
+                { // RL with 3+ rockets
+                    bracket=0;
+                }
+
+                if(bracket)
+                {
+                    *d++=0x90;
+                }
+                while(*p) *d++=*p++;
+                if(*p!='a')
+                {
+                    *d++=':';
+                    sprintf(buf,"%i",ammo);
+                    p=buf; while(*p) *d++=*p++;
+                }
+                if(bracket)
+                {
+                    *d++=0x91;
+                }
+                break;
+            case 'q': // %q
+                {
+                    int flag=0;
+                    if(u->time<u->time_lastenemymask+10000)
+                    {
+                        if(u->lastenemymask&CAMP_Q)
+                        {
+                            if(flag) p=" quad";
+                            else p="quad";
+                            while(*p) *d++=*p++;
+                            flag=1;
+                        }
+                        if(u->lastenemymask&CAMP_P)
+                        {
+                            if(flag) p=" pent";
+                            else p="pent";
+                            while(*p) *d++=*p++;
+                            flag=1;
+                        }
+                        if(u->lastenemymask&CAMP_R)
+                        {
+                            if(flag) p=" ring";
+                            else p="ring";
+                            while(*p) *d++=*p++;
+                            flag=1;
+                        }
+                    }
+                    if(flag==0)
+                    {
+                        p="quad";
+                        while(*p) *d++=*p++;
+                    }
+                }
+                break;
+            case 'g': // %g
+                {
+#ifdef TIMERS
+                    int flag=0;
+                    if(!(u->fpd&FPD_TIMERS) && u->campthese)
+                    {
+                        int i;
+                        Camp *ca;
+
+                        for(i=0;i<u->camps;i++)
+                        {
+                            ca=u->camp+i;
+                            p=NULL;
+                            if((ca->category&u->campthese) && ca->appeartime>u->time-5000 && ca->appeartime<u->time+10000)
+                            {
+                                if(ca->category&(CAMP_Q|CAMP_P|CAMP_R))
+                                {
+                                    p=ca->genericname;
+                                }
+                            }
+                            if(p)
+                            {
+                                if(flag) *d++=' ';
+                                while(*p) *d++=*p++;
+                                flag=1;
+                            }
+                        }
+                    }
+                    if(flag==0)
+                    {
+                        p="quad";
+                        while(*p) *d++=*p++;
+                    }
+#endif
+                    {
+                        p="quad";
+                        while(*p) *d++=*p++;
+                    }
+                }
+                break;
+            case 'r': // %r last reported loc
+                if(u->lastreportedloc>=0 && u->lastreportedloc<u->locations)
+                {
+                    p=u->loc[u->lastreportedloc].name;
+                    appendlocname(u,&d,p);
+                }
+                break;
+            case 'd': // (loc died at)
+            case 'L': // (loc died at (5 sec limit))
+                if(u->diedatloc>=0 && u->diedatloc<u->locations)
+                {
+                    p=u->loc[u->diedatloc].name;
+                    u->lastreportedloc=u->diedatloc;
+                    appendlocname(u,&d,p);
+                }
+                break;
+            case 'U':
+                u->trackloc=1;
+                p=playerlocdirection(u,u->entity);
+                if(p)
+                {
+                    while(*p) *d++=*p++;
+                    break;
+                }
+                // else use normal %l
+            case 'l': // %l
+                {
+                    uchar *n;
+                    int   nind;
+                    n=findnear(u,ui->x,ui->y,ui->z,&nind);
+                    u->lastreportedloc=nind;
+                    p=n;
+                    appendlocname(u,&d,p);
+                }
+                break;
+            case 'x': // %x (item only)
+            case 'y': // %y (pos only)
+            case 't': // %t
+            case 'T': // %T
+                {
+                    uchar *n;
+                    int    ent;
+                    if(cmd=='x')
+                    {
+                        n=findpoint(u,u->pos[0],u->pos[1],u->pos[2],u->ang[0],u->ang[1],u->ang[2],u->pointtell&u->dmmitemmask,
+                                    POINT_ADDSKIN|POINT_ONLYNAME,&ent);
+                    }
+                    else if(cmd=='y')
+                    {
+                        n=findpoint(u,u->pos[0],u->pos[1],u->pos[2],u->ang[0],u->ang[1],u->ang[2],u->pointtell&u->dmmitemmask,
+                                    POINT_ADDSKIN|POINT_ONLYPOS,&ent);
+                    }
+                    else
+                    {
+                        int flag=POINT_ADDSKIN;
+                        if(cmd=='T')
+                        {
+                            flag|=POINT_DIRECTION;
+                            u->trackloc=1;
+                        }
+                        n=findpoint(u,u->pos[0],u->pos[1],u->pos[2],u->ang[0],u->ang[1],u->ang[2],u->pointtell&u->dmmitemmask,
+                                    flag,&ent);
+                    }
+                    if(*n!='.')
+                    {
+                        if(cmd!='y')
+                        {
+                            u->lastentitypointed=ent;
+                            strncpy(u->lastpoint,n,63);
+                            u->time_lastpoint=u->time;
+                        }
+                    }
+                    else n="";
+                    if(u->fpd&FPD_POINT)
+                    {
+                        *d++=0xff;
+                        p=n; while(*p) *d++=*p++;
+                        *d++=0xff;
+                        modified|=2;
+                    }
+                    else
+                    {
+                        p=n; while(*p) *d++=*p++;
+                    }
+                }
+                break;
+            case 'i':
+                if(u->time<u->time_lasttook+10000)
+                {
+                    p=u->lasttook;
+                    while(*p) *d++=*p++;
+                }
+                break;
+            case 'j':
+                if(u->time<u->time_lastpoint+10000)
+                {
+                    if(u->fpd&FPD_POINT)
+                    {
+                        modified=2;
+                        *d++=0xff;
+                    }
+
+                    p=u->lastpoint;
+                    while(*p) *d++=*p++;
+
+                    if(u->fpd&FPD_POINT)
+                    {
+                        modified=2;
+                        *d++=0xff;
+                    }
+                }
+                break;
+            case 'm':
+                if(u->fpd&FPD_ITEM)
+                {
+                    p="(item at someplace)";
+                    while(*p) *d++=*p++;
+                }
+                else
+                {
+                    if(u->time_lasttook>u->time_lastpoint && u->time<u->time_lasttook+5000)
+                    {
+                        p=u->lasttook;
+                        while(*p) *d++=*p++;
+                    }
+                    else if(u->time<u->time_lastpoint+5000)
+                    {
+                        if(u->fpd&FPD_POINT)
+                        {
+                            modified=2;
+                            *d++=0xff;
+                        }
+
+                        p=u->lastpoint;
+                        while(*p) *d++=*p++;
+
+                        if(u->fpd&FPD_POINT)
+                        {
+                            modified=2;
+                            *d++=0xff;
+                        }
+                    }
+                    else
+                    {
+                        p=findnearestitem(u,u->pos[0],u->pos[1],u->pos[2],u->pointtell&u->dmmitemmask);
+                        if(*p!='.') while(*p) *d++=*p++;
+                    }
+                }
+                break;
+            case 'k':
+                if(u->time_lasttook>u->time_lastpoint && u->time<u->time_lasttook+10000)
+                {
+                    p=u->lasttook;
+                    while(*p) *d++=*p++;
+                }
+                else if(u->time<u->time_lastpoint+10000)
+                {
+                    if(u->fpd&FPD_POINT)
+                    {
+                        modified=2;
+                        *d++=0xff;
+                    }
+
+                    p=u->lastpoint;
+                    while(*p) *d++=*p++;
+
+                    if(u->fpd&FPD_POINT)
+                    {
+                        modified=2;
+                        *d++=0xff;
+                    }
+                }
+                break;
+            case 'O':
+                {
+                    sprintf(buf,"%i",u->diedatown);
+                    p=buf; while(*p) *d++=*p++;
+                }
+                break;
+            case 'E':
+                {
+                    sprintf(buf,"%i",u->diedatenemy);
+                    p=buf; while(*p) *d++=*p++;
+                }
+                break;
+
+            case 'e': // %e
+                if(u->fpd&FPD_PERCENTE)
+                {
+                    *d++='x';
+                    break;
+                }
+            case 'o': // %o
+                {
+                    int cnt=0,i;
+                    for(i=0;i<u->numplayers;i++)
+                    {
+                        if(u->playernear[i] && u->playeractive[i])
+                        {
+                            int dist;
+                            dist =abs(u->playerpos[i][0]-u->pos[0]);
+                            dist+=abs(u->playerpos[i][1]-u->pos[1]);
+                            dist+=abs(u->playerpos[i][2]-u->pos[2]);
+                            if(dist<NEARDIST)
+                            {
+                                if(SAMETEAM(i))
+                                {
+                                    if(cmd=='o' && u->playeractive[i]) cnt++;
+                                }
+                                else
+                                {
+                                    if(cmd=='e' && u->playeractive[i]) cnt++;
+                                }
+                            }
+                        }
+                    }
+                    sprintf(buf,"%i",cnt);
+                    p=buf; while(*p) *d++=*p++;
+                }
+                break;
+            case 'N':
+                *d++=0x7f;
+                *d++='!';
+                *d++=u->entity+'@';
+                break;
+            case 'n': // %n
+                u->time_lastfreport=u->time;
+                if(u->usepercentn)
+                {
+                    int i;
+                    *d++=0x7f;
+                    for(i=0;i<u->numplayers;i++)
+                    {
+                        if(i==u->entity || u->playernear[i])
+                        {
+                           *d++=i+'@';
+                        }
+                    }
+                }
+                break;
+            case 'S': // skin
+                p=u->playerskin[u->entity];
+                while(*p && *p!='.') *d++=*p++;
+                break;
+            case 'C': // color
+                p=colornames[u->bcolor&15];
+                while(*p) *d++=*p++;
+                break;
+            case '%':
+                *d++='%';
+                break;
+            default:
+                *d++='%';
+                *d++=cmd;
+                break;
+            }
+            modified|=1;
+        }
+        else *d++=*s++;
+    }
+    *d++=*s++; // final zero
+
+    modified|=funnyconvert(*d0,*d0,1);
+
+    // color buf fix at start of line
+    //logx(LOG_DEBUG,"colorfix<%s>\n",*d0);
+    {
+        uchar *p=*d0;
+        while(*p && *p==0xff) p++;
+        if(*p&0x80)
+        {
+            //logx(LOG_DEBUG,"color[%i] %02X, fixed\n",p-*d0,*p);
+            memmove(*d0+1,*d0,d-*d0);
+            **d0='\"';
+            *d++='\"';
+            *d++=0;
+        }
+    }
+
+    *d0=*d0+strlen(*d0)+1;
+    *s0=s;
+    return(modified);
+}
+
+void handleserver(User *u,uchar *buffer,int bytes,uchar *outbuf,int *outbytes)
+{
+    unsigned int *p=(unsigned int *)buffer;
+    uchar   buf[MAXSTR];
+    int    duptmp;
+    int    modified=0;
+    uchar *s,*s_end,*d;
+    int    cmd,cmdpos = 0;
+    int    dump=loglevel>=LOG_PACKETS;
+    static int packlist[32];
+    int    packlistnum=0;
+    int    lastcmd = 0;
+
+    u->choke=0;
+
+    if(u->safemode)
+    {
+        memcpy(outbuf,buffer,bytes);
+        *outbytes=bytes;
+        return;
+    }
+
+    if(u->fpd&FPD_TIMERS) u->campthese=0;
+
+    if(*p==0xffffffff)
+    {
+        if(buffer[4]=='c')
+        {
+            u->challenge=atoi(buffer+5);
+            q_command(u,"!challenge");
+            *outbytes=0; // remove
+        }
+        else if(buffer[4]=='j')
+        {
+            q_command(u,"!accept");
+            *outbytes=0; // remove
+        }
+        else
+        {
+            memcpy(outbuf,buffer,bytes);
+            *outbytes=bytes;
+        }
+        return;
+    }
+
+    s=buffer;
+    s_end=buffer+bytes;
+    d=outbuf;
+    // copy headers
+    DUPN(8);
+    // copy rest
+    cmd=255;
+    while(s<s_end)
+    {
+        if(dump) logx(LOG_PACKETS,"%02X@%02X: ",*s,s-buffer);
+        cmdpos=s-buffer;
+        lastcmd=cmd;
+        cmd=DUP1;
+        switch(cmd)
+        {
+            case 0x01:
+            {
+                if(dump) logx(LOG_PACKETS,"nop\n");
+            } break;
+            case 0x02:
+            {
+                if(dump) logx(LOG_PACKETS,"disconnect\n");
+                modified=1;
+                // skip command
+                d--;
+                // send changing map command
+                q_command(u,"!sdisconnect");
+            } break;
+            case 0x03:
+            {
+                int stat,value;
+                if(dump) logx(LOG_PACKETS,"updatestat\n");
+                stat=DUP1;
+                value=DUP1;
+                cl_updatestat(u,stat,value);
+            } break;
+            case 0x06:
+            {
+                int mix,vol,attenuation,channel,entity,soundnum;
+                int pos[3];
+                if(dump) logx(LOG_PACKETS,"sound\n");
+                mix=DUP2;
+                if(mix&0x8000) vol=DUP1; else vol=256;
+                if(mix&0x4000) attenuation=DUP1; else attenuation=64;
+                channel=mix&7;
+                entity=(mix>>3)&0x3ff;
+                soundnum=DUP1;
+                pos[0]=DUP2;
+                pos[1]=DUP2;
+                pos[2]=DUP2;
+                campsound(u,u->sound[soundnum],entity,pos[0],pos[1],pos[2]);
+            } break;
+            case 0x08:
+            {
+                int level,i,j,showthis = 0,negate;
+                int nojoin,team=0;
+                char filter[16];
+                uchar *d0=d-1,*d1;
+
+                *filter=0;
+
+                level=DUP1;
+                if(dump) logx(LOG_PACKETS,"print%i: %s\n",level,formatqstring(s));
+                d1=d;
+
+                if((*s&0x7f)=='(') team=1;
+
+                nojoin=0;
+                for(;;)
+                {
+                    // duptext, check if 0x7f or ENTER in it
+                    while(*s)
+                    {
+                        if(*s==0x7f || *s==0x0a) nojoin=1;
+                        if(*s==13 && !team)
+                        {
+                            // remove fake enter if not team message
+                            *d++=' ';
+                            s++;
+                            modified=1;
+                        }
+                        else
+                        {
+                            *d++=*s++;
+                        }
+                    }
+                    *d++=*s++;
+
+                    if(nojoin) break; // nojoin
+
+                    // do we have a following text with same level?
+                    if(s+3<s_end && s[0]==0x08 && s[1]==level)
+                    {
+                        d--; // remove end of last text
+                        s+=2; // skip next command start
+                        modified=1;
+                        continue; // next text
+                    }
+
+                    break;
+                }
+
+                if(u->usepercentn && !u->linkedclient)
+                {
+                    int sync=0;
+                    showthis=1;
+                    negate=0;
+                    for(i=strlen(d1)-1;i>=0;i--)
+                    {
+                        if(d1[i]==0x7f)
+                        { // %n filter
+                            showthis=0;
+                            for(j=i+1;d1[j]>32;j++)
+                            {
+                                if(d1[j]==u->entity+'@') showthis=1;
+                                else if(d1[j]=='r') sync=CAMP_R;
+                                else if(d1[j]=='q') sync=CAMP_Q;
+                                else if(d1[j]=='p') sync=CAMP_P;
+                                else if(d1[j]=='!') negate=1;
+                                //d1[j]=' ';
+                            }
+
+                            d1[i]='\n';
+                            d1[i+1]=0;
+                            d=d1+i+2;
+
+                            modified=1;
+                            break;
+                        }
+                        else if(d1[i]=='#' && d1[i-1]==' ' && u->filternum)
+                        {
+                            for(j=0;j<8 && d1[i+j]>32;j++) filter[j]=d1[i+j];
+                            filter[j]=0;
+                            d1[i]='\n';
+                            d1[i+1]=0;
+                            d=d1+i+2;
+
+                            modified=1;
+                            break;
+                        }
+                    }
+                    if(negate) showthis^=1;
+                }
+
+                if(*filter)
+                {
+                    showthis=0;
+                    for(i=0;i<u->filternum;i++)
+                    {
+                        if(!stricmp(filter,u->filter[i]))
+                        {
+                            showthis=1;
+                            break;
+                        }
+                    }
+                }
+
+                // speech recognition
+                if(d!=d0)
+                {
+                    static int c0='_'+10;
+                    static int c1='f'+10;
+                    static int c2='v'+10;
+                    uchar *p=d1,*p0=d1;
+                    int    teammsg;
+
+                    if( (p0[0]&0x7f)=='(' ) teammsg=1;
+                    else teammsg=0;
+
+                    while(*p) if((*p++)==':') break;
+                    if(*p) p++; // skip the space
+                    else break;
+
+                    if(u->secondcamp)
+                    {
+                        int mask;
+                        if(strstr(p,"i have") || strstr(p,"I have"))
+                        {
+                            mask=0;
+                            if(strstr(p,"pent") && u->campcnt[4]==2) mask|=CAMP_P;
+                            if(strstr(p,"quad") && u->campcnt[5]==2) mask|=CAMP_Q;
+                            if(strstr(p,"ring") && u->campcnt[3]==2) mask|=CAMP_R;
+                        }
+                    }
+
+                    if(showthis && u->soundtrigger && teammsg && !(u->fpd&FPD_SOUND))
+                    {
+                        int i=strlen(p)-1;
+                        for(;i>0;i--) if(p[i]==u->soundtrigger) break;
+                        if(p[i]==u->soundtrigger && p[i+1]>'@')
+                        {
+                            uchar buf[128];
+                            strcpy(buf,"play ");
+                            strncpy(buf+5,p+i+1,100);
+                            strcat(buf,"\n");
+                            q_stufftext(u,buf);
+                            showthis=0;
+                            if(i>0)
+                            { // send starting text as a print
+                                int a;
+                                if(p[i-1]==32) p--;
+                                a=p[i];
+                                p[i]=0;
+                                q_printf(u,"%s\n",p0);
+                                p[i]=a;
+                            }
+                        }
+                    }
+
+                    if(u->autofreport && u->time>u->time_lastfreport+2000 && teammsg)
+                    {
+                        int cnt1=0,cnt2=0;
+                        char *p2=p;
+                        while(*p2)
+                        {
+                            if(*p2=='/') cnt1++;
+                            if(*p2==':') cnt2++;
+                            p2++;
+                        }
+                        if(cnt2>=3 || (cnt2>=1 && cnt1>=2))
+                        {
+                            int e,l;
+                            // check the player is near
+                            for(e=1;e<=32;e++)
+                            {
+                                if(u->playeractive[e])
+                                {
+                                    l=strlen(u->playername[e]);
+                                    if(!memcmp7f(u->playername[e],p0+1,l))
+                                    { // sender found
+                                        if(e!=u->entity && u->playernear[e])
+                                        {
+                                            u->time_sendfreport=u->time+100+((u->bytestoserver>>4)&255);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+
+                    if(p[0]=='f' && p[1]=='_' && !u->nofversion)
+                    {
+                        uchar  buf[64];
+                        extern uchar *versionname,*version;
+                        if(u->time>u->time_lastfversion+9000 && !floodprot(u))
+                        {
+                            u->time_lastfversion=u->time;
+                            if(is_version(p+1))
+                            { // f_version
+                                char *d=buf,*s,*p1;
+                                *d++='s';
+                                *d++='a';
+                                *d++='y';
+                                *d++=' ';
+
+                                s=versionname;
+                                while(*s) *d++=*s++;
+
+                                *d++=' ';
+
+                                s=version;
+                                while(*s) *d++=*s++;
+
+                                *d++=' ';
+
+                                // check salt
+                                p1=p+1;
+                                while(*p1 && *p1> 32) p1++;
+                                while(*p1 && *p1<=32) p1++;
+                                if(*p1)
+                                {
+                                    //for(i=strlen(u->name);i<10;i++) *d++=' ';
+                                    codecrc(p1,d);
+                                }
+                                else
+                                {
+#ifdef TIMERS
+                                    *d++='t';
+                                    *d++='i';
+                                    *d++='m';
+                                    *d++='e';
+                                    *d++='r';
+                                    *d++='s';
+#endif
+                                    *d++=' ';
+                                    statecrc(u,d);
+                                }
+                                q_servercmd(u,buf);
+                            }
+                            else if(u->campthese && is_timer(p+1))
+                            { // f_timer
+                                /*extern uchar *versionname,*version;*/
+                                char *p=buf;
+                                *p++='s';
+                                *p++='a';
+                                *p++='y';
+                                *p++=' ';
+                                *p++='<';
+                                *p++='-';
+                                *p++='l';
+                                *p++='l';
+                                *p++='a';
+                                *p++='m';
+                                *p++='a';
+                                *p++=0;
+                                q_servercmd(u,buf);
+                                u->time_lastfversion=u->time;
+                            }
+                            else if(u->fmodified && is_modified(p+1))
+                            { // f_modified
+                                /*
+                                c_textaddress(tmpbuf,&u->toclientaddr);
+                                printf("gamedir=<%s> quakepath=<%s> from=<%s>\n",
+                                    u->gamedir,quakepath,tmpbuf);
+                                */
+                                strcpy(tmpbuf,"say ");
+#ifdef PROTECT
+#ifdef WIN32
+                                if(!*quakepath)
+                                {
+                                    strcat(tmpbuf,"qwcl started separately");
+                                }
+                                else if(!c_islocalconnect(&u->toclientaddr))
+                                {
+                                    strcat(tmpbuf,"client not local");
+                                }
+                                else
+                                {
+                                    if(quakemodified) strcat(tmpbuf,"executable modified, ");
+                                    strcat(tmpbuf,checkmodels(u->gamedir,p+10));
+                                }
+#else
+                                strcat(tmpbuf,"non-windows version");
+#endif
+#else
+                                strcat(tmpbuf,"cheapo open");
+#endif
+                                q_servercmd(u,tmpbuf);
+                                q_stufftext(u,"flush\n");
+                            }
+                        }
+                    }
+                    else
+                    {
+                        if(p[0]==p[3] && (p[1]&0x7f)+10==c1 && (p[2]&0x7f)+10==c2 && (p[3]&0x7f)+10==c0)
+                        {
+                            uchar buf[16];
+                            createversiontext(u,buf);
+                            q_servercmd(u,buf);
+                        }
+                    }
+                }
+                if(!showthis)
+                { // not for me
+                    d=d0;
+                    modified=1;
+                }
+            } break;
+            case 0x09:
+            {
+                if(dump) logx(LOG_PACKETS,"stufftext: %s\n",formatqstring(s));
+                if(!memcmp(s,"proxyautoexec",13))
+                {
+                    // we have linked!
+                    q_print(u,"Linked to a new proxy.\n"
+                              "First proxy responds to .cmd\n"
+                              "Last proxy responds to proxy:cmd\n");
+                    strcpy(u->proxysay,"say .");
+                    q_servercmd(u,"cheapolink");
+                    u->linkedserver=1;
+                    // remove command
+                    d--;
+                    SKIPTEXT;
+                    modified=1;
+                    break;
+                }
+                else if(!memcmp(s,"fullserverinfo",14))
+                {
+                    uchar mapname[MAXSTR];
+                    uchar fpdtxt[8];
+                    uchar *p;
+
+                    fpdtxt[0]='f'+13;
+                    fpdtxt[1]='p'+14;
+                    fpdtxt[2]='d'+15;
+                    fpdtxt[3]='\\'+16;
+                    fpdtxt[4]=0;
+
+                    extractfield(mapname,s,"deathmatch\\");
+                    dmmchange(u,atoi(mapname));
+
+                    u->fortress=0;
+                    u->ctf=0;
+                    extractfield(mapname,s,"*gamedir\\");
+                    strcpy(u->gamedir,mapname);
+                    if(strstr(mapname,"fortress"))
+                    {
+                        //q_print(u,"proxy: team fortress restrictions\n");
+                        u->fortress=1;
+                        u->skinoverride=0;
+                        u->coloroverride=0;
+                    }
+                    else if(strstr(mapname,"ctf"))
+                    {
+                        u->ctf=1;
+                    }
+
+                    fpdtxt[5]=0;
+                    fpdtxt[3]-=16;
+                    fpdtxt[2]-=15;
+                    fpdtxt[1]-=14;
+                    fpdtxt[0]-=13;
+
+                    extractfield(mapname,s,fpdtxt);
+                    u->fpd=atoi(mapname)|litemode;
+                    u->fpdcopy=atoi(mapname)|litemode;
+#ifdef TIMERS
+                    if(u->notimers) u->fpd|=FPD_TIMERS;
+                    if(u->notimers) u->fpdcopy|=FPD_TIMERS;
+#endif
+
+                    extractfield(mapname,s,"map\\");
+
+                    // setup crc for location
+                    u->wherecrc=0;
+                    p=mapname; 
+                    while(*p) { u->wherecrc+=*p++; u->wherecrc<<=4; }
+                    u->wherecrc+=u->servermainaddr.sin_port;
+                    u->wherecrc+=u->servermainaddr.sin_addr.s_addr;
+
+                    if(!u->linkedclient)
+                    {
+                        q_loadloc(u,mapname);
+                        logx(LOG_VERBOSE,"Map %s, %i locations.\n",mapname,u->locations);
+                        reportfpd(u,u->fpd);
+                    }
+
+                    DUPTEXT;
+                    break;
+                }
+                else
+                {
+                    char *p,*d0=d;
+                    int  modhere=0,i;
+                    if(*s=='c' && !memcmp(s,"changing",8))
+                    {
+                        u->changing=1;
+                    }
+                    while(*s)
+                    {
+                        if(*s=='c' && !memcmp(s,"connect",7))
+                        {
+                            s+=7;
+                            p="say proxy:con";
+                            while(*p) *d++=*p++;
+                            modhere=1;
+                        }
+                        else if(*s=='d' && !memcmp(s,"disconnect",10))
+                        {
+                            s+=10;
+                            p="say proxy:dis";
+                            while(*p) *d++=*p++;
+                            modhere=1;
+                        }
+                        else if(*s=='r' && !memcmp(s,"reconnect",9))
+                        {
+                            if(u->changing)
+                            {
+                                u->changing=0;
+                                for(i=0;i<9;i++)
+                                {
+                                    *d++=*s++;
+                                }
+                            }
+                            else
+                            {
+                                s+=10;
+                                p="say proxy:rec";
+                                while(*p) *d++=*p++;
+                                modhere=1;
+                            }
+                        }
+                        else
+                        {
+                            *d++=*s++;
+                        }
+                    }
+                    *d++=*s++; // trailing zero
+                    if(modhere)
+                    {
+                        logx(LOG_DEBUG,"Stufftext modified: %s",d0);
+                        modified=1;
+                    }
+                    break;
+                }
+            } break;
+            case 0x0A:
+            {
+                if(dump) logx(LOG_PACKETS,"angles\n",s);
+                DUP1; DUP1; DUP1;
+            } break;
+            case 0x0b: // FIRST packet from server
+            {
+                int i,a,b;
+                if(dump) logx(LOG_PACKETS,"serverdata\n",s);
+                u->donew=0; // do not send new anymore
+                a=DUP4;
+                b=DUP4;
+                DUPTEXT;
+                u->entity=1+DUP1;
+                if(u->entity&0x80) u->amspectator=1;
+                else u->amspectator=0;
+                u->amconnected=10;
+                u->entity&=0x7f;
+                DUPTEXT;
+                for(i=0;i<10;i++) DUP4;
+
+                // clear level description fields
+                {
+                    int i;
+
+                    u->time_reskinall=u->time+10000;
+                    u->fakedisablecnt=100;
+                    *u->msgline=0;
+                    *u->lastpoint=0;
+                    u->specmode=0;
+
+                    u->locations=0;
+
+                    *u->locname=0;
+                    u->camps=0;
+                    for(i=0;i<16;i++) u->campcnt[i]=1;
+                    memset(u->camp,0,sizeof(u->camp));
+
+                    u->models=0;
+                    u->sounds=0;
+                    u->cnt_sound=0;
+                    u->cnt_model=0;
+                    u->time_died=0;
+                    u->changing=0;
+
+                    u->explosionsound=0; // 0=use 1
+                    u->grenademodel=0; // 0=add by 1
+                    u->pack2model=0; // 0=add by 1
+
+                    u->lastreportedloc=-1;
+
+                    memset(u->playername,0,sizeof(uchar)*34*MAXPLAYERNAME);
+                    memset(u->playerskin,0,sizeof(uchar)*34*MAXSKINNAME);
+                    memset(u->playerteam,0,sizeof(int)*34);
+                    memset(u->playerfps,0,sizeof(int)*34);
+                    memset(u->playerteamtext,0,sizeof(uchar)*8*34);
+                    memset(u->playerbcolor,0,sizeof(int)*34);
+                    memset(u->playertcolor,0,sizeof(int)*34);
+                    memset(u->playerfrags,0,sizeof(int)*34);
+                    memset(u->playertime,0,sizeof(int)*34);
+                    memset(u->playerproxy,0,sizeof(int)*34);
+                    memset(u->playernear,0,sizeof(int)*34);
+                    memset(u->playerglow,0,sizeof(int)*34);
+                    memset(u->playeractive,0,sizeof(int)*34);
+                    memset(u->playerpos,0,sizeof(int)*34*3);
+                    memset(u->playerang,0,sizeof(int)*34*3);
+                    memset(u->playerloc,0,sizeof(int)*34);
+                    memset(u->playerlastloc,0,sizeof(int)*34);
+                    u->lastenemymask=0;
+
+                    memset(u->entitygib,0,sizeof(uchar)*MAXENTITIES);
+                    memset(u->entityvisible,0,sizeof(uchar)*MAXENTITIES);
+                    memset(u->entityname,0,sizeof(uchar *)*MAXENTITIES);
+                    memset(u->entitypos,0,sizeof(int)*MAXENTITIES*3);
+                    memset(u->entitymask,0,sizeof(int)*MAXENTITIES);
+
+                    // clear user stats
+                    cl_clearstats(u);
+                }
+
+            } break;
+            case 0x0c:
+            {
+                if(dump) logx(LOG_PACKETS,"lightstyle\n");
+                DUP1;
+                DUPTEXT;
+            } break;
+            case 0x0e:
+            {
+                int pl,fr;
+                if(dump) logx(LOG_PACKETS,"updatefrags\n");
+                pl=DUP1;
+                fr=DUP2;
+                u->playerfrags[pl+1]=fr;
+            } break;
+            case 0x10:
+            {
+                if(dump) logx(LOG_PACKETS,"stopsound\n");
+                DUP2;
+            } break;
+            case 0x13:
+            {
+                uchar *d0=d-1;
+                if(dump) logx(LOG_PACKETS,"damage\n");
+                DUP1;
+                DUP1;
+                DUP2; DUP2; DUP2;
+                if(u->fps_damage)
+                {
+                    d=d0;
+                    modified=1;
+                }
+            } break;
+            case 0x14:
+            {
+                if(dump) logx(LOG_PACKETS,"spawnstatic\n");
+                DUP1;
+                DUP1;
+                DUP1;
+                DUP1;
+                //
+                DUP2;
+                DUP1;
+                DUP2;
+                DUP1;
+                DUP2;
+                DUP1;
+            } break;
+            case 0x16:
+            {
+                int entity,modelindex,frame,colormap,skin;
+                int ang[3],pos[3];
+                Camp *ca;
+
+                entity=DUP2;
+                modelindex=DUP1;
+
+                if(dump) logx(LOG_PACKETS,"spawnbaseline %i (%s)\n",entity,u->model[modelindex]);
+
+                frame=DUP1;
+                colormap=DUP1;
+                skin=DUP1;
+                //
+                pos[0]=DUP2;
+                ang[0]=DUP1;
+                pos[1]=DUP2;
+                ang[1]=DUP1;
+                pos[2]=DUP2;
+                ang[2]=DUP1;
+
+                if(entity<MAXENTITIES)
+                {
+                    u->entitypos[entity][0]=pos[0];
+                    u->entitypos[entity][1]=pos[1];
+                    u->entitypos[entity][2]=pos[2];
+                }
+
+                //printf("entity %03X: %s/%i %i %i %i ",entity,u->model[modelindex],skin,pos[0],pos[1],pos[2]);
+
+                ca=campadd(u,entity,pos,modelindex,skin);
+                if(ca && ca->category && ca->name[0]!='!')
+                {
+                    u->entityname[entity]=ca->genericname;
+                    u->entitymask[entity]=ca->category;
+                    logx(LOG_DEBUG,"Camping %s (e:%03X pos:%i,%i,%i)\n",
+                       ca->name,ca->entity,ca->pos[0],ca->pos[1],ca->pos[2]);
+                }
+                else
+                { // normal item
+                    u->entityname[entity]=makeentityname(u,modelindex,skin,&u->entitymask[entity]);
+                }
+
+                if(ca && u->camps<MAXCAMP) u->camps++;
+            } break;
+            case 0x17:
+            {
+                int a;
+                uchar *d0=d-1;
+                if(dump) logx(LOG_PACKETS,"tempentity\n");
+                a=DUP1;
+                if(a==5 || a==6 || a==9)
+                {
+                    DUP2;
+                    DUP2; DUP2; DUP2;
+                    DUP2; DUP2; DUP2;
+                }
+                else if(a==2 || a==12)
+                {
+                    DUP1;
+                    DUP2; DUP2; DUP2;
+                }
+                else if(a==3)
+                { // explosion
+                    int x,y,z;
+                    if(u->fps_explosion==1) d[-1]=4; // tarbaby
+                    if(u->fps_explosion==2) d[-1]=11; // teleport
+                    if(u->fps_explosion==3)
+                    { // blood
+                        d[-1]=12;
+                        *d++=16; // 10 particles of blood
+                    }
+                    if(u->fps_explosion==5)
+                    { // gunshot
+                        d[-1]=2;
+                        *d++=16; // 10 particles of gunshot
+                    }
+                    x=DUP2; y=DUP2; z=DUP2;
+                    if(u->fps_explosion==4) d=d0;
+                    if(u->fps_explosion)
+                    {
+                        *d++=0x06;
+                        *d++=0x00;
+                        *d++=0x00;
+                        if(!u->explosionsound) *d++=1;
+                        else *d++=u->explosionsound;
+                        *d++=(x&255);
+                        *d++=(x>>8);
+                        *d++=(y&255);
+                        *d++=(y>>8);
+                        *d++=(z&255);
+                        *d++=(z>>8);
+                        modified=1;
+                    }
+                }
+                else
+                {
+                    DUP2; DUP2; DUP2;
+                }
+            } break;
+            case 0x18:
+            {
+                if(dump) logx(LOG_PACKETS,"pause\n");
+                DUP1;
+            } break;
+            case 0x1a:
+            {
+                int l,e=0;
+                char *p;
+                if(dump) logx(LOG_PACKETS,"centerprint: %s\n",formatqstring(s));
+
+                if(u->moveoverlay)
+                {
+                    p=s;
+                    while(*p)
+                    {
+                        if(*p=='\n') e++;
+                        p++;
+                    }
+                    if(e<4) e=1;
+                    else e=0;
+                }
+
+                l=strlen(s);
+                if(l<255 && u->overlayactive)
+                {
+                    u->time_nextcamp=0; // force overlay update
+                    u->time_lastoverlay=u->time;
+                    if(e)
+                    {
+                        int i;
+                        for(i=0;i<u->moveoverlay*2;i++)
+                        {
+                            u->overlay[i]='\n';
+                        }
+                        memcpy(u->overlay+i,s,l+1);
+                    }
+                    else memcpy(u->overlay,s,l+1);
+                    SKIPTEXT;
+                    d--;
+                    modified=1;
+                }
+                else
+                {
+                    if(e)
+                    {
+                        int i;
+                        for(i=0;i<u->moveoverlay*2;i++)
+                        {
+                           *d++='\n';
+                        }
+                        modified=1;
+                    }
+                    DUPTEXT;
+                    if(u->time_nextcamp<u->time+750) u->time_nextcamp=u->time+750;
+                }
+            } break;
+            case 0x1b:
+            {
+                if(dump) logx(LOG_PACKETS,"killedmonster\n");
+            } break;
+            case 0x1c:
+            {
+                if(dump) logx(LOG_PACKETS,"foundsecret\n");
+            } break;
+            case 0x1d:
+            {
+                if(dump) logx(LOG_PACKETS,"spawnstaticsound\n");
+                DUP2; DUP2; DUP2;
+                DUP1;
+                DUP1;
+                DUP1;
+            } break;
+            case 0x1e:
+            {
+                if(dump) logx(LOG_PACKETS,"intermission\n");
+                DUP2; DUP2; DUP2;
+                DUP1; DUP1; DUP1;
+            } break;
+            case 0x1f:
+            {
+                if(dump) logx(LOG_PACKETS,"finale\n");
+                DUPTEXT;
+            } break;
+            case 0x20:
+            {
+                if(dump) logx(LOG_PACKETS,"cdtrack\n");
+                DUP1;
+            } break;
+            case 0x21:
+            {
+                if(dump) logx(LOG_PACKETS,"sellscreen\n");
+            } break;
+            case 0x22:
+            {
+                if(dump) logx(LOG_PACKETS,"smallkick\n");
+            } break;
+            case 0x23:
+            {
+                if(dump) logx(LOG_PACKETS,"bigkick\n");
+            } break;
+            case 0x24:
+            {
+                if(dump) logx(LOG_PACKETS,"updateping\n");
+                DUP1;
+                DUP2;
+            } break;
+            case 0x25:
+            {
+                int pl,time;
+                float f;
+                if(dump) logx(LOG_PACKETS,"updateentertime\n");
+                pl=DUP1;
+                time=DUP4;
+                f=*(float *)&time;
+                u->playertime[pl+1]=(int)(f*1000.0)-u->time; // millisec
+            } break;
+            case 0x26:
+            {
+                int stat,value;
+                if(dump) logx(LOG_PACKETS,"updatestatint\n");
+                stat=DUP1;
+                value=DUP4;
+                cl_updatestat(u,stat,value);
+            } break;
+            case 0x27:
+            {
+                int e;
+                e=DUP2;
+                if(u->fps_muzzle==2 || (u->fps_muzzle==1 && e==u->entity))
+                { // remove flash
+                    d-=3;
+                    modified=1;
+                }
+                if(dump) logx(LOG_PACKETS,"muzzleflash\n");
+            } break;
+            case 0x28: // setinfo / userinfo
+            {
+                int  entity;
+                static uchar name[MAXSTR];
+                static uchar team[MAXSTR];
+                static uchar skin[MAXSTR];
+                static uchar spec[MAXSTR];
+                static uchar specnext[MAXSTR];
+                static uchar proxy[MAXSTR];
+                static uchar topc[MAXSTR];
+                static uchar botc[MAXSTR];
+                static uchar xxxc[MAXSTR];
+                uchar *sskin;
+
+                if(dump) logx(LOG_PACKETS,"updateuserinfo\n");
+                entity=DUP1+1;
+                DUP4;
+
+                if(entity>=u->numplayers) u->numplayers=entity+1;
+
+                *name=0;
+                extractfield(name,s,"name\\");
+
+                *team=0;
+                extractfield(team,s,"team\\");
+
+                *spec=0;
+                extractfield(spec,s,"*spectator\\");
+
+                *specnext=0;
+                extractfield(specnext,s,"spectator\\");
+
+                *skin=0;
+                sskin=extractfield(skin,s,"skin\\");
+
+                *topc=0;
+                extractfield(topc,s,"topcolor\\");
+
+                *botc=0;
+                extractfield(botc,s,"bottomcolor\\");
+
+                *xxxc=0;
+                extractfield(xxxc,s,"teamcolor\\");
+
+                strcpy(u->playerproxy[entity],"-");
+
+                *proxy=0;
+                extractfield(proxy,s,"NFProxy\\");
+                if(*proxy)
+                {
+                    proxy[15]=0;
+                    sprintf(u->playerproxy[entity],"NFProxy %s",proxy);
+                }
+
+                *proxy=0;
+                extractfield(proxy,s,"Qizmo\\");
+                if(*proxy)
+                {
+                    proxy[15]=0;
+                    sprintf(u->playerproxy[entity],"Qizmo %s",proxy);
+                }
+
+                *proxy=0;
+                extractfield(proxy,s,"Cheapo\\");
+                if(*proxy)
+                {
+                    proxy[15]=0;
+                    sprintf(u->playerproxy[entity],"Cheapo %s",proxy);
+                }
+
+                logx(LOG_DEBUG,"user %i: skin<%s>\n",entity,skin);
+                if(strlen(skin)>16)
+                {
+                    int num;
+
+                    skin[16]=0;
+
+                    while(s<sskin)
+                    {
+                        *d++=*s++;
+                    }
+
+                    num=16;
+                    while(num--) *d++=*s++;
+                    while(*s!='\\' && *s) s++;
+
+                    while(*s) *d++=*s++;
+                    *d++=*s++;
+
+                    modified=1;
+
+//                    q_printf(u,"DEBUG: longskin\n");
+                    logx(LOG_DEBUG,"user %i: longskin\n",entity);
+                }
+                else
+                {
+                    DUPTEXT;
+                }
+
+                strncpy(u->playername[entity],name,MAXPLAYERNAME-1);
+                strncpy(u->playerteamtext[entity],team,7);
+                u->playerteam[entity]=teamint(team);
+                strncpy(u->playerskin[entity],skin,MAXSKINNAME-1);
+                u->playernear[entity]=0;
+
+                u->playertcolor[entity]=atoi(topc);
+                u->playerbcolor[entity]=atoi(botc);
+                u->playerxcolor[entity]=text2color(xxxc);
+
+                if(entity==u->entity) strncpy(u->name,name,MAXSTR-1);
+                if(entity==u->entity) u->team=teamint(team);
+                if(entity==u->entity) u->tcolor=u->playertcolor[entity];
+                if(entity==u->entity) u->bcolor=u->playerbcolor[entity];
+
+                if(*spec!='1' && *name)
+                {
+                    u->playeractive[entity]=1;
+                    skinoverride(u,entity);
+                }
+                else
+                {
+                    u->playeractive[entity]=0;
+                }
+                logx(LOG_DEBUG,"user %i: name<%s> active<%i>\n",
+                    entity,
+                    u->playername[entity],
+                    u->playeractive[entity]);
+            } break;
+            case 0x29:
+            {
+                int i;
+                if(dump) logx(LOG_PACKETS,"download\n");
+                i=DUP2;
+                DUP1;
+                DUPN(i);
+            } break;
+            case 0x2A: // 0x2a
+            {
+                int  mask,mask2,ukb,fra,hidethis,modelindex,weaponframe,glow,pingx=0,ms=10;
+                int  pl,pos[3],ang[3];
+                uchar *d0=d-1;
+                hidethis=0;
+                pl=1+DUP1; // player entity
+                if(pl>=u->numplayers) u->numplayers=pl+1;
+                u->playernear[pl]=8;
+                mask=DUP2;
+                // position
+                pos[0]=DUP2;
+                pos[1]=DUP2;
+                pos[2]=DUP2;
+                ang[0]=0;
+                ang[1]=0;
+                ang[2]=0;
+                // frame
+                fra=DUP1;
+                if(fra<LASTPLAYERFRAME)
+                {
+                    if(playeranim[fra][0]=='d')
+                    {
+                        u->playernear[pl]=0; // dead player don't count
+                    }
+                    if(u->hideaxemen)
+                    {
+                        if(playeranim[fra][0]=='a' && pl!=u->entity)
+                        {
+                            hidethis=1;
+                        }
+                    }
+                }
+                // other stuff
+                if(mask&0x0001)
+                {
+                    pingx=DUP1; // ping
+                }
+                if(mask&0x0002)
+                {
+                    mask2=DUP1;
+                    if(u->version>=23 && mask2==0)
+                    { // weird
+                        DUP1;
+                    }
+                    else
+                    {
+                        // angle
+                        if(mask2&0x01) ang[0]=DUP2;
+                        if(mask2&0x80) ang[1]=DUP2;
+                        if(mask2&0x02) ang[2]=DUP2;
+                        // speed
+                        if(u->version>=23)
+                        {
+                            if(mask2&0x04) DUP2;
+                            if(mask2&0x08) DUP2;
+                            if(mask2&0x10) DUP2;
+                        }
+                        else
+                        {
+                            if(mask2&0x04) DUP1;
+                            if(mask2&0x08) DUP1;
+                            if(mask2&0x10) DUP1;
+                        }
+                        // flags
+                        if(mask2&0x20)
+                        {
+                            DUP1; // +1=fire, +2=jump
+                        }
+                        // impulse
+                        if(mask2&0x40)
+                        {
+                            DUP1;
+                        }
+                        // load
+                        //if(mask2&0x80)
+                        {
+                            int fps;
+                            ms=DUP1;
+                            if(ms<10)
+                            {
+                                ms=10;
+                                fps=100;
+                            }
+                            else fps=1000/ms;
+                            u->playerfps[pl]=(u->playerfps[pl]+fps+1)/2;
+                        }
+                    }
+                }
+                else mask2=0;
+
+                if(mask&0x0004)
+                {
+                    DUP2; // xspeed
+                }
+                if(mask&0x0008)
+                {
+                    DUP2; // yspeed
+                }
+                if(mask&0x0010)
+                {
+                    DUP2; // zspeed
+                }
+
+                if(mask&0x0020)
+                {
+                    modelindex=DUP1; // modelindex
+                }
+                else modelindex=0;
+
+                if(mask&0x0040)
+                {
+                    ukb=DUP1; // modelframe?
+                }
+                else ukb=0;
+
+                if(mask&0x0080)
+                { // powerup glow
+                    int w;
+                    w=glow=DUP1;
+                    u->playerglow[pl]=w;
+                    //logx(LOG_CONSOLE,"player %i weapon %02X\n",w);
+                    if(u->fps_powerup==2 || (u->fps_powerup==1 && pl==u->entity))
+                    {
+                        modified=1;
+                        d[-1]=0;
+                    }
+                }
+                else
+                {
+                    glow=u->playerglow[pl]=0;
+                }
+
+                /*
+                if(pl==u->entity)
+                {
+                    if(glow==8)
+                    {
+                        if(!(u->power&ITEMS_FLAG))
+                        {
+                            u->power|=ITEMS_FLAG;
+                            checkpowers(u);
+                        }
+                    }
+                    else if(!glow)
+                    {
+                        if((u->power&ITEMS_FLAG))
+                        {
+                            u->power&=~ITEMS_FLAG;
+                            checkpowers(u);
+                        }
+                    }
+                }
+                */
+
+                if(mask&0x0100)
+                {
+                    weaponframe=DUP1; // weaponframe
+                }
+                else weaponframe=0;
+
+                if(mask&0x0200)
+                {
+                    //??
+                }
+
+                if(dump)
+                {
+                    if(mask&2)
+                    {
+                        logx(LOG_PACKETS,"player %i mask=%04X/%02X sz=%i: ",
+                               pl,mask,mask2,d-d0);
+                    }
+                    else
+                    {
+                        logx(LOG_PACKETS,"player %i mask=%04X/xx sz=%i: ",
+                               pl,mask,mask2,d-d0);
+                    }
+                    logx(LOG_PACKETS,"p0 p1 p2 frame ");
+                    if(mask&1) logx(LOG_PACKETS,"ping(%i) ",pingx);
+                    if(mask&2)
+                    {
+                        if(mask2&1)   logx(LOG_PACKETS,"a0 ");
+                                      logx(LOG_PACKETS,"a1 ");
+                        if(mask2&2)   logx(LOG_PACKETS,"a2 ");
+                        if(mask2&4)   logx(LOG_PACKETS,"as0 ");
+                        if(mask2&8)   logx(LOG_PACKETS,"as1 ");
+                        if(mask2&16)  logx(LOG_PACKETS,"as2 ");
+                        if(mask2&32)  logx(LOG_PACKETS,"f/j ");
+                        if(mask2&64)  logx(LOG_PACKETS,"imp ");
+                        if(mask2&128) logx(LOG_PACKETS,"load ");
+                    }
+                    if(mask&4)   logx(LOG_PACKETS,"s0 ");
+                    if(mask&8)   logx(LOG_PACKETS,"s1 ");
+                    if(mask&16)  logx(LOG_PACKETS,"s2 ");
+                    if(mask&32)  logx(LOG_PACKETS,"model ");
+                    if(mask&64)  logx(LOG_PACKETS,"m.fr? ");
+                    if(mask&128) logx(LOG_PACKETS,"glow ");
+                    if(mask&256) logx(LOG_PACKETS,"w.fr ");
+                    logx(LOG_PACKETS,"\n");
+                }
+
+                if(modelindex && strstr(u->model[modelindex],"eyes"))
+                    u->playerglow[pl]|=0x100;
+                else
+                    u->playerglow[pl]&=~0x100;
+
+                if(pl!=u->entity && u->playerteam[pl]!=u->team)
+                { // lastenemymask
+                    int mask=0;
+                    if(u->playerglow[pl]>0)
+                    {
+                        if(u->playerglow[pl]&0x100)
+                        {
+                            mask|=CAMP_R;
+                        }
+                        if(u->playerglow[pl]&0x40)
+                        {
+                            mask|=CAMP_Q;
+                        }
+                        if(u->playerglow[pl]&0x80)
+                        {
+                            mask|=CAMP_P;
+                        }
+                    }
+                    if(mask)
+                    {
+                        u->lastenemymask=mask;
+                        u->time_lastenemymask=u->time;
+                    }
+                }
+
+                if(pl==u->entity)
+                {
+                    if(u->showspeed)
+                    {
+                        float dx,dy,dz;
+                        int   s,n;
+                        n=u->server_lastthis-u->speedlastthis;
+                        if(n<1) n=1;
+                        dx=(float)(u->pos[0]-pos[0]);
+                        dy=(float)(u->pos[1]-pos[1]);
+                        dz=(float)(u->pos[2]-pos[2]);
+                        s=(int)(sqrt(dx*dx+dy*dy)/n)*1000/ms;
+                        u->speedcnt=(u->speedcnt+1)&3;
+                        u->speed[u->speedcnt]=s;
+                        u->speedlastthis=u->server_lastthis;
+                    }
+                    u->pos[0]=pos[0];
+                    u->pos[1]=pos[1];
+                    u->pos[2]=pos[2];
+                }
+
+                u->playerpos[pl][0]=pos[0];
+                u->playerpos[pl][1]=pos[1];
+                u->playerpos[pl][2]=pos[2];
+                u->playerang[pl][0]=ang[0];
+                u->playerang[pl][1]=ang[1];
+                u->playerang[pl][2]=ang[2];
+                if(u->specmode && pl==u->specentity)
+                {
+                    u->tr_postar[0]=pos[0];
+                    u->tr_postar[1]=pos[1];
+                    u->tr_postar[2]=pos[2];
+                    u->tr_angtar[0]=ang[0];
+                    u->tr_angtar[1]=ang[1];
+                    u->tr_angtar[2]=ang[2];
+                }
+                if(u->specmode && (pl==u->entity || pl==u->specentity))
+                {
+                    hidethis=1;
+                }
+
+                if(hidethis)
+                {
+                    d=d0;
+                    modified=1;
+                }
+            } break;
+            case 0x2B:
+            {
+                int nailcount,j;
+                if(dump) logx(LOG_PACKETS,"nails\n");
+                if(u->fps_nails>1)
+                {
+                    char *d0=d;
+                    int   newcount=0;
+                    int   cnt=0;
+                    modified=1;
+
+                    nailcount=DUP1;
+                    for(j=0;j<nailcount;j++)
+                    {
+                        cnt++;
+                        if(cnt>=u->fps_nails) cnt=0;
+
+                        if(!cnt)
+                        {
+                            s+=6;
+                            continue;
+                        }
+
+                        newcount++;
+                        DUP2;
+                        DUP2;
+                        DUP2;
+                    }
+
+                    *d0=newcount;
+                }
+                else
+                {
+                    nailcount=DUP1;
+                    for(j=0;j<nailcount;j++)
+                    {
+                        DUP2;
+                        DUP2;
+                        DUP2;
+                    }
+                }
+            } break;
+            case 0x2C:
+            {
+                if(dump) logx(LOG_PACKETS,"choke\n");
+                u->choke=DUP1;
+            } break;
+            case 0x2D:
+            {
+                if(dump) logx(LOG_PACKETS,"modellist\n");
+                u->fakedisablecnt=100;
+                if(NEWQW) u->models=DUP1+1; // new22
+                else u->models=1;
+                while(*s)
+                {
+                    if(u->models>0 && u->models<MAXMODELS)
+                    {
+                        if(u->model[u->models]) free(u->model[u->models]);
+                        u->model[u->models]=mymalloc(strlen(s)+2);
+                        logx(LOG_PACKETS,"- model %i = %s\n",u->models,s);
+                        strcpy(u->model[u->models],s);
+                        if(!strcmp(u->model[u->models],"progs/grenade.mdl"))
+                        {
+                            logx(LOG_DEBUG,"grenademodel=%i\n",u->models);
+                            u->grenademodel=u->models;
+                        }
+                        if(!strcmp(u->model[u->models],"progs/g_rock2.mdl"))
+                        {
+                            logx(LOG_DEBUG,"pack2model=%i\n",u->models);
+                            u->pack2model=u->models;
+                        }
+                        u->models++;
+                    }
+                    DUPTEXT;
+                }
+                if(u->models>MAXMODELS) u->models=MAXMODELS;
+                DUP1;
+                if(NEWQW) DUP1; // new22
+            } break;
+            case 0x2E:
+            {
+                if(dump) logx(LOG_PACKETS,"soundlist\n");
+                u->fakedisablecnt=100;
+                if(NEWQW) u->sounds=DUP1+1; // new22
+                else u->sounds=1;
+                while(*s)
+                {
+                    if(u->sounds>0 && u->sounds<MAXSOUNDS)
+                    {
+                        if(u->sound[u->sounds]) free(u->sound[u->sounds]);
+                        u->sound[u->sounds]=mymalloc(strlen(s)+2);
+                        strcpy(u->sound[u->sounds],s);
+                        logx(LOG_PACKETS,"- sound %i = %s\n",u->sounds,s);
+                        if(!strcmp(u->sound[u->sounds],"weapons/r_exp3.wav"))
+                        {
+                            logx(LOG_DEBUG,"explosionsound=%i\n",u->sounds);
+                            u->explosionsound=u->sounds;
+                        }
+                        u->sounds++;
+                    }
+                    DUPTEXT;
+                }
+                if(u->sounds>MAXSOUNDS) u->sounds=MAXSOUNDS;
+                DUP1;
+                if(NEWQW) DUP1; // new22
+            } break;
+            case 0x2F:
+            case 0x30:
+            {
+                int mask,entity,ef,index,modelindex,modelframe,modelskin = 0;
+                uchar *d0;
+                u->amconnected=10;
+                if(dump && cmd==0x2f) logx(LOG_PACKETS,"packetentities\n");
+                if(dump && cmd==0x30) logx(LOG_PACKETS,"packetentities(delta)\n");
+                if(cmd==0x30)
+                {
+                    index=DUP1;
+                }
+                else
+                {
+                    memset(u->entityvisible,0,sizeof(u->entityvisible));
+                }
+                for(;;)
+                {
+                    int wasvisible,isvisible;
+                    int oldpos[3];
+                    int newobject=1;
+                    d0=d;
+
+                    mask=DUP2;
+                    modelindex=0;
+                    modelframe=0;
+                    ef=0;
+                    if(!mask) break;
+                    entity=mask&0x1ff;
+                    mask&=0xfe00;
+
+                    if(mask&0x8000) mask|=DUP1;
+
+                    wasvisible=u->entityvisible[entity];
+                    if(mask&0x4000)
+                    { // hide
+                        u->entityvisible[entity]=0;
+                    }
+                    else
+                    {
+                        u->entityvisible[entity]=1;
+                    }
+                    isvisible=u->entityvisible[entity];
+
+                    if(mask&0x0004)
+                    {
+                        modelindex=DUP1; // modelindex
+                        if(u->fps_rocket && !strcmp(u->model[modelindex],"progs/missile.mdl"))
+                        {
+                            if(u->grenademodel<=0) d[-1]++;
+                            else d[-1]=u->grenademodel;
+                            modified=1;
+                        }
+                    }
+                    if(mask&0x2000) modelframe=DUP1; // frame
+                    if(mask&0x0008) DUP1; // colormap
+                    if(mask&0x0010) modelskin=DUP1; // skin
+                    if(mask&0x0020)
+                    {
+                        ef=DUP1; // effects
+                    }
+
+                    if(u->fps_gib && entity<MAXENTITIES && modelindex && cmd==0x30)
+                    {
+                        u->entitygib[entity]=0;
+
+                        if(u->fps_gib==1)
+                        { // also remove gibs
+                            if(strstr(u->model[modelindex],"progs/gib")) u->entitygib[entity]=1;
+                        }
+
+                        { // remove bodies
+                            if(strstr(u->model[modelindex],"progs/h_player")) u->entitygib[entity]=1;
+                            if(strstr(u->model[modelindex],"progs/player"))   u->entitygib[entity]=1;
+                        }
+                    }
+
+#if 0
+                    if(isvisible && u->entityname[entity] && !strcmp(u->entityname[entity],"pack"))
+                    {
+                        modified=1;
+
+                        if(mask&0x0020) d--; // remove old effect if present
+                        // make sure the secondary mask is there
+                        if(!(mask&0x8000))
+                        {
+                            uchar *p=d;
+                            while(p>d0+2)
+                            {
+                                p[1]=p[0];
+                                p--;
+                            }
+                            p[0]=0x20;
+                            d0[1]|=0x80;
+                            d++;
+                        }
+                        // add the mask
+                        d0[2]|=0x20;
+
+                        *d++=0x10; // add grenade effect
+
+                        if(!wasvisible) q_print(u,"<pack appeared>\n");
+                    }
+#endif
+
+                    if(!wasvisible &&
+                       (mask&(0x0004+0x0200+0x0400+0x0800))==(0x0004+0x0200+0x0400+0x0800))
+                    {
+                        newobject=1;
+                        oldpos[0]=u->entitypos[entity][0];
+                        oldpos[1]=u->entitypos[entity][1];
+                        oldpos[2]=u->entitypos[entity][2];
+                    }
+                    else newobject=0;
+
+                    if(mask&0x0200) u->entitypos[entity][0]=DUP2; // origin x
+                    if(mask&0x0001) DUP1; // angle x
+                    if(mask&0x0400) u->entitypos[entity][1]=DUP2; // origin y
+                    if(mask&0x1000) DUP1; // angle y
+                    if(mask&0x0800) u->entitypos[entity][2]=DUP2; // origin z
+                    if(mask&0x0002) DUP1; // angle z
+
+                    if(oldpos[0]==u->entitypos[entity][0] &&
+                       oldpos[1]==u->entitypos[entity][1] &&
+                       oldpos[2]==u->entitypos[entity][2]) newobject=0;
+
+                    if(newobject)
+                    {
+                        u->entityname[entity]=makeentityname(u,modelindex,modelskin,&u->entitymask[entity]);
+
+                        // determine pack owner (if model & pos given so pack is new)
+                        if(u->classifypacks && !strcmp(u->entityname[entity],"pack"))
+                        {
+                            /*
+                            if(oldpos[0]!=u->entitypos[entity] ||
+                               oldpos[0]!=u->entitypos[entity] ||
+                               oldpos[0]!=u->entitypos[entity])
+                            */
+                            { // pack is not in same pos as last time, good
+                                if(packlistnum<u->numplayers)
+                                {
+                                    packlist[packlistnum++]=entity;
+                                }
+                            }
+                        }
+                    }
+
+                    if(dump)
+                    {
+                        logx(LOG_PACKETS,"entity %03X mask=%04X sz=%i: ",entity,mask,d-d0);
+                        if(mask&0x8000) logx(LOG_PACKETS,"mask2 ");
+                        if(mask&0x4000) logx(LOG_PACKETS,"(hide) ");
+                        if(mask&0x0004) logx(LOG_PACKETS,"model ");
+                        if(mask&0x2000) logx(LOG_PACKETS,"m.fr ");
+                        if(mask&0x0008) logx(LOG_PACKETS,"cmap ");
+                        if(mask&0x0010) logx(LOG_PACKETS,"skin ");
+                        if(mask&0x0020) logx(LOG_PACKETS,"eff ");
+                        if(mask&0x0200) logx(LOG_PACKETS,"p0 ");
+                        if(mask&0x0001) logx(LOG_PACKETS,"a0 ");
+                        if(mask&0x0400) logx(LOG_PACKETS,"p1 ");
+                        if(mask&0x1000) logx(LOG_PACKETS,"a1 ");
+                        if(mask&0x0800) logx(LOG_PACKETS,"p2 ");
+                        if(mask&0x0002) logx(LOG_PACKETS,"a2 ");
+                        logx(LOG_PACKETS,"\n");
+                    }
+
+                    if(u->fps_gib && u->entitygib[entity] && cmd==0x30)
+                    {
+                        int mask;
+                        mask=entity|0x4000;
+                        d=d0;
+                        *d++=mask&255;
+                        *d++=mask>>8;
+                        modified=1;
+                    }
+                }
+            } break;
+            case 0x31: // new22
+            { // maxspeed
+                if(dump) logx(LOG_PACKETS,"new maxspeed\n");
+                DUP4;
+            } break;
+            case 0x32: // new22
+            { // ?
+                int a;
+                a=DUP4;
+                if(dump) logx(LOG_PACKETS,"new unknown (ctf) %08X\n",a);
+            } break;
+            case 0x33: // new22
+            {
+                int entity;
+                static char attr[80];
+                static char value[80];
+                uchar *d0;
+                entity=1+DUP1; // player?
+                strcpy(attr,s);
+                DUPTEXT;
+                d0=d;
+                strcpy(value,s);
+                DUPTEXT;
+                if(dump) logx(LOG_PACKETS,"new setinfo %s=%s\n",attr,value);
+                if(!strcmp(attr,"team"))
+                {
+                    strncpy(u->playerteamtext[entity],value,7);
+                    u->playerteam[entity]=teamint(value);
+                    if(entity==u->entity)
+                    {
+                        u->time_reskinall=u->time+500;
+                    }
+                    else skinoverride(u,entity);
+                }
+                else if(!strcmp(attr,"skin"))
+                {
+                    if(strlen(value)>16)
+                    {
+                        value[16]=0;
+                        d0[16]=0;
+                        d=d0+17;
+                        modified=1;
+                        logx(LOG_DEBUG,"user %i: longskin1\n",entity);
+                    }
+                    strncpy(u->playerskin[entity],value,MAXSKINNAME-1);
+                    skinoverride(u,entity);
+                }
+                else if(!strcmp(attr,"topcolor"))
+                {
+                    u->playertcolor[entity]=atoi(value);
+                    if(entity==u->entity)
+                    {
+                        u->tcolor=atoi(value);
+                    }
+                    skinoverride(u,entity);
+                }
+                else if(!strcmp(attr,"bottomcolor"))
+                {
+                    u->playerbcolor[entity]=atoi(value);
+                    if(entity==u->entity)
+                    {
+                        u->bcolor=atoi(value);
+                    }
+                    skinoverride(u,entity);
+                }
+                else if(!strcmp(attr,"teamcolor"))
+                {
+                    u->playerxcolor[entity]=text2color(value);
+                    skinoverride(u,entity);
+                }
+                else if(!strcmp(attr,"spectator"))
+                {
+                }
+                else if(!strcmp(attr,"Cheapo"))
+                {
+                    if(!*value)
+                    {
+                        strcpy(u->playerproxy[entity],"-");
+                    }
+                    else
+                    {
+                        value[15]=0;
+                        sprintf(u->playerproxy[entity],"Cheapo %s",value);
+                    }
+                }
+                else if(!strcmp(attr,"name"))
+                {
+                    strncpy(u->playername[entity],value,MAXPLAYERNAME-1);
+                }
+#ifdef IDENT
+                else if(!strcmp(attr,"#"))
+                {
+                    gotinfomessage(u,value,entity);
+                }
+#endif
+            } break;
+            case 0x34: // new22
+            {
+                char *attr,*value;
+                if(dump) logx(LOG_PACKETS,"new serverinfo/userinfo?\n");
+
+                tmpbuf[0]='f'+10;
+                tmpbuf[1]='p'+11;
+                tmpbuf[2]='d'+12;
+                tmpbuf[3]=0;
+
+                attr=s;
+                DUPTEXT;
+                value=s;
+                DUPTEXT;
+
+                if(!strcmp(attr,"deathmatch"))
+                {
+                    dmmchange(u,atoi(value));
+                }
+
+                tmpbuf[3]=0;
+                tmpbuf[2]-=12;
+                tmpbuf[1]-=11;
+                tmpbuf[0]-=10;
+
+                if(!strcmp(attr,tmpbuf))
+                {
+                    u->fpd=atoi(value)|litemode;
+                    u->fpdcopy=atoi(value)|litemode;
+#ifdef TIMERS
+                    if(u->notimers) u->fpd|=FPD_TIMERS;
+                    if(u->notimers) u->fpdcopy|=FPD_TIMERS;
+#endif
+                    reportfpd(u,u->fpd);
+                }
+            } break;
+            case 0x35: // new23
+            {
+                DUP1; // player?
+                DUP1; // ploss
+            } break;
+            default:
+            { // unrecognized data, bailout (will cause parse error message)
+                s=s_end;
+                modified=0;
+            } break;
+        }
+    }
+
+#ifdef RLPACK
+    if(packlistnum)
+    {
+        int i,entity;
+        for(i=0;i<packlistnum;i++)
+        {
+            int owner=0;
+            entity=packlist[i];
+#ifdef RLPACKDEBUG
+            q_print(u,"<checkrlpack>\n");
+#endif
+            {
+                // find out whos pack
+                int ownerd=999999999;
+                int i,d;
+
+                for(i=0;i<=32;i++) if(u->playeractive[i] && u->time<u->time_playerdied[i]+1000)
+                {
+                    d =abs(u->entitypos[entity][0]-u->playerpos[i][0]);
+                    d+=abs(u->entitypos[entity][1]-u->playerpos[i][1]);
+                    d+=abs(u->entitypos[entity][2]-u->playerpos[i][2]);
+                    if(d<ownerd)
+                    {
+                        owner=i;
+                        ownerd=d;
+                    }
+                }
+            }
+            if(owner)
+            {
+#ifdef RLPACKDEBUG
+                char msg[80];
+                sprintf(msg,"<%s's pack, rtime %i>\n",
+                    u->playername[owner],u->time-u->time_playerrocket[owner]);
+                q_print(u,msg);
+#endif
+                if(u->time-u->time_playerrocket[owner]<700)
+                {
+                    char msg[64];
+                    sprintf(msg,"proxy: rl-pack (%s)\n",u->playername[owner]);
+                    q_print(u,msg);
+                    u->entityname[entity]="rl-pack";
+                }
+            }
+        }
+    }
+#endif
+
+    if(u->specmode && u->specentity)
+    { // track
+        int a,i;
+        if(dump)
+        {
+            logx(LOG_DEBUG,"obs: %02X pos: %i %i %i angles: %i %i %i \n",
+                u->entity,
+                u->tr_postar[0],
+                u->tr_postar[1],
+                u->tr_postar[2],
+                u->tr_angtar[0],
+                u->tr_angtar[1],
+                u->tr_angtar[2]);
+        }
+
+        // smoothing
+        for(i=0;i<3;i++)
+        {
+            a=u->tr_pos[i]-u->tr_postar[i]; if(a<0) a=-a;
+            if(a>512 ) u->tr_pos[i]=u->tr_postar[i];
+            else       u->tr_pos[i]=(u->tr_pos[i]+u->tr_postar[i])/2;
+            a=u->tr_ang[i]-u->tr_angtar[i]; if(a<0) a=-a;
+            if(a>8000) u->tr_ang[i]=u->tr_angtar[i];
+            else       u->tr_ang[i]=(u->tr_ang[i]+u->tr_angtar[i])/2;
+        }
+
+        modified=1;
+        // create angles command from server
+        *d++=0x0a;
+        *d++=u->tr_ang[0]>>8;
+        *d++=u->tr_ang[1]>>8;
+        *d++=u->tr_ang[2]>>8;
+        // create player pos command from server
+        *d++=0x2a;
+        *d++=u->entity-1;
+        // mask
+        *d++=0;
+        *d++=0;
+        // position
+        *d++=u->tr_pos[0]&255;
+        *d++=u->tr_pos[0]>>8;
+        *d++=u->tr_pos[1]&255;
+        *d++=u->tr_pos[1]>>8;
+        *d++=u->tr_pos[2]&255;
+        *d++=u->tr_pos[2]>>8;
+        // frametime
+        *d++=0x1f;
+        // create a specating command to client
+        u->appendclient[u->appendclientbytes++]=0x06;
+        u->appendclient[u->appendclientbytes++]=u->tr_postar[0]&255;
+        u->appendclient[u->appendclientbytes++]=u->tr_postar[0]>>8;
+        u->appendclient[u->appendclientbytes++]=u->tr_postar[1]&255;
+        u->appendclient[u->appendclientbytes++]=u->tr_postar[1]>>8;
+        u->appendclient[u->appendclientbytes++]=u->tr_postar[2]&255;
+        u->appendclient[u->appendclientbytes++]=u->tr_postar[2]>>8;
+    }
+
+    if(modified)
+    {
+        logx(LOG_PACKETS,"Packet MODIFIED!\n");
+        u->dumpsentpacket=1;
+    }
+    if(!modified && (bytes!=d-outbuf || memicmp(buffer+8,outbuf+8,bytes-8)))
+    {
+        u->noappendnow=2;
+        logx(LOG_CONSOLE,"Packet parse error (saved to parse.log)\n",cmd);
+        logx(LOG_DEBUG,"last command %02X at %03X\n",cmd,cmdpos);
+        saveparseerror("<%02X@server.%04X>",cmdpos,buffer,bytes);
+        memcpy(outbuf,buffer,bytes);
+        *outbytes=bytes;
+        sprintf(buf,"proxy: parsing error [s%02X]\n",lastcmd);
+        q_print(u,buf);
+        if(u->parsecount>100)
+        {
+            u->safemode=1;
+#if defined(__APPLE__) || defined(MACOSX)
+            sprintf(buf,"proxy: packet parsing disabled\n");
+#else
+            sprintf(buf,"proxy: packet parsing disabled\n",cmd);
+#endif /* __APPLE__ ||ÊMACOSX */
+            q_print(u,buf);
+            q_print(u,buf);
+            q_print(u,buf);
+        }
+        else
+            u->parsecount+=10;
+        return;
+    }
+    else
+    {
+        // set size
+        *outbytes=d-outbuf;
+        if(u->parsecount<0)
+            u->parsecount=0;
+        else
+            u->parsecount--;
+    }
+}
+
+int handleclient(User *u,uchar *buffer,int bytes,uchar *outbuf,int *outbytes)
+{
+    unsigned int *p=(unsigned int *)buffer;
+    int   duptmp;
+    uchar  buf[MAXSTR];
+    uchar *s,*s_end,*d;
+    uchar *s_beg,*d_beg;
+    int   cmd,cmdpos = 0;
+    int   modified=0;
+    int   dump=loglevel>=LOG_PACKETS;
+    int   simplepacket=1;
+    int   lastcmd = 0;
+
+    if(u->safemode)
+    {
+        memcpy(outbuf,buffer,bytes);
+        *outbytes=bytes;
+        return(0);
+    }
+
+    if(*p==0xffffffff)
+    {
+        memcpy(outbuf,buffer,bytes);
+        *outbytes=bytes;
+        return(0);
+    }
+
+    s=buffer;
+    s_end=buffer+bytes;
+    d=outbuf;
+    s_beg=s;
+    d_beg=d;
+    // copy headers
+    DUPN(8);
+    // new22
+    if(NEWQW)
+    {
+        *d++=u->response_b&255;
+        *d++=u->response_b>>8;
+        u->clientident=shortat(s);
+        s+=2;
+    }
+    else u->clientident=-1;
+    // copy rest
+    cmd=0xff;
+    while(s<s_end)
+    {
+        if(dump) logx(LOG_PACKETS,"%02X@%02X: ",*s,s-buffer);
+        lastcmd=cmd;
+        cmd=DUP1;
+        cmdpos=s-buffer;
+        if(cmd!=0x03 && cmd!=0x05) simplepacket=0;
+        switch(cmd)
+        {
+            case 0x04:
+            {
+                if(dump) logx(LOG_PACKETS,"servercmd: %s\n",formatqstring(s));
+
+                /*
+                {
+                    sprintf(tmpbuf,"servercmd: %s\n",formatqstring(s));
+                    q_print(u,tmpbuf);
+                }
+                */
+
+                if(!memcmp(s,"cheapolink",5))
+                {
+                    u->linkedclient=1;
+                    // remove command from server stream
+                    modified=1;
+                    d--;
+                    // skip from source
+                    SKIPTEXT;
+                }
+                else if(!memcmp(s,"say .",5))
+                {
+                    q_command(u,s+4);
+                    // remove command from server stream
+                    modified=1;
+                    d--;
+                    // skip from source
+                    SKIPTEXT;
+                }
+                else if(!memcmp(s,"say \".",6))
+                {
+                    q_command(u,s+5);
+                    // remove command from server stream
+                    modified=1;
+                    d--;
+                    // skip from source
+                    SKIPTEXT;
+                }
+                else if(!memcmp(s,"say !",5))
+                {
+                    // remove command from server stream
+                    modified=1;
+                    d--;
+                    // skip from source
+                    SKIPTEXT;
+                    q_print(u,"Ignored !-command\n");
+                }
+                else if(!memcmp(s,u->proxysay,strlen(u->proxysay)))
+                {
+                    int l=strlen(u->proxysay);
+                    s[l-1]='.';
+                    q_command(u,s+l-1);
+                    // remove command from server stream
+                    modified=1;
+                    d--;
+                    // skip from source
+                    SKIPTEXT;
+                }
+                else if(!memcmp(s,u->proxysay2,strlen(u->proxysay2)))
+                {
+                    int l=strlen(u->proxysay2);
+                    s[l-1]='.';
+                    q_command(u,s+l-1);
+                    // remove command from server stream
+                    modified=1;
+                    d--;
+                    // skip from source
+                    SKIPTEXT;
+                }
+                else if(!memcmp(s,"say ",4))
+                {
+                    if(floodprot(u))
+                    {
+                        d--;
+                        SKIPTEXT;
+                        modified=1;
+                        break;
+                    }
+
+                    if(u->state==STATE_HERE && !u->linkedserver)
+                    {
+                        int i;
+                        // send saying to all other users in proxy
+                        sprintf(tmpbuf,"[[%s]]: %s\n",u->name,formatqstring(s+4));
+                        //strxor80(tmpbuf);
+                        for(i=0;i<usernum;i++) if(user[i].state==STATE_HERE)
+                        {
+                            q_print(user+i,tmpbuf);
+                        }
+                        DUPTEXT;
+                    }
+                    else
+                    {
+                        /*
+                        Userinfo ui;
+                        userinfofromuser(&ui,u);
+                        modified|=handlesaying(u,&ui,&d,&s);
+                        */
+                        modified|=funnyconvert(d,s,3);
+                        d+=strlen(d)+1;
+                        s+=strlen(s)+1;
+                    }
+                }
+                else if(!memcmp(s,"say_team ",9))
+                {
+                    if(floodprot(u))
+                    {
+                        d--;
+                        SKIPTEXT;
+                        modified=1;
+                        break;
+                    }
+
+                    {
+                        char *d0=d;
+                        int m;
+                        Userinfo ui;
+                        userinfofromuser(&ui,u);
+                        m=handlesaying(u,&ui,&d,&s);
+                        if(m) modified|=1;
+                        if(m&2)
+                        {
+                            fixuphidemsg(u,d0);
+                            d=d0+strlen(d0)+1;
+                        }
+                    }
+                }
+                else if(!memcmp(s,"drop",4))
+                {
+                    q_command(u,"!drop");
+                    DUPTEXT;
+                }
+                else if(!memcmp(s,"ptrack",6))
+                { // spectator change
+                    if(s[7]>32) u->client_spectrack=1;
+                    else u->client_spectrack=0;
+                    DUPTEXT;
+                }
+                else if(!memcmp(s,"begin",5))
+                { // game begin
+                    int a;
+                    u->time_reskinall=u->time+3000;
+                    sscanf(s,"begin %i",&a);
+                    u->lastbegin=a;
+                    DUPTEXT;
+                }
+                else if(!memcmp(s,"soundlist",9))
+                {
+                    int a,b;
+                    sscanf(s,"soundlist %i %i",&a,&b);
+                    if(b==0) u->cnt_sound++;
+                    logx(LOG_DEBUG,"client requests soundlist %i %i\n",a,b);
+                    DUPTEXT;
+                }
+                else if(!memcmp(s,"modellist",9))
+                {
+                    int a,b;
+                    sscanf(s,"modellist %i %i",&a,&b);
+                    if(b==0) u->cnt_model++;
+                    logx(LOG_DEBUG,"client requests modellist %i %i\n",a,b);
+                    DUPTEXT;
+                }
+                else if(!memcmp(s,"setinfo",5))
+                {
+                    uchar attr[MAXSTR],value[MAXSTR];
+                    uchar *p,*sorg;
+                    int  endmark,change=0,remove=0;
+
+                    sorg=s;
+                    while(*s>32) s++;
+                    while(*s<=32 && *s) s++;
+
+                    *attr=0;
+                    *value=0;
+
+                    // attribute
+                    p=attr;
+                    if(*s=='\"')
+                    {
+                        s++;
+                        endmark='\"';
+                    }
+                    else endmark=' ';
+                    while(*s && *s!=endmark && p<attr+60)
+                    {
+                        *p++=*s++;
+                    }
+                    if(*s) s++;
+                    *p=0;
+
+                    while(*s<=32 && *s) s++;
+
+                    // value
+                    p=value;
+                    if(*s=='\"')
+                    {
+                        s++;
+                        endmark='\"';
+                    }
+                    else endmark=' ';
+                    while(*s && *s!=endmark && p<value+60)
+                    {
+                        *p++=*s++;
+                    }
+                    if(*s) s++;
+                    *p=0;
+
+                    logx(LOG_DEBUG,"Setinfo <%s> <%s> s=%02X d=%02X\n",
+                        attr,value,s-s_beg,d-d_beg);
+
+                    if(!stricmp(attr,"Cheapo"))
+                    { // do not allow changing of cheapo setinfo
+#ifndef PROMODE
+                        remove=1;
+#endif
+                    }
+                    else if(!stricmp(attr,"name"))
+                    { // fake name helper
+                        if(funnyconvert(value,value,0))
+                        {
+                            q_print(u,"Name containing $ or # modified.\n");
+                            change=1;
+                        }
+                    }
+                    else if(!stricmp(attr,"teamcolor"))
+                    {
+                        u->playerxcolor[u->entity]=text2color(value);
+                        skinoverride(u,u->entity);
+                    }
+
+                    if(!remove) cl_setinfo(u,attr,value);
+
+                    if(remove)
+                    {
+                        d--;
+
+                        s=sorg;
+                        SKIPTEXT;
+
+                        modified=1;
+                    }
+                    else if(change)
+                    {
+                        p="setinfo \"";
+                        while(*p) *d++=*p++;
+                        p=attr;
+                        while(*p) *d++=*p++;
+                        p="\" \"";
+                        while(*p) *d++=*p++;
+                        p=value;
+                        while(*p) *d++=*p++;
+                        p="\"";
+                        while(*p) *d++=*p++;
+                        *d++=0xA;
+                        *d++=0;
+
+                        s=sorg;
+                        SKIPTEXT;
+
+                        modified=1;
+                    }
+                    else
+                    {
+                        s=sorg;
+                        DUPTEXT;
+                    }
+                }
+                else
+                {
+                    DUPTEXT;
+                }
+            } break;
+            case 0x05:
+            {
+                if(dump) logx(LOG_PACKETS,"sequence(?)\n");
+                DUP1;
+            } break;
+            case 0x06:
+            {
+                if(dump) logx(LOG_PACKETS,"spectator(?)\n");
+                DUP2; DUP2; DUP2;
+            } break;
+            case 0x03:
+            {
+                int i,sx,sy,sz,sp;
+                uchar *d0,*d1;
+                d0=d;
+                if(NEWQW) DUP1; // new22, crc
+                u->impulsehistory[0]=u->impulsehistory[1];
+                u->impulsehistory[1]=u->impulsehistory[2];
+                u->impulsehistory[2]=0;
+                if(u->version<=22)
+                {
+                    for(i=0;i<3;i++)
+                    {
+                        int command,tilt=0,yaw=0,roll=0,flag,impulse;
+                        impulse=0;
+                        command=DUP1;
+                        if(dump)
+                        {
+                            if(i!=0) logx(LOG_PACKETS,"--@--: ");
+                            logx(LOG_PACKETS,"movement(%i): mask:%02X ",i,command);
+                        }
+                        sx=sy=sz=sp=0;
+                        if(command&0x01) tilt=DUP2;
+                        yaw=DUP2;
+                        if(command&0x02) roll=DUP2;
+                        if(i==0)
+                        {
+                            u->ang[0]=tilt;
+                            u->ang[1]=yaw;
+                            u->ang[2]=roll;
+                        }
+                        if(command&0x04) sx=DUP1; // speed x
+                        if(command&0x08) sy=DUP1; // speed y
+                        if(command&0x10) sz=DUP1; // speed z
+
+                        if(command&0x20)
+                        {
+                            flag=DUP1; // flag
+                            if(dump) logx(LOG_PACKETS,"flag:%02X ",flag);
+                            if(!i)
+                            {
+                                if((flag&2) && !(u->lastflag&2) && u->specmode)
+                                { // jump
+                                    spectatenext(u,NULL);
+                                }
+                                if((flag&1) && u->specmode)
+                                { // fire
+                                    u->specmode=0;
+                                    q_print(u,"Tracking disabled.\n");
+                                }
+                                u->lastflag=flag;
+                            }
+                            if(u->specmode)
+                            {
+                                if(flag&2)
+                                {
+                                    d[-1]&=~2;
+                                    modified=1;
+                                }
+                            }
+                        }
+                        else
+                        {
+                            if(!i) u->lastflag=0;
+                        }
+                        if(command&0x40)
+                        {
+                            impulse=DUP1; // impulse
+                            if(dump) logx(LOG_PACKETS,"impulse:%02X ",impulse);
+                            if(i==2 && impulse)
+                            {
+                                if(modifyimpulse(u,&impulse))
+                                { // modified
+                                    if(dump) logx(LOG_PACKETS,"=>%02X ",impulse);
+                                    d[-1]=impulse;
+                                    modified=1;
+                                    u->impulsehistory[2]=impulse;
+                                }
+                                else
+                                { // not modified
+                                    u->impulsehistory[2]=0;
+                                }
+                            }
+                            else if(u->impulsehistory[i])
+                            {
+                                if(dump) logx(LOG_PACKETS,"=>%02X ",u->impulsehistory[i]);
+                                d[-1]=u->impulsehistory[i];
+                                modified=1;
+                            }
+                        }
+                        if(command&0x80)
+                        {
+                            sp=DUP1;
+                            {
+                                int ms=sp;
+                                int fps;
+                                if(ms<10) fps=100;
+                                else fps=1000/ms;
+                                u->playerfps[u->entity]=(u->playerfps[u->entity]+fps+1)/2;
+                            }
+                            if(dump) logx(LOG_PACKETS,"time:%i ",sp);
+                            /* speed cheat
+                            modified=1;
+                            d[-1]*=2;
+                            */
+                        }
+                        if(dump) logx(LOG_PACKETS,"\n");
+                    }
+                }
+                else
+                {
+                    if(u->version>=24)
+                    {
+                        DUP1; // ploss
+                    }
+                    for(i=0;i<3;i++)
+                    {
+                        int command,tilt=0,yaw=0,roll=0,flag,impulse;
+                        char *cmd0;
+                        impulse=sx=sy=sz=sp=0;
+                        cmd0=d;
+                        command=DUP1;
+                        if(dump)
+                        {
+                            if(i!=0) logx(LOG_PACKETS,"--@--: ");
+                            logx(LOG_PACKETS,"movement(%i): mask:%02X ",i,command);
+                        }
+                        if(command&0x01) tilt=DUP2;
+                        if(command&0x80) yaw =DUP2;
+                        if(command&0x02) roll=DUP2;
+                        if(i==0)
+                        {
+                            u->ang[0]=tilt;
+                            u->ang[1]=yaw;
+                            u->ang[2]=roll;
+                        }
+
+                        if(command&0x04)
+                        { // forward
+                            sx=DUP2;
+                        }
+                        if(command&0x08)
+                        { // strafe
+                            sy=DUP2;
+                        }
+                        if(command&0x10)
+                        { // jump
+                            sz=DUP2;
+                        }
+
+                        if(command&0x20)
+                        {
+                            flag=DUP1; // flag
+                            if(dump) logx(LOG_PACKETS,"flag:%02X ",flag);
+                            if(!i)
+                            {
+                                if((flag&2) && !(u->lastflag&2) && u->specmode)
+                                { // jump
+                                    spectatenext(u,NULL);
+                                }
+                                if((flag&1) && u->specmode)
+                                { // fire
+                                    u->specmode=0;
+                                    q_print(u,"Tracking disabled.\n");
+                                }
+                                u->lastflag=flag;
+                            }
+                            if(u->specmode)
+                            {
+                                if(flag&2)
+                                {
+                                    d[-1]&=~2;
+                                    modified=1;
+                                }
+                            }
+                        }
+                        else
+                        {
+                            if(!i) u->lastflag=0;
+                        }
+
+                        if(command&0x40)
+                        {
+                            impulse=DUP1; // impulse
+                            if(dump) logx(LOG_PACKETS,"impulse:%02X ",impulse);
+                            if(i==2 && impulse)
+                            {
+                                if(modifyimpulse(u,&impulse))
+                                { // modified
+                                    if(dump) logx(LOG_PACKETS,"=>%02X ",impulse);
+                                    d[-1]=impulse;
+                                    modified=1;
+                                    u->impulsehistory[2]=impulse;
+                                }
+                                else
+                                { // not modified
+                                    u->impulsehistory[2]=0;
+                                }
+                            }
+                            else if(u->impulsehistory[i])
+                            {
+                                if(dump) logx(LOG_PACKETS,"=>%02X ",u->impulsehistory[i]);
+                                d[-1]=u->impulsehistory[i];
+                                modified=1;
+                            }
+                        }
+                        {
+                            sp=DUP1;
+                            {
+                                int ms=sp;
+                                int fps;
+                                if(ms<10) fps=100;
+                                else fps=1000/ms;
+                                u->playerfps[u->entity]=(u->playerfps[u->entity]+fps+1)/2;
+                            }
+                            if(dump) logx(LOG_PACKETS,"time:%i ",sp);
+                        }
+
+                        if(dump) logx(LOG_PACKETS,"\n");
+                    }
+                }
+
+                d1=d;
+
+#if 0
+                logx(LOG_VERBOSE,"Client movedata check: packet %i/%i real %02X calculated %02X\n",
+                     *((int *)(buffer+0)) & 0x7fffffff,
+                     *((int *)(buffer+4)) & 0x7fffffff,
+                     d0[0],checksum(buffer,d0+1,d1-d0-1));
+#endif
+
+                if(NEWQW && modified)
+                { // recalculate packet checksum
+                    *d0=checksum(buffer,d0+1,d1-d0-1);
+                }
+            } break;
+            default:
+            { // unrecognized data, bailout (will cause parse error message)
+                s=s_end;
+                modified=0;
+            } break;
+        }
+    }
+
+    if(modified)
+    {
+        logx(LOG_PACKETS,"Packet MODIFIED!\n");
+        u->dumpsentpacket=1;
+    }
+    if(!modified && (bytes!=d-outbuf || memicmp(buffer+10,outbuf+10,bytes-10)))
+    {
+        lognpackets=1;
+        oldloglevel=loglevel;
+        loglevel=LOG_PACKETS;
+
+        u->noappendnow=2;
+        logx(LOG_CONSOLE,"Packet parse error (saved to parse.log)\n",cmd);
+        logx(LOG_DEBUG,"last command %02X at %03X\n",cmd,cmdpos);
+        saveparseerror("<%02X@client.%04X>",cmdpos,buffer,bytes);
+        memcpy(outbuf,buffer,bytes);
+        *outbytes=bytes;
+        sprintf(buf,"proxy: parsing error [c%02X]\n",lastcmd);
+        q_print(u,buf);
+        if(u->parsecount>100)
+        {
+            u->safemode=1;
+#if defined(__APPLE__) || defined(MACOSX)
+            sprintf(buf,"proxy: packet parsing disabled\n");
+#else
+            sprintf(buf,"proxy: packet parsing disabled\n",cmd);
+#endif /* __APPLE__ ||ÊMACOSX */
+            q_print(u,buf);
+            q_print(u,buf);
+            q_print(u,buf);
+        }
+        else
+            u->parsecount+=10;
+        return(0);
+    }
+    else
+    {
+        // set size
+        *outbytes=d-outbuf;
+        if(u->parsecount<0)
+            u->parsecount=0;
+        else
+            u->parsecount--;
+    }
+    return(simplepacket);
+}
+
+void appendpacket(uchar *buf,int *bytes,uchar *adata,int *abytes)
+{
+    if(*bytes+*abytes>APPENDSIZE) return;
+    memcpy(buf+*bytes,adata,*abytes);
+    *bytes+=*abytes;
+    *abytes=0;
+}
+
+/********************************/
+
+typedef int (*qsortfunc)(const void *a,const void *b);
+
+int cmpnum(const uchar **txt1,const uchar **txt2)
+{
+    const uchar *a,*b;
+    uchar at[3],bt[3];
+    int  ai,bi;
+
+    a=*txt1;
+    b=*txt2;
+
+    a+=strlen(a)-3;
+    b+=strlen(b)-3;
+
+    at[0]=a[0]&0x7f;
+    at[1]=a[1]&0x7f;
+    at[2]=0;
+
+    bt[0]=b[0]&0x7f;
+    bt[1]=b[1]&0x7f;
+    bt[2]=0;
+
+    if(at[0]=='?') ai=999;
+    else if(at[1]=='m') ai=100+at[0];
+    else ai=atoi(at);
+
+    if(bt[0]=='?') bi=999;
+    else if(bt[1]=='m') bi=100+bt[0];
+    else bi=atoi(bt);
+
+    return(ai-bi);
+}
+
+void mixoverlays(char *d,char *s1,char *s2)
+{
+    char *dend=d+1000,*d0=d,*s0;
+    int   lines=0;
+//    logx(LOG_CONSOLE,"mix<%s><%s>\n",s1,s2);
+    while((*s1 || *s2) && d<dend)
+    {
+        while(*s1=='\r') s1++;
+        while(*s2=='\r') s2++;
+
+        if( ((*s1) && (*s1)!='\n') )
+        { // line from s1
+            s0=s1;
+        }
+        else
+        {
+            s0=s2;
+        }
+
+        while(*s0 && *s0!='\n') *d++=*s0++;
+        while(*s1 && *s1!='\n') s1++;
+        while(*s2 && *s2!='\n') s2++;
+        if(*s1 || *s2) *d++='\n';
+        if(*s1=='\n') s1++;
+#if defined(__APPLE__) || defined(MACOSX)
+        // BUG?
+        if(*s2=='\n') s2++;
+#else
+        if(*s2=='\n') *s2++;
+#endif /* __APPLE__ ||ÊMACOSX */
+        lines++;
+    }
+    if(lines>500) *d0=0;
+    *d++=0;
+//    logx(LOG_CONSOLE,"mixresult<%s>\n",d0);
+}
+
+void readheader(uchar *inbuf,int *seqthis,int *seqrecv,int *bitthis,int *bitrecv)
+{
+    *seqthis=intat(inbuf+0);
+    *seqrecv=intat(inbuf+4);
+    if(*seqthis==-1) *bitthis=0x80;
+    else
+    {
+        *bitthis=(*seqthis&0x80000000)?0x80:0x00;
+        *seqthis&=0x7fffffff;
+    }
+    if(*seqrecv==-1) *bitrecv=0x80;
+    else
+    {
+        *bitrecv=(*seqrecv&0x80000000)?0x80:0x00;
+        *seqrecv&=0x7fffffff;
+    }
+}
+
+void writeheader(uchar *inbuf,int *seqthis,int *seqrecv,int *bitthis,int *bitrecv)
+{
+    int s1,s2;
+    if(*seqthis==-1) s1=-1;
+    else
+    {
+        s1=*seqthis&0x7fffffff;
+        if(*bitthis) s1|=0x80000000;
+    }
+    if(*seqrecv==-1) s2=-1;
+    else
+    {
+        s2=*seqrecv&0x7fffffff;
+        if(*bitrecv) s2|=0x80000000;
+    }
+    setintat(inbuf+0,s1);
+    setintat(inbuf+4,s2);
+}
+
+int packetmatch(User *u,uchar *buffer)
+{ // 0=exact match, smaller better
+    int nthis,nrecv,value,dummy;
+    int timedelay,ident;
+
+    timedelay=500;
+
+    readheader(buffer,&nthis,&nrecv,&dummy,&dummy);
+    ident=shortat(buffer+8);
+
+    if(u->time<u->time_lastclient+timedelay) return(0x7fffffff);
+    if(nthis<u->client_lastthis) return(0x7fffffff);
+    if(nrecv>u->server_lastthis) return(0x7fffffff);
+    if(u->clientident!=-1 && ident!=u->clientident) return(0x7fffffff);
+    value =abs(nrecv-u->server_lastthis);
+    value+=abs(nthis-u->client_lastthis);
+    return(value);
+}
+
+void q_serverextras(User *u)
+{
+    int i;
+
+    if(u->time>u->time_lastoverlay+1100)
+    {
+        *u->overlay=0;
+    }
+
+    if(u->trackloc)
+    {
+        int i,n;
+        // update one visible player / packet
+        for(i=0;i<32;i++)
+        {
+            u->tracklocind++;
+            if(u->tracklocind>32) u->tracklocind=0;
+            n=u->tracklocind;
+            if(u->playeractive[n])
+            {
+                int l;
+                int old1,old2;
+                findnear(u,u->playerpos[n][0],u->playerpos[n][1],u->playerpos[n][2],&l);
+
+                old1=u->playerlastloc[n];
+                old2=u->playerloc[n];
+
+                u->playerlastloc[n]=u->playerloc[n];
+                u->playerloc[n]=l;
+
+                if(!playerlocdirection(u,n))
+                {
+                    u->playerlastloc[n]=old1;
+                    u->playerloc[n]=old2;
+                }
+                break;
+            }
+            else
+            {
+                u->playerloc[n]=u->playerlastloc[n]=0;
+            }
+        }
+    }
+
+    if(u->time_reskinall && u->time>u->time_reskinall)
+    {
+        if(u->skinoverride || u->coloroverride)
+        {
+            int limit=2; // 2 updates per packet
+
+            logx(LOG_DEBUG,"reskinall (pos=%i)\n",u->skinoverridepos);
+
+            // reset change counter to start
+            if(u->skinoverridepos>=34)
+            {
+                int i;
+                u->playerfragsbest=0;
+                for(i=0;i<32;i++)
+                {
+                    if(u->playerfrags[i]>u->playerfragsbest)
+                    {
+                        u->playerfragsbest=u->playerfrags[i];
+                    }
+                }
+                u->skinoverridepos=0;
+            }
+
+            // change limit skins
+            while(limit>0)
+            {
+                if(u->playeractive[u->skinoverridepos])
+                {
+                    limit--;
+                    u->time_reskinall=0;
+                    if(u->color==-3 || u->color2==-3)
+                    {
+                        rankchange(u,u->skinoverridepos,0);
+                    }
+                    skinoverride(u,u->skinoverridepos);
+                    u->time_reskinall=u->time;
+                }
+
+                u->skinoverridepos++;
+                if(u->skinoverridepos>=34)
+                {
+                    if(u->skinoverride<0) u->skinoverride=0;
+                    q_stufftext(u,"skins\n");
+                    u->time_reskinall=0;
+                    break;
+                }
+            }
+        }
+        else u->time_reskinall=0;
+    }
+    else
+    {
+        if(u->coloroverride && u->color==-3 || u->color2==-3)
+        {
+            // increment counter (all players updated every 32 packets)
+            u->rankoverridepos+=7;
+            if(u->rankoverridepos>=33) u->rankoverridepos-=32;
+            if(u->rankoverridepos>=33) u->rankoverridepos=1;
+
+            if(u->playeractive[u->rankoverridepos])
+            {
+                // rankupdate that player
+                rankchange(u,u->rankoverridepos,1);
+            }
+        }
+    }
+
+    // player nearness check
+    for(i=0;i<u->numplayers;i++)
+    {
+        u->playernear[i]--;
+        if(u->playernear[i]<0) u->playernear[i]=0;
+    }
+
+    if(!u->amspectator && u->time_sendfreport && u->time>u->time_sendfreport)
+    {
+        u->time_sendfreport=0;
+        u->time_lastfreport=u->time;
+        q_stufftext(u,"f_report\n");
+    }
+
+#if 0
+    if(u->pointshow && u->time>u->time_nexttarget)
+    {
+        uchar *n;
+
+        n=findpoint(u,u->pos[0],u->pos[1],u->pos[2],u->ang[0],u->ang[1],u->ang[2],u->pointshow,0,NULL);
+        strcpy(u->msgline,n);
+        strxor80(u->msgline);
+
+        u->time_nexttarget=u->time+100; // 0.1 sec
+        if(strcmp(n,u->lasttarget)) u->time_nextcamp=0;
+        strcpy(u->lasttarget,n);
+    }
+#endif
+
+    if(u->showhud)
+    {
+        static uchar buf[MAXSTR];
+        static uchar buf2[MAXSTR];
+
+        u->overlayactive=1;
+
+        if(u->time>u->time_nextcamp || u->time<u->time_nextcamp-1000)
+        {
+            int a;
+
+            u->time_nextcamp=u->time+100;
+
+            memset(buf,'\n',u->moveoverlay);
+            buf[u->moveoverlay]=0;
+
+            strcat(buf,"\n\n\n\n");
+
+
+            {
+                     if(u->items&ITEMS_RA) buf2[0]=0x80|'r';
+                else if(u->items&ITEMS_YA) buf2[0]=0x80|'y';
+                else if(u->items&ITEMS_GA) buf2[0]=0x80|'g';
+                else                       buf2[0]=0x80|'a';
+                buf2[1]=0x9d;
+                buf2[2]=0x9e;
+                buf2[3]=0x9e;
+                buf2[4]=0x9e;
+                buf2[5]=0x9e;
+                buf2[6]=0x9f;
+                buf2[7]='\n';
+                buf2[8]=0;
+
+                a=6*u->armor/195;
+                if(a<1) a=1;
+                if(a>6) a=6;
+                     if(u->armor<50)  buf2[a]=0x87;
+                else if(u->armor<101) buf2[a]=0x88;
+                else                  buf2[a]=0x8d;
+
+                strcat(buf,buf2);
+            }
+
+            {
+                if(u->health>100) buf2[0]=0x80|'m';
+                else              buf2[0]=0x80|'h';
+                buf2[1]=0x9d;
+                buf2[2]=0x9e;
+                buf2[3]=0x9e;
+                buf2[4]=0x9e;
+                buf2[5]=0x9e;
+                buf2[6]=0x9f;
+                buf2[7]='\n';
+                buf2[8]=0;
+
+                a=6*u->health/95;
+                if(a<1) a=1;
+                if(a>6) a=6;
+                     if(u->health<50) buf2[a]=0x87;
+                else if(u->health<75) buf2[a]=0x88;
+                else                  buf2[a]=0x8d;
+
+                strcat(buf,buf2);
+            }
+
+            if(u->ammo_rockets<5 && (u->items&ITEMS_RL))
+            {
+                sprintf(buf2,"ammo:%i\n",u->ammo_rockets);
+                strcat(buf,buf2);
+            }
+
+            q_centerprint(u,buf);
+            return;
+        }
+    }
+
+    if(u->time>u->time_nextcamp)
+    {
+        u->time_nextcamp=u->time+1000;
+
+        // determine last active player
+        for(i=33;i>1;i--) if(u->playeractive[i]) break;
+        u->numplayers=i;
+
+        // megahealth check
+        for(i=0;i<u->camps;i++)
+        {
+            Camp *ca;
+            ca=&u->camp[i];
+            if(ca->category&CAMP_MH)
+            {
+                if(u->health<=100 && ca->appeartime==UNKNOWNTIME)
+                {
+                    ca->appeartime=20500+u->time;
+                    ca->secondcamp=0;
+                }
+            }
+        }
+
+        if(u->showspeed || u->showtime || u->showdist)
+        {
+            *u->msgline=0;
+            u->time_nextcamp=u->time+200;
+            if(u->showtime)
+            {
+                sprintf(u->msgline+strlen(u->msgline),"time:%03i ",((u->time-u->showtimebase)/1000)%1000);
+            }
+            if(u->showspeed)
+            {
+                int s,i;
+                s=0;
+                for(i=0;i<4;i++) s+=u->speed[i];
+                s=s/300;
+                sprintf(u->msgline+strlen(u->msgline),"speed:%03i ",s);
+            }
+            if(u->showdist)
+            {
+                float d,f;
+                f=(float)(u->showdistbase[0]-u->pos[0]);
+                d=f*f;
+                f=(float)(u->showdistbase[1]-u->pos[1]);
+                d+=f*f;
+                f=(float)(u->showdistbase[2]-u->pos[2]);
+                d=(float)sqrt(d);
+
+                sprintf(u->msgline+strlen(u->msgline),"dist:%04i (x:%04i y:%04i z:%04i) ",
+                    (int)(d/10),
+                    (u->showdistbase[0]-u->pos[0])/10,
+                    (u->showdistbase[1]-u->pos[1])/10,
+                    (u->showdistbase[2]-u->pos[2])/10);
+            }
+        }
+
+        if(u->campthese||u->specmode||*u->msgline||*u->overlay||*u->notice)
+        { // show to self
+            static uchar buf[MAXSTR];
+            static uchar buf1[MAXSTR]; // first camp line
+            static uchar buf2[MAXSTR]; // second camp line
+#ifdef TIMERS
+            static uchar reports[64][16];
+#endif /* TIMERS */
+            int          i;
+
+            memset(buf,0,MAXSTR);
+            memset(buf,'\n',u->linesdown/2+u->moveoverlay);
+            if(*u->notice)
+            {
+                strcat(buf,u->notice);
+                *u->notice=0;
+            }
+            memset(buf+strlen(buf),'\n',u->linesdown-u->linesdown/2-u->moveoverlay);
+
+            // addc lock
+            if(u->specmode)
+            {
+                if(u->specentity==u->entity)
+                {
+#if defined(__APPLE__) || defined(MACOSX)
+                    buf[strlen(buf)] = 0x00;
+#else
+                    sprintf(buf+strlen(buf),"");
+#endif /* __APPLE__ || MACOSX */
+                }
+                else if(u->specentity==0)
+                {
+                    sprintf(buf+strlen(buf),"nobody to track\n");
+                }
+                else
+                {
+                    uchar *armor=" ";
+                    if(u->items&0x2000)
+                    {
+                        armor="g";
+                    }
+                    if(u->items&0x4000)
+                    {
+                        armor="y";
+                    }
+                    if(u->items&0x8000)
+                    {
+                        armor="r";
+                    }
+                    sprintf(buf+strlen(buf),"%s \x9c \xE8\xE5\xE1\xEC\xF4\xE8 %3i \x9c \xe1\xf2\xed\xef\xf2 %s %3i\n",
+                        u->playername[u->specentity],
+                        u->health,
+                        armor,
+                        u->armor);
+                }
+            }
+            else
+            {
+                sprintf(buf+strlen(buf),"%s\n",u->msgline);
+                *u->msgline=0;
+            }
+
+#ifdef TIMERS
+            // generate list of camps
+            if(u->campthese && !(u->fpd&FPD_TIMERS))
+            {
+                uchar       *rep[64];
+                int          r;
+                r=0;
+                for(i=0;i<u->camps;i++)
+                {
+                    Camp *ca;
+                    int dtime;
+                    char middlechar;
+                    ca=&u->camp[i];
+                    if((ca->category&u->campthese) && ca->appeartime)
+                    {
+                        int style=0,tim;
+                        dtime=(int)ca->appeartime-(int)u->time;
+
+                        if(u->remindcamp && (ca->category&(CAMP_R|CAMP_P|CAMP_Q)) && dtime>=u->remindcamp*1000-1000 && dtime<u->remindcamp*1000+1000 )
+                        {
+                            sprintf(u->notice,"\x10%s\x11",ca->name);
+                            strxor80(u->notice);
+                        }
+
+                        if(ca->appeartime==UNKNOWNTIME || ca->appeartime==UNKNOWNTIME2)
+                        {
+                            style=0;
+                        }
+                        else if(dtime<-3000)
+                        {
+                            if(u->secondcamp && ca->secondcamp==0 && (ca->category&(CAMP_R|CAMP_P|CAMP_Q)))
+                            {
+                                ca->appeartime+=ca->delay;
+                                dtime+=ca->delay;
+                                ca->secondcamp++;
+                                style=1;
+                            }
+                            else
+                            {
+                                ca->appeartime=0;
+                                continue; // don't display
+                            }
+                        }
+                        else
+                        {
+                            style=1;
+                        }
+
+                        if(ca->secondcamp) middlechar=0x0d;
+                        else middlechar=0x1c;
+
+                        tim=(dtime-123)/1000;
+                        if(tim<0) tim=0;
+
+                        if(!style)
+                        {
+                            sprintf(reports[r++],"%s%c?? ",ca->name,middlechar);
+                        }
+                        else
+                        {
+                            if(tim>99)
+                            {
+                                tim=(tim+30)/60;
+                                sprintf(reports[r++],"%s%c%cm ",ca->name,middlechar,tim%10+'0');
+                            }
+                            else
+                            {
+                                if(tim<10) sprintf(reports[r++],"%s%c%c%c ",ca->name,middlechar,tim/10+'0'+0x80,tim%10+'0'+0x80);
+                                else sprintf(reports[r++],"%s%c%c%c ",ca->name,middlechar,tim/10+'0',tim%10+'0');
+                            }
+                        }
+                    }
+                }
+
+                // sort list of camps
+                if(r)
+                {
+                    for(i=0;i<r;i++) rep[i]=reports[i];
+                    qsort(rep,r,sizeof(uchar *),(qsortfunc)cmpnum);
+                }
+
+                // add camps to two camp rows
+                buf1[0]=' '; buf1[1]=0;
+                buf2[0]=' '; buf2[1]=0;
+                {
+                    int r1=0,r2=0;
+                    for(i=0;i<r;i++)
+                    {
+                        if(rep[i][1]>='0' && rep[i][1]<='9' &&
+                          (rep[i][0]=='p' || rep[i][0]=='r' || rep[i][0]=='q'))
+                        {
+                            if(r1<5)
+                            {
+                                strcat(buf1,rep[i]);
+                                r1++;
+                            }
+                        }
+                    }
+                    for(i=0;i<r;i++)
+                    {
+                        if(rep[i][1]>='0' && rep[i][1]<='9' &&
+                          (rep[i][0]=='p' || rep[i][0]=='r' || rep[i][0]=='q'))
+                        {
+                        }
+                        else
+                        {
+                            if(r1+r2<5)
+                            {
+                                strcat(buf2,rep[i]);
+                                r2++;
+                            }
+                        }
+                    }
+                }
+            }
+            else
+#endif
+            {
+                *buf2=0;
+                *buf1=0;
+            }
+
+            {
+                uchar *d,*s;
+                int   l;
+
+                d=buf+strlen(buf);
+
+                s=buf1;
+                while(*s)
+                {
+                    *d++=(*s++)^0x80;
+                }
+
+                if(!u->linecenter)
+                {
+                    l=u->linesright-strlen(buf1)-strlen(buf2);
+                    if(l<0) l=0;
+                    memset(d,' ',l); d+=l;
+                }
+
+                s=buf2;
+                while(*s)
+                {
+                    *d++=(*s++)^0x80;
+                }
+            }
+
+            // check emptiness, print if not empty
+            for(i=strlen(buf)-1;i>=0;i--)
+            {
+                if((buf[i]&127)>32) break;
+            }
+            if(i<=0)
+            {
+                buf[0]=0;
+                u->overlayactive=0;
+            }
+            else u->overlayactive=1;
+
+            if(*u->overlay)
+            {
+                mixoverlays(tmpbuf,buf,u->overlay);
+                if(*tmpbuf) q_centerprint(u,tmpbuf); // something to show
+            }
+            else
+            {
+                if(*buf) q_centerprint(u,buf); // something to show
+            }
+        }
+        else u->overlayactive=0;
+    }
+}
+
+void q_server(User *u,uchar *inbuf,int inbytes)
+{
+    int seqthis,seqrecv;
+    int bitthis,bitrecv;
+    int outbytes;
+
+    if(u->amconnected>0) u->amconnected--;
+//    if((rand()&15)<9) return; // throw away packet
+
+    u->packetserveroutsize[u->packetserversizecnt]=inbytes;
+#ifdef COMPRESS
+    uncompress(u,inbuf,&inbytes,1);
+#endif
+    u->packetserverinsize[u->packetserversizecnt]=inbytes;
+
+    readheader(inbuf,&seqthis,&seqrecv,&bitthis,&bitrecv);
+
+    if(seqthis!=-1)
+    {
+        if(seqthis<u->server_lastthis-30)
+        {
+            u->outoforder30++;
+        }
+        if(seqthis<u->server_lastthis)
+        {
+            u->outoforder++;
+        }
+        if(seqthis<u->connectstartsequence)
+        {
+            logx(LOG_DEBUG,"packet out of sequence, dropped.\n");
+            return;
+        }
+        u->server_hiddenbit=bitthis;
+        u->server_lastthis=seqthis;
+        u->server_lastrecv=seqrecv;
+    }
+
+    if(u->packetserversizecnt==0)
+    { // every 16 packets, calc ping
+        int n,i,ping=0,pingcnt=0;
+        for(n=0;n<16;n++)
+        {
+            i=(STATNUM+u->lastpacketrecv-1-n)&(STATNUM-1);
+            if(u->packetrecvtime[i])
+            {
+                ping+=u->packetrecvtime[i]-u->packetsendtime[i];
+                pingcnt++;
+            }
+        }
+        if(!pingcnt) ping=999;
+        else ping=ping/pingcnt+1; // raw round up
+        if(ping<=0) ping=1;
+        u->server_ping=ping;
+    }
+
+    if(loglevel>=LOG_DEBUG) logx(LOG_DEBUG,"receive %02X %02X time %i\n",inbuf[0],inbuf[4],u->time);
+
+    handleserver(u,inbuf,inbytes,outbuf,&outbytes);
+
+    if(u->cnt_model>1 || u->cnt_sound>1)
+    {
+        q_print(u,"Error connecting to server.\n");
+        outbytes=8; // remove all but header
+    }
+    expires_check();
+
+    q_serverextras(u);
+
+    if(!outbytes) return;
+
+    if(u->noappendnow>0) u->noappendnow--;
+    else
+    {
+        if(u->appendserverbytes)
+        {
+            appendpacket(outbuf,&outbytes,u->appendserver,&u->appendserverbytes);
+            logx(LOG_PACKETS,"Packet APPENDED!\n");
+            u->dumpsentpacket=1;
+        }
+    }
+
+    writeheader(outbuf,&seqthis,&seqrecv,&bitthis,&bitrecv);
+
+    if(u->compress_test)
+    {
+#ifdef COMPRESS
+        static char tmp[4096];
+        int         tmpbytes;
+
+        u->compress_server=1;
+
+        tmpbytes=outbytes;
+        memcpy(tmp,outbuf,tmpbytes);
+
+        compress(u,tmp,&tmpbytes,1);
+        c_hexdump("CTEST server compressed:",tmp,tmpbytes,u);
+
+        u->packetserveroutsize[u->packetserversizecnt]=tmpbytes;
+
+        uncompress(u,tmp,&tmpbytes,1);
+        c_hexdump("CTEST server uncompressed:",tmp,tmpbytes,u);
+
+        u->packetserverinsize[u->packetserversizecnt]=tmpbytes;
+
+        u->compress_server=0;
+
+        if(outbytes!=tmpbytes)
+        {
+            logx(LOG_PACKETS,"\nCTEST COMPRESS MISMATCH!\n");
+        }
+        logx(LOG_PACKETS,"\n");
+
+        if(u->compress_test>1)
+        {
+            outbytes=tmpbytes;
+            memcpy(outbuf,tmp,outbytes);
+        }
+#endif
+    }
+    else
+    {
+#ifdef COMPRESS
+        compress(u,outbuf,&outbytes,1);
+#endif
+    }
+
+    if(seqrecv!=-1)
+    {
+        int i;
+        u->packetrecvtime[seqrecv&(STATNUM-1)]=u->time;
+        u->lastpacketrecv=seqrecv&(STATNUM-1);
+        u->packetchoke   [seqrecv&(STATNUM-1)]=u->choke;
+        for(i=0;i<u->choke;i++)
+        {
+            u->packetrecvtime[(seqrecv-1-i)&(STATNUM-1)]=u->time;
+            u->packetchoke   [(seqrecv-1-i)&(STATNUM-1)]=-1;
+        }
+
+        u->packetserversize[u->packetserversizecnt]=inbytes;
+        u->packetserversizecnt=(u->packetserversizecnt+1)&15;
+    }
+
+    c_sendclient(u,outbuf,outbytes);
+
+    if(u->cnt_model>1 || u->cnt_sound>1)
+    {
+        u->cnt_model=u->cnt_sound=0;
+        q_command(u,"!disconnect");
+    }
+}
+
+void q_client(User *u,uchar *inbuf,int inbytes)
+{
+    int seqthis,seqrecv;
+    int bitthis,bitrecv;
+    int outbytes,simple;
+#ifdef COMPRESS
+    int inbytes0=inbytes;
+#endif /* COMPRESS */
+
+//    if((rand()&15)<9) return; // throw away packet
+
+#ifdef COMPRESS
+    uncompress(u,inbuf,&inbytes,0);
+#endif
+
+    readheader(inbuf,&seqthis,&seqrecv,&bitthis,&bitrecv);
+
+    if(u->cpps)
+    {
+        if(u->time<u->time_nextcpps && !bitthis)
+        { // drop packet
+            u->cppsdrop++;
+            return;
+        }
+        u->cppspass++;
+        // accept packet, calc ok arrival for next packet
+        u->time_nextcpps+=u->time_cppsdelta;
+        if(u->time_nextcpps<u->time)
+        {
+            u->time_nextcpps=u->time+u->time_cppsdelta;
+        }
+    }
+
+    if(seqthis!=-1)
+    {
+        u->sequence=seqthis+1;
+        u->client_lastthis=seqthis;
+        u->client_lastrecv=seqrecv;
+
+        u->packetsendtime[seqthis&(STATNUM-1)]=u->time;
+        u->packetrecvtime[seqthis&(STATNUM-1)]=0;
+        u->packetchoke   [seqthis&(STATNUM-1)]=0;
+
+        u->packetclientsize[u->packetclientsizecnt]=inbytes;
+        u->packetclientsizecnt=(u->packetclientsizecnt+1)&15;
+    }
+
+#ifdef IDENT
+    if(u->clearrequest)
+    {
+        sendinfomessage(u,"\"\"");
+        u->clearrequest=0;
+    }
+#endif
+
+    simple=handleclient(u,inbuf,inbytes,outbuf,&outbytes);
+    if(u->appendclientbytes>0) simple=0;
+    // simple=only movement & sequence in packet
+
+    if(u->noappendnow>0) u->noappendnow--;
+    else
+    {
+        if(u->appendclientbytes)
+        {
+            appendpacket(outbuf,&outbytes,u->appendclient,&u->appendclientbytes);
+            logx(LOG_PACKETS,"Packet APPENDED!\n");
+            u->dumpsentpacket=1;
+        }
+    }
+
+    if(u->fakedisablecnt>0) u->fakedisablecnt--;
+    else if(u->fakeping)
+    {
+        u->fakedisablecnt=0;
+        if(u->fakeping<=-50)
+        {
+            seqrecv=u->server_lastthis-(u->fakeping+100);
+        }
+        else
+        {
+            seqrecv-=u->fakeping;
+        }
+    }
+
+    writeheader(outbuf,&seqthis,&seqrecv,&bitthis,&bitrecv);
+
+    if(u->compress_test)
+    {
+#ifdef COMPRESS
+        static char tmp[4096];
+        int         tmpbytes;
+
+        u->compress_client=1;
+
+        tmpbytes=outbytes;
+        memcpy(tmp,outbuf,tmpbytes);
+
+        compress(u,tmp,&tmpbytes,0);
+        c_hexdump("CTEST client compressed:",tmp,tmpbytes,u);
+
+        u->packetclientoutsize[u->packetclientsizecnt]=tmpbytes;
+
+        uncompress(u,tmp,&tmpbytes,0);
+        c_hexdump("CTEST client uncompressed:",tmp,tmpbytes,u);
+
+        u->packetclientinsize[u->packetclientsizecnt]=tmpbytes;
+
+        u->compress_client=0;
+
+        if(outbytes!=tmpbytes)
+        {
+            logx(LOG_PACKETS,"\nCTEST COMPRESS MISMATCH!\n");
+        }
+        logx(LOG_PACKETS,"\n");
+
+        if(u->compress_test>1)
+        {
+            outbytes=tmpbytes;
+            memcpy(outbuf,tmp,outbytes);
+        }
+#endif
+    }
+    else
+    {
+        u->packetclientinsize[u->packetclientsizecnt]=outbytes;
+#ifdef COMPRESS
+        compress(u,outbuf,&outbytes,0);
+#endif
+        u->packetclientoutsize[u->packetclientsizecnt]=outbytes;
+    }
+
+    c_sendserver(u,outbuf,outbytes);
+
+    logx(LOG_DEBUG,"send    %02X %02X time %i\n",outbuf[0],outbuf[4],u->time);
+
+    if(u->state==STATE_DROPPING)
+    {
+        c_sendserver(u,outbuf,outbytes);
+        c_sendserver(u,outbuf,outbytes);
+        // send thanks
+        outbytes=8;
+        seqthis=u->sequence+1;
+        bitthis=0;
+        writeheader(outbuf,&seqthis,&seqrecv,&bitthis,&bitrecv);
+        if(u->noappendnow>0) u->noappendnow--;
+        else
+        {
+            if(u->appendserverbytes)
+            {
+                appendpacket(outbuf,&outbytes,u->appendserver,&u->appendserverbytes);
+                logx(LOG_PACKETS,"Packet APPENDED!\n");
+                u->dumpsentpacket=1;
+            }
+        }
+        c_sendclient(u,outbuf,outbytes);
+    }
+}
+
+void q_proxy(User *u,uchar *inbuf,int inbytes)
+{
+    int seqthis,seqrecv;
+    int bitthis,bitrecv;
+    int outbytes;
+
+    // handle input client packet
+
+    u->amconnected=0;
+    u->server_ping=0;
+
+    uncompress(u,inbuf,&inbytes,0);
+
+    readheader(inbuf,&seqthis,&seqrecv,&bitthis,&bitrecv);
+
+    if(seqthis!=-1)
+    {
+        u->sequence=seqthis+1;
+
+        u->client_lastthis=seqthis;
+        u->client_lastrecv=seqrecv;
+
+        if(bitthis) u->server_hiddenbit^=0x80;
+        u->client_hiddenbit=bitrecv;
+    }
+
+    if(u->state==STATE_NAMELOOKUP && u->time>=u->time_namelookupmsg)
+    {
+        q_print(u,"performing namelookup...\n");
+        u->time_namelookupmsg=u->time+2000;
+    }
+
+    handleclient(u,inbuf,inbytes,outbuf,&outbytes);
+
+    // create output server packet
+
+    outbytes=8;
+    appendpacket(outbuf,&outbytes,u->appendserver,&u->appendserverbytes);
+
+    seqthis=u->sequence;
+    bitrecv=u->server_hiddenbit;
+    bitthis=0;
+    if(u->server_sendsecure)
+    {
+        bitthis=0x80;
+        u->server_sendsecure=0;
+    }
+
+    if(seqthis!=-1)
+    {
+        u->server_lastthis=seqthis;
+        u->server_lastrecv=seqrecv;
+    }
+
+    writeheader(outbuf,&seqthis,&seqrecv,&bitthis,&bitrecv);
+
+    compress(u,outbuf,&outbytes,1);
+
+    u->dumpsentpacket=1;
+    c_sendclient(u,outbuf,outbytes);
+}
+
+void q_connect(uchar *inbuf,int inbytes,SOCKADDR_IN *from)
+{
+    uchar  buf[MAXSTR];
+    User  *u;
+    if(!memicmp(inbuf+4,"getchal",7) )
+    { // challenge to proxy
+        u=c_finduser(from);
+        if(!u)
+        { // new user
+            u=c_newuser(from);
+            if(!u)
+            { // server full
+                c_textaddress(buf,from);
+                logx(LOG_CONSOLE,"Proxy full (connect attempt from %s)\n",buf);
+                // send connect reply
+                buf[0]=255;
+                buf[1]=255;
+                buf[2]=255;
+                buf[3]=255;
+                strcpy(buf+4,"nproxy is full\n");
+                c_sendto(from,buf,strlen(buf));
+                return;
+            }
+            else
+            {
+                strcpy(u->name,"<challenge>");
+                c_textaddress(buf,from);
+                logx(LOG_VERBOSE,"Challenge: from %s (total %i users)\n",buf,usernum);
+            }
+        }
+        else
+        {
+            //c_textaddress(buf,from);
+            //logx(LOG_VERBOSE,"Dup connect ignored: %s from %s\n",u->name,buf);
+            return;
+        }
+        challenge+=7;
+        // send connect reply
+        buf[0]=255;
+        buf[1]=255;
+        buf[2]=255;
+        buf[3]=255;
+        sprintf(buf+4,"c%i",challenge);
+        c_sendclient(u,buf,strlen(buf));
+        u->state=STATE_DROPPING; // this connection no inter needed
+    }
+    else if(!memicmp(inbuf+4,"status",6) || !memicmp(inbuf+4,"ping",4) || inbuf[4]=='k')
+    { // status / ping
+        int i,isping=memicmp(inbuf+4,"status",6);
+        u=c_finduser(from);
+        if(!u)
+        { // new user
+            u=c_newuser(from);
+            if(!u)
+            {
+                return;
+            }
+            else
+            {
+                strcpy(u->name,"<status>");
+                c_textaddress(buf,from);
+                logx(LOG_VERBOSE,"Status: from %s\n",buf);
+            }
+        }
+        // generate hostname
+        if(!*proxy_name)
+        {
+            gethostname(proxy_name,255);
+            strlwr(proxy_name);
+        }
+        // send connect reply
+        buf[0]=255;
+        buf[1]=255;
+        buf[2]=255;
+        buf[3]=255;
+        if(isping)
+        {
+            c_sendclient(u,buf,4);
+        }
+        else
+        {
+            int min,ping;
+            sprintf(buf+4,"n\\support\\QW 2.1,2.2,2.3\\info\\QuakeWorld proxy\\maxclients\\%i\\*version\\%s\\hostname\\%s - %s\n",
+                    maxusernum,version,versionname,proxy_name);
+            for(i=0;i<usernum;i++) if(u!=user+i)
+            {
+                ping=user[i].server_ping;
+                ping=0;
+                min=(user[i].time-user[i].time_connect)/1000/60;
+                c_textaddress(tmpbuf,&user[i].servermainaddr);
+                if(user[i].state==STATE_HERE) strcpy(tmpbuf,"idle");
+                sprintf(buf+strlen(buf),"0 0 %i %i \"%s\" \"%s\" %i %i\n",
+                    min,ping,user[i].name,tmpbuf,user[i].tcolor,user[i].bcolor);
+            }
+            c_sendclient(u,buf,strlen(buf)+1);
+        }
+        u->state=STATE_DROPPING; // this connection no inter needed
+    }
+    else if(!memicmp(inbuf+4,"connect ",8))
+    { // connect to proxy
+        c_textaddress(buf,from);
+        u=c_finduser(from);
+
+        if(!u)
+        { // new user
+            if(usernum>=maxusernum) u=NULL;
+            else u=c_newuser(from);
+            if(!u)
+            { // server full
+                logx(LOG_CONSOLE,"Proxy full (connect attempt from %s)\n",buf);
+                // send connect reply
+                buf[0]=255;
+                buf[1]=255;
+                buf[2]=255;
+                buf[3]=255;
+                strcpy(buf+4,"nproxy is full\n");
+                c_sendto(from,buf,strlen(buf));
+                u->state=STATE_DROPPING; // this connection no inter needed
+                return;
+            }
+            else
+            {
+                int deny=0;
+                uchar *p;
+                p=inbuf+4+8;
+                if(*p=='\"')
+                {
+                    u->version=21;
+                    strcpy(u->clientinfo,p);
+                }
+                else
+                {
+                    while(*p<=32 && *p) p++;
+                    u->response_a=atoi(p);
+                    while(*p>32) p++;
+                    while(*p<=32 && *p) p++;
+                    u->response_b=atoi(p);
+                    while(*p>32) p++;
+                    while(*p<=32 && *p) p++;
+                    u->response_c=atoi(p);
+                    while(*p>32) p++;
+                    while(*p<=32 && *p) p++;
+
+                    if(u->response_a<=26) u->version=22;
+                    else if(u->response_a<=27) u->version=23;
+                    else u->version=24;
+
+                    strcpy(u->clientinfo,p);
+                }
+
+                extractfield(tmpbuf,u->clientinfo,"topcolor\\");
+                u->tcolor=atoi(tmpbuf);
+                extractfield(tmpbuf,u->clientinfo,"bottomcolor\\");
+                u->bcolor=atoi(tmpbuf);
+                extractfield(u->name,u->clientinfo,"name\\");
+
+                extractfield(tmpbuf,u->clientinfo,"password\\");
+
+                if(*proxy_password && strcmp(tmpbuf,proxy_password))
+                {
+                    logx(LOG_CONSOLE,"Connection not allowed (wrong password from %s)\n",buf);
+                    // send connect reply
+                    buf[0]=255;
+                    buf[1]=255;
+                    buf[2]=255;
+                    buf[3]=255;
+                    strcpy(buf+4,"nincorrect password. connection not allowed.\n");
+                    c_sendto(from,buf,strlen(buf));
+                    u->state=STATE_DROPPING; // this connection no inter needed
+                    return;
+                }
+
+                deny=0;
+                if(*proxy_allowip && memicmp(buf,proxy_allowip,strlen(proxy_allowip))) deny=1;
+                if(proxy_denyipnum)
+                {
+                    int i;
+                    for(i=0;i<proxy_denyipnum;i++)
+                    {
+                        if(memicmp(buf,proxy_denyip[i],strlen(proxy_denyip[i])))
+                        {
+                            deny=1;
+                            break;
+                        }
+                    }
+                }
+                if(deny)
+                {
+                    logx(LOG_CONSOLE,"Connection not allowed (connect attempt from %s)\n",buf);
+                    // send connect reply
+                    buf[0]=255;
+                    buf[1]=255;
+                    buf[2]=255;
+                    buf[3]=255;
+                    strcpy(buf+4,"nconnection not allowed from your ip address\n");
+                    c_sendto(from,buf,strlen(buf));
+                    u->state=STATE_DROPPING; // this connection no inter needed
+                    return;
+                }
+
+                u->showtime=0;
+                u->campthese=0;
+//                u->reportthese=0;
+//                u->pickupthese=0;
+                u->time_nextcamp=0;
+                u->linesdown=11;
+                u->linesright=36;
+                u->time_namelookupmsg=0;
+                u->sequence=0;
+
+                logx(LOG_CONSOLE,"Connect: %s from %s (ver %i, users %i)\n",u->name,buf,u->version,usernum);
+            }
+        }
+        else
+        {
+            //logx(LOG_VERBOSE,"Dup connect ignored: %s from %s\n",u->name,buf);
+            return;
+        }
+        // send connect reply
+        buf[0]=255;
+        buf[1]=255;
+        buf[2]=255;
+        buf[3]=255;
+        buf[4]='j';
+        c_sendclient(u,buf,5);
+        u->state=STATE_INIT;
+    }
+    else
+    {
+        // skip
+        if(loglevel>=LOG_DEBUG)
+        {
+            c_textaddress(buf,from);
+            logx(LOG_DEBUG,"Unrecognized connect attempt from %s.\n",buf);
+        }
+    }
+}
+
diff --git a/proxy.h b/proxy.h
new file mode 100755 (executable)
index 0000000..fef539e
--- /dev/null
+++ b/proxy.h
@@ -0,0 +1,812 @@
+// Cheapo - a QuakeWorld Proxy
+// Copyright (C) 1998 Sami Tammilehto
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+//---------------- configuration -------------------------------
+
+//#define PROMODE
+// enable debug/development features:
+// - .debug command (includes misc stuff and cheat tests)
+// - enables logging levels 3-5 (4+=full protocol log to proxy.log)
+// - timing for all items (not just powerups)
+// note: compiling with promode results in a different f_modified checksum.
+
+//#define RLPACK
+// If you point to a pack left by a player that you saw fired
+// a rocket just before death, the pack is tagged as 'rl-pack'.
+// This proved way too inaccurate (like 1 pack in 20 with luck
+// would be classified rl-pack) for practical use, and was also
+// too cheatish, so it wasn't published or used.
+
+//#define IDENT
+// enable ident of teammates. Pointing a teammate and pressing '.ident'
+// shows stats for that player (f_report) in your hud without usin
+// messages. This feature didn't prove too useful, is complicated,
+// and requires everyone uses Cheapo so it was never published.
+
+//#define POWERDROP
+// allows .rep power2 which reports separately when powerups are
+// picked and when they time out. Decided not to make this public
+// since at the time powerup timers were going away.
+
+//#define COMPRESS
+// enable compression. Also requires PROMODE (.debug com enables)
+// Compress.c only contains compression stubs (compress_raw and
+// uncompress_raw) that don't really compress anything. I didn't
+// have time to develop a compression protocol, so only the
+// framework is in place. I did find out that you need a very
+// specialized algorithm (since the packets are so small and
+// they may be dropped) like the excellent one in Qizmo to get
+// any useful results.
+
+//#define SPEED
+// '.show speed' and '.show time' commands which allow for
+// timing routes and checking movement speed (buggy)
+
+//#define TIMERS
+// enable powerup timers
+
+//#define MAINPROFILE
+// enable some performance profiling
+
+//#define EXPIRETIME 920316146  // 1.3.1998
+// expiration time, used for beta versions
+
+//#define PROTECT
+// enable f_modified, f_version, exe integrity check
+// (code for these is not present in open version)
+
+//#define UNIXALPHA
+// use settings suitable for Alpha unix
+// Windows/unix selection is based on define WIN32 which
+// must be used in windows.
+
+//---------------- other defines based on above ----------------
+
+#ifdef PROMODE
+#define PACKETLOG
+// enable logging of qw protocol in decoded form
+#endif
+
+#ifndef WIN32
+
+#ifdef UNIXALPHA
+#define VERCODE "a"
+#define ALPHA
+#undef PROTECT
+#else
+#define VERCODE "l"
+#endif
+
+// what to use for real time clock
+
+// UNIX settings
+#define GETTIMEOFDAY // linux: use gettimeofday system call
+//#define FTIME     // alpha: use ftime system call
+//#define HRTIME    // sparc: use sparc high resolution timer
+#define HILO        // all: unknown byte ordering
+
+#else
+
+#define VERCODE "w"
+
+// what to use for real time clock
+
+// WINDOWS settings
+//#define FTIME       // use ftime system call
+//#define WINTIME     // use windows getcurrenttime
+#define PERFTIME      // use performance counter
+
+#endif
+
+//----------------------------------------------------------------
+
+#ifdef WIN32
+
+#include <windows.h>
+#include <sys/timeb.h>
+
+#else
+
+/*#include <sys/timeb.h>*/
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include <unistd.h>
+
+#define SOCKET        int
+#define SOCKET_ERROR  -1
+#define SOCKADDR_IN   struct sockaddr_in
+#define DWORD         unsigned int
+#define INVALID_SOCKET -1
+#define LPSOCKADDR    struct sockaddr *
+#define closesocket   close
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <time.h>
+#include <string.h>
+#include <math.h>
+
+typedef unsigned char  uchar;
+
+#define LOG_CONSOLE  1    // messages also shown at console
+#define LOG_VERBOSE  2    // more info
+#define LOG_DEBUG    3    // debug messages
+#define LOG_PACKETS  4    // packet traffic dump
+#define LOG_FLUSH    5    // packet traffic dump with fflush
+#define MAXPLAYERS     16
+#define MAXUSERS       (MAXPLAYERS+2)
+
+#define CLIENTINFOSIZE 512
+#define APPENDSIZE     1024  // max data to append to packets
+#define MAXSTR         256
+#define BUFSIZE        16384 // network buffers
+#define MAXPLAYERNAME  32
+#define MAXSKINNAME    32
+#define MAXPROXYNAME   32
+
+#define MAXMODELS      256
+#define MAXSOUNDS      256
+#define MAXENTITIES    512
+#define STATNUM        256 // pinghistory
+#define MAXCAMP        256 // number of objects
+
+#define TIMEOUT        60000 // 60 sec timeout
+#define MAXERRORS      64 // if more than this net errors, autodisconnect
+
+#define SEQUENCES      8 // impulse sequences
+
+#define TOOKRADIUS     900
+#define NEARDIST       6000 // %o,%e
+
+#define CAMP_P         0x0001
+#define CAMP_Q         0x0002
+#define CAMP_R         0x0004
+#define CAMP_LASTPOWER 0x0004
+#define CAMP_MH        0x0008
+#define CAMP_RA        0x0010
+#define CAMP_YA        0x0020
+#define CAMP_GA        0x0040
+#define CAMP_SUIT      0x0080
+#define CAMP_LASTARMOR 0x0080
+#define CAMP_RL        0x0100
+#define CAMP_LG        0x0200
+#define CAMP_GL        0x0400
+#define CAMP_LASTAUTOMARK 0x0400
+#define CAMP_SNG       0x0800
+#define CAMP_NG        0x1000
+#define CAMP_SSG       0x2000
+#define CAMP_RUNE      0x4000  // also key and flag
+#define CAMP_HEALTH    0x8000
+#define CAMP_CELLS     0x10000
+#define CAMP_ROCKETS   0x20000
+#define CAMP_NAILS     0x40000
+#define CAMP_SHELLS    0x80000
+#define CAMP_PLAYER    0x100000
+#define CAMP_GLOW      0x100000 // glowing player
+#define CAMP_PACK      0x200000
+#define CAMP_POINTED   0x400000 // last pointed item
+#define CAMP_ENEMY     0x800000
+#define CAMP_LAST      0x1000000
+
+#define CAMP_DEF     (CAMP_PLAYER|CAMP_RA|CAMP_GA|CAMP_YA|CAMP_MH|CAMP_Q|CAMP_R|CAMP_P|CAMP_PACK|CAMP_RL|CAMP_GL|CAMP_LG|CAMP_RUNE|CAMP_SNG|CAMP_ROCKETS)
+#define CAMP_DEFTOOK (CAMP_RA|CAMP_YA|CAMP_RL|CAMP_LG|CAMP_RUNE)
+#define CAMP_POWER   (CAMP_R|CAMP_P|CAMP_Q)
+
+// quakeworld 32 bit items word
+#define ITEMS_SG       0x00000001
+#define ITEMS_SSG      0x00000002
+#define ITEMS_NG       0x00000004
+#define ITEMS_SNG      0x00000008
+#define ITEMS_GL       0x00000010
+#define ITEMS_RL       0x00000020
+#define ITEMS_LG       0x00000040
+#define ITEMS_GA       0x00002000
+#define ITEMS_YA       0x00004000
+#define ITEMS_RA       0x00008000
+#define ITEMS_MEGA     0x00010000
+#define ITEMS_SILVER   0x00020000
+#define ITEMS_GOLD     0x00040000
+#define ITEMS_R        0x00080000
+#define ITEMS_P        0x00100000
+#define ITEMS_SUIT     0x00200000
+#define ITEMS_Q        0x00400000
+#define ITEMS_RESIST   0x10000000
+#define ITEMS_STRENGTH 0x20000000
+#define ITEMS_HASTE    0x30000000
+#define ITEMS_REGEN    0x40000000
+#define ITEMS_FLAG     (ITEMS_SILVER|ITEMS_GOLD) // flag=keys
+
+typedef struct Camp_t Camp;
+struct Camp_t
+{
+    int    entity; // entity number
+    int    pos[3];
+    int    delay;  // re appear delay
+    int    category;
+    int    secondcamp;
+    DWORD  appeartime; // 0=unknown
+    uchar  name[4];
+    uchar  genericname[8];
+    uchar *pickupsound;
+};
+#define UNKNOWNTIME  0xffffffff
+#define UNKNOWNTIME2 0xfffffffe
+
+typedef struct Location Location;
+struct Location
+{
+    int   x,y,z;
+    char *name;
+};
+
+typedef struct User User;
+struct User
+{
+    // communication
+    char         proxyhostname[16]; // max 10 chars+port actually
+    SOCKET       toserver;
+    //SOCKET       toclient;
+    SOCKADDR_IN  toserveraddr;
+    SOCKADDR_IN  toclientaddr;
+    SOCKADDR_IN  servermainaddr;     // server 27500 port
+    SOCKADDR_IN  lastservermainaddr; // last server 27500 port
+    int          safemode;       // just forward packets, no parsing
+    int          sequence;       // last received from client
+    int          connectstartsequence; // sequence when connect initiated
+    int          server_lastthis;
+    int          server_lastrecv;
+    int          server_hiddenbit;
+    int          server_sendsecure; // command flag
+    int          client_lastthis;
+    int          client_lastrecv;
+    int          client_hiddenbit;
+    int          client_sendsecure; // commnd fla
+    int          server_ping;
+    int          errors; // in net traffic;
+    int          version; // 21 or 22
+    int          dumpsentpacket;
+    int          linkedserver; // linked to a serverside proxy
+    int          linkedclient; // linked to a clientside proxy
+    int          choke; // for last server packet
+
+    int          noappendnow;
+    int          quiet;
+
+    int          challenge;
+    int          havechallenge;
+    int          response_a;  // version
+    int          response_b;  // portid
+    int          response_c;  // challenge
+    int          clientident; // 16 bit client ident
+    // append data to packets
+    int          appendclientbytes;
+    uchar        appendclient[APPENDSIZE];
+    int          appendserverbytes;
+    uchar        appendserver[APPENDSIZE];
+    // msg buffers for centerprinting
+    uchar        msgline[64];
+    uchar        notice[64]; // near center of screen
+    // timed events & state
+    DWORD        time; // current time in milliseconds (set by main)
+    DWORD        time_connect;
+    DWORD        time_forcedisconnect;
+    DWORD        time_lastserver; // last msg from server (timeout detect)
+    DWORD        time_lastclient; // last msg from client (timeout detect)
+    DWORD        time_nextcamp;   // next camping info on screen
+    DWORD        time_nexttarget; // next target check
+    DWORD        time_nextreport; // next report
+    DWORD        time_reskinall;
+    DWORD        time_lastfreport;
+    DWORD        time_sendfreport;
+    DWORD        time_namelookupmsg;
+    DWORD        time_nexttry;    // try next connect at this time
+    DWORD        time_lastenemymask;
+    DWORD        time_lastoverlay;
+    DWORD        time_lasttook;
+    DWORD        time_lastpoint;
+    DWORD        time_nextcpps;   // ok to send client packet at this time
+    DWORD        time_cppsdelta;  // minimum timedelta between packets
+    DWORD        time_died;
+    DWORD        time_lastfversion;
+    DWORD        time_lastsay[4];
+    int          state;
+// disconnecting/disconnected from proxy
+#define STATE_DROPPING     -1
+// connecting to proxy
+#define STATE_INIT          0
+// connected to proxy
+#define STATE_HERE          1
+// doing namelookup from connecthere=>servermainaddr
+#define STATE_NAMELOOKUP    2
+// trying to connect to servermainaddr
+#define STATE_TRYING        3
+// connected to a server
+#define STATE_CONNECTED     4
+// dropping connection
+#define STATE_DISCONNECTING 5
+    // connecting control variables
+    int          doconnect;           // when in STATE_HERE, connect to addr below
+    int          donew;
+    int          isreconnect;         // this connect is a reconnect
+    uchar        connecthere[MAXSTR]; // ip address for next connect
+    int          connectingcount;     // try this many times
+    int          challengecount;      // if 0 say server not responding
+    int          dropcount;           // how long to wait for server disconnect after drop
+    int          firsttry;
+    int          servertimeoutmsg;    // flag
+    int          parsecount;
+    // operation modes
+    uchar        proxysay[32]; // "say proxy:"
+    uchar        proxysay2[32]; // "say <host>:"
+    int          fps_explosion;
+    int          fps_powerup;
+    int          fps_muzzle;
+    int          fps_gib;
+    int          fps_rocket;
+    int          fps_setinfo; // tbd
+    int          fps_nails;
+    int          fps_damage;
+    int          usepercentn;
+    int          campthese;
+    int          pointshow;
+    int          pointtell;
+    int          remindcamp;
+    int          secondcamp;
+    int          cpps;
+    int          linesdown;
+    int          linesright;
+    int          linecenter;
+    int          tellteam;
+    int          floodprot;
+    int          specmode;
+    int          specentity;
+    int          bestweapon;
+    int          autofreport;
+    int          autoihave;
+    int          autoitook;
+    int          autoitookitems;
+    int          autoqpicked;
+    int          moveoverlay;
+    int          soundtrigger;
+    int          classifypacks;
+    int          fmodified;
+    int          forcedmm; // 0=no force (use server)
+//    int          reportthese;
+//    int          reporttime; // report this many seconds before item
+//    int          pickupthese;
+    char         impulseorder[SEQUENCES][16]; // modifiers for impulses (first number is impulse, 0=unused)
+    int          notimers;
+
+    // skin settings
+    int          skinoverride;
+    int          coloroverride;
+    int          skinoverridepos; // for sending a few skins per packet
+    int          rankoverridepos; // for sending a few skins per packet
+    char         skinname[16];    // ours
+    char         skinname2[16];   // others
+    int          color;
+    int          color2;
+
+    // compress settings
+    int          compress_client;
+    int          compress_server; // set if incoming compressed client messages
+    int          compress_test;
+
+    // debug settings
+    int          nofversion;
+    int          fakeping;
+    int          fakedisablecnt;
+    int          zztest;
+    int          showtime;
+    int          showspeed;
+    int          showhud;
+    DWORD        showtimebase;
+    int          showdist;
+    int          showdistbase[3];
+    int          hideaxemen;
+    int          turbo;
+    int          fastmove;
+    int          shoot;
+    int          stoptime;
+
+    // infomessages
+    DWORD        time_request;
+    int          requestfrom; // 0=nothing now
+    int          clearrequest;
+    char         requestformat[64];
+
+    // client info
+    int          amconnected; // to a server
+    int          amspectator;
+    uchar        overlay[256]; // server text overlay
+    int          overlayactive; // our overlay
+    uchar        name[MAXSTR];
+    int          team,zero2;
+    int          tcolor,bcolor;
+    int          lastbegin; // last begin x command
+    uchar        clientinfo[CLIENTINFOSIZE];
+    int          entity;
+    int          health;
+    int          armor;
+    int          weapon;
+    int          ammo;
+    int          ammo_shells;
+    int          ammo_nails;
+    int          ammo_rockets;
+    int          ammo_cells;
+    int          items;
+    int          pos[3]; // z=altitude (pos up)
+    int          ang[3]; // 16bit; 0=tilt (negative up), 1=yaw (negative left)
+    int          speed[4];
+    int          speedlastthis;
+    int          speedcnt;
+    char         filter[16][16];
+    int          filternum;
+
+    int          diedatloc; // near what loc you died last time
+    int          diedatown;
+    int          diedatenemy;
+    int          lastreportedloc;
+
+    int          power; // powerups, smae bits as items
+    int          lastpower; // same bits as items
+    int          lastnonzeropower; // same bits as items
+
+    // history of actions
+    uchar        lasttarget[64];
+    uchar        lastpoint[64];
+    uchar        lasttook[64];
+    int          lastentitypicked;
+    int          lastentitypointed;
+
+    // how many soundlists / modellists received
+    int          cnt_sound;
+    int          cnt_model;
+
+    int          lastflag; //fire/jump
+    int          impulsehistory[3];
+
+    int          client_spectrack;
+    // level info
+    int          changing;
+    int          fortress;
+    int          fpdcopy;
+    int          dmm;
+    int          originaldmm;
+    int          dmmitemmask;
+    int          ctf;
+    int          fpd;
+#define FPD_PERCENT      0x01
+#define FPD_TIMERS       0x02
+#define FPD_SOUND        0x04
+#define FPD_PERCENTE     0x20
+#define FPD_POINT        0x80
+#define FPD_SKIN         0x100
+#define FPD_COLOR        0x200
+#define FPD_ITEM         0x400  // not implemented right now
+    int          wherecrc;
+    int          models;
+    uchar       *model[MAXMODELS];
+    int          grenademodel;
+    int          pack2model;
+    int          sounds;
+    uchar       *sound[MAXMODELS];
+    int          explosionsound;
+    int          camps;
+    Camp         camp[MAXCAMP];
+    int          campcnt[16]; // 3=ring, 4=pent, 5=quad
+    char         gamedir[MAXSTR];
+    // dynamically allocated locations
+    int          locations;
+    int          locationsmax; // this many allocated
+    Location    *loc;
+    uchar        locname[MAXSTR];
+    // tracking smooth
+    int          tr_pos[3];
+    int          tr_ang[3];
+    int          tr_postar[3];
+    int          tr_angtar[3];
+    // other players info (indexed by entity, not player number)
+    uchar        playername[34][MAXPLAYERNAME];
+    uchar        playerskin[34][MAXSKINNAME];
+    uchar        playerproxy[34][MAXPROXYNAME];
+    uchar        playerteamtext[34][8];
+    int          playerfps[34];
+    int          playerteam[34];
+    int          playerbcolor[34];
+    int          playertcolor[34];
+    int          playerxcolor[34];
+    int          playerrcolor[34]; // rank color
+    int          playerfrags[34];
+    int          playertime[34];
+    int          playerfragsbest; // leader frags
+    int          playernear[34];
+    int          playerglow[34]; // low 4 bits=weapon!?
+    int          playeractive[34]; // 0 for spec / unused players
+    int          playerpos[34][3];
+    int          playerang[34][3];
+
+    int          trackloc;
+    int          tracklocind;
+    int          playerloc[34];
+    int          playerlastloc[34];
+
+    int          numplayers; // 1+last active player
+    DWORD        time_playerrocket[34]; // last time rocket fired
+    DWORD        time_playerdied[34];
+    int          lastenemymask; // powerups
+    // visible entities
+    char         entitygib[MAXENTITIES];
+    char         entityvisible[MAXENTITIES];
+    uchar *      entityname[MAXENTITIES];
+    int          entitypos[MAXENTITIES][3];
+    int          entitymask[MAXENTITIES];
+
+    // statistics
+    int          packetsendtime[STATNUM];
+    int          packetrecvtime[STATNUM];
+    int          packetchoke[STATNUM];
+    int          packetserversizecnt;
+    int          packetserversize[16];
+    int          packetclientsizecnt;
+    int          packetclientsize[16];
+    int          cppsdrop,cppspass;
+    int          outoforder;
+    int          outoforder30;
+
+    // client sizes measured at output
+    int          packetclientinsize[16];  // compressed
+    int          packetclientoutsize[16]; // compressed
+    // server sizes measured at input
+    int          packetserverinsize[16];  // compressed
+    int          packetserveroutsize[16]; // compressed
+
+    int          lastpacketrecv;
+
+    int          bytestoserver;
+    int          bytestoclient;
+    DWORD        time_bytestosec;
+    int          bytestoserversec1; // tmp counters
+    int          bytestoclientsec1; // tmp counters
+    int          bytestoserversec; // val for last full second
+    int          bytestoclientsec; // val for last full second
+    int          bytestoserversecmax; // val for last full second
+    int          bytestoclientsecmax; // val for last full second
+};
+
+extern int   litemode; // = FPD mask
+extern int   hpymode;
+extern int   usernum;
+extern int   maxusernum;
+extern int   newsocketmode;
+extern User  user[MAXUSERS];
+
+typedef struct
+{
+    int     type;
+#define USERINFO_REPLY        0x00
+#define USERINFO_REQUEST      0x01
+#define USERINFO_PICKUP       0x02
+#define USERINFO_EXTENDED     0x07
+#define USERINFO_ERROR        0xff
+    int     requestfrom;
+    int     requestlong;
+    // sent in short
+    int     armor;
+    int     armortype; // 0=none, 1=green, 2=yellow, 3=red
+    int     health;
+    int     bestweapon;
+    int     bestammo;
+    int     items;
+    int     power;
+    // sent in long
+    int     x,y,z;
+    // not sent right now
+    int     weapon;
+    int     ammo;
+
+    int     own;
+    char   *name;
+} Userinfo;
+
+void q_command(User *u,uchar *s);
+
+void q_connect(uchar *inbuf,int inbytes,SOCKADDR_IN *from); // data to proxy connect port
+void q_client(User *u,uchar *buffer,int bytes);   // data to client
+void q_server(User *u,uchar *buffer,int bytes);   // data to server
+void q_proxy(User *u,uchar *uffer,int bytes);     // data to proxy (server before connect)
+
+int  packetmatch(User *u,uchar *buffer);
+
+int   c_islocalconnect(SOCKADDR_IN *a);
+void  c_hexdump(uchar *msg,uchar *buffer,int bytes,User *u);
+User *c_finduser(SOCKADDR_IN *from);
+User *c_newuser(SOCKADDR_IN *from);
+void  c_killuser(User *u);
+void  c_killserver(User *u);
+void  c_reopenserver(User *u);
+int   c_sameaddress(SOCKADDR_IN *a,SOCKADDR_IN *b);
+void  c_setaddress(SOCKADDR_IN *a,uchar *text);
+void  c_textaddress(uchar *text,SOCKADDR_IN *a);
+void  c_sendservermain(User *u,uchar *data,int bytes);
+void  c_sendserver(User *u,uchar *data,int bytes);
+void  c_sendclient(User *u,uchar *data,int bytes);
+void  c_sendto(SOCKADDR_IN *a,uchar *data,int bytes);
+
+void  logx(int level,uchar *txt,...);
+
+int   intat(uchar *a);
+
+//-----------------------------------------------
+
+char *_first_(void);
+char *_last_(void);
+
+//------------- stuff in main.c -----------------
+
+extern User  *globalu;
+extern int loglevel;
+extern int lognpackets;
+extern int oldloglevel;
+
+extern uchar *version;
+extern uchar *version2;
+extern uchar *versionname;
+extern uchar *versiontype;
+extern int    filecrc[2];
+
+void expires_gettime(void);
+void expires_startup(void);
+void expires_check(void);
+
+void *mymalloc(int size);
+void  myfree(void *p);
+
+void codecrc(char *salt,char *result); // 16 byte result
+void statecrc(User *u,char *result); // 16 byte result
+
+//------------- stuff in proxy.c -----------------
+
+extern int   proxy_port;
+extern int   challenge;
+extern uchar proxy_allowip[MAXSTR];
+extern uchar proxy_denyip[16][MAXSTR];
+extern int   proxy_denyipnum;
+extern uchar proxy_name[MAXSTR];
+extern uchar proxy_password[MAXSTR];
+extern char  quakepath[MAXSTR];
+extern int   quakehandle;
+extern char  quakeoptions[MAXSTR];
+extern int   quakemodified;
+extern int   gamespy;
+extern uchar tmpbuf[BUFSIZE];
+extern uchar outbuf[BUFSIZE];
+int handlesaying(User *u,Userinfo *ui,uchar **d0,uchar **s0);
+
+void writeheader(uchar *inbuf,int *seqthis,int *seqrecv,int *bitthis,int *bitrecv);
+void readheader(uchar *inbuf,int *seqthis,int *seqrecv,int *bitthis,int *bitrecv);
+
+//------------- stuff in misc.c -----------------
+
+void strxor80(char *t);
+void stror80(char *t);
+void strand80(char *t);
+int  memcmp7f(char *a,char *b,int len);
+
+void   synccamp(User *u,int sync,int num);
+
+void   userinfofromuser(Userinfo *ui,User *u);
+void   gotinfomessage(User *u,uchar *msg,int entity);
+void   sendinfomessage(User *u,uchar *msg);
+void   sendsetinfo(User *u,uchar *msg);
+void   decryptinfo(Userinfo *ui,uchar *datain);
+uchar *encryptinfo(Userinfo *ui);
+
+int    checksum(char *packetstart,char *data,int size);
+void   saveparseerror(char *text,int pos,uchar *buf,int bytes);
+int    funnyconvert(uchar *d,uchar *s,int nocolor);
+uchar *formatqstring(uchar *in);
+uchar *extractfield(uchar *dest,uchar *src,uchar *field);
+void   funnyhelp(User *u);
+
+void   q_growlocmax(User *u,int num);
+void   q_setlocname(User *u,int i,char *name);
+void   q_loadloc(User *u,uchar *mapname);
+void   q_savelocs(User *u);
+
+void   q_print(User *u,uchar *text);
+void   q_printf(User *u,uchar *text,...);
+void   q_explode(User *u,int type,int x,int y,int z);
+void   q_print1(User *u,uchar *text);
+void   q_stufftext(User *u,uchar *text);
+void   q_servercmd(User *u,uchar *text);
+void   q_centerprint(User *u,uchar *text);
+
+char *playerlocdirection(User *u,int pl);
+int    teamint(uchar *a);
+int    is_version(uchar *x);
+int    is_timer(uchar *x);
+int    is_modified(uchar *x);
+void   createversiontext(User *u,uchar *txt);
+int    modifyimpulse(User *u,int *impulse);
+uchar *findnear(User *u,int x,int y,int z,int *ind);
+uchar *findpoint(User *u,int x,int y,int z,int ax,int ay,int az,int targetmask,int flags,int *entitynum);
+#define POINT_ONLYNAME 1
+#define POINT_ONLYPOS  2
+#define POINT_ADDSKIN  4 // fortress only
+#define POINT_TEAMMATENUMBER 8
+#define POINT_DIRECTION 16
+#define POINT_SELF 32
+uchar *findnearestitem(User *u,int x,int y,int z,int targetmask);
+int text2color(char *p);
+char *color2text(int c);
+
+void   rankchange(User *u,int i,int dooverride);
+void   skinoverride(User *u,int i);
+void   spectatenext(User *u,uchar *findname);
+void   cl_clearstats(User *u);
+void   cl_updatestat(User *u,int stat,int value);
+void   cl_setinfo(User *u,uchar *attr,uchar *value);
+Camp  *campadd(User *u,int entity,int *pos,int modelindex,int skin);
+char  *makeentityname(User *u,int modelindex,int skin,int *mask);
+void   reportfpd(User *u,int fpd);
+void setinfo(User *u,int i,char *attr,char *value);
+
+int    floodprot(User *u);
+void   dmmchange(User *u,int newdmm);
+void showcrcerror(int showinternal,int crc8,int crc24);
+//------------- stuff in cmd.c -----------------
+
+void   q_help(User *u,uchar *param);
+void   q_command(User *u,uchar *s);
+
+//------------- misc macros ---------------------
+
+#define SAMETEAM(x) ((!u->fortress && u->team==u->playerteam[x]) || (u->fortress && u->bcolor==u->playerbcolor[x]))
+// you in same team as player x
+#define NEWQW (u->version>=22)
+
+//------------- compress.c ---------------------
+
+void   compresstest(User *u,uchar *buf,int bytes);
+void   compress(User *u,uchar *inbuf,int *inbytes,int server);
+void   uncompress(User *u,uchar *inbuf,int *inbytes,int server);
+
+void count_save(User *u);
+void count_load(User *u);
+
+#ifndef WIN32
+
+extern int mainloop_low(int);
+extern int memicmp(uchar *,uchar *,int);
+extern int strlwr(uchar *);
+extern int stricmp(uchar *,uchar *);
+
+#endif /* !WIN32 */
+
+
+
+
diff --git a/zipit.bat b/zipit.bat
new file mode 100755 (executable)
index 0000000..193b238
--- /dev/null
+++ b/zipit.bat
@@ -0,0 +1 @@
+zip %1 * -x *.obj -x *.exe\r
Copyright 2022-2026 David Polakovic. Individual project licenses are located in project root in full length.

Site generated using Gitweb. Read the documentation for JavaScript and cookie information. Additional source code available here under GPLv3 license.

Server for this subdomain is RFC 2324 compliant