pax_global_header00006660000000000000000000000064142714604100014511gustar00rootroot0000000000000052 comment=599cbb1d402decfed93387ecc6d489474dd6c76d mvdsv-0.35/000077500000000000000000000000001427146041000125775ustar00rootroot00000000000000mvdsv-0.35/.github/000077500000000000000000000000001427146041000141375ustar00rootroot00000000000000mvdsv-0.35/.github/workflows/000077500000000000000000000000001427146041000161745ustar00rootroot00000000000000mvdsv-0.35/.github/workflows/build-and-deploy-release.yml000066400000000000000000000105411427146041000234670ustar00rootroot00000000000000name: build and deploy release on: release: types: [published] jobs: build: if: github.repository == 'QW-Group/mvdsv' runs-on: ${{ matrix.base-image }} strategy: fail-fast: false matrix: target: [linux-amd64, linux-i686, linux-armhf, linux-aarch64, windows-x64, windows-x86] include: - target: linux-amd64 os: linux arch: amd64 base-image: ubuntu-latest build-image: amd64/ubuntu:bionic ext: "" - target: linux-i686 os: linux arch: i686 base-image: ubuntu-latest build-image: i386/ubuntu:bionic ext: "" - target: linux-armhf os: linux arch: armhf base-image: ubuntu-latest build-image: arm32v7/ubuntu:bionic ext: "" - target: linux-aarch64 os: linux arch: aarch64 base-image: ubuntu-latest build-image: arm64v8/ubuntu:bionic ext: "" - target: windows-x64 os: windows arch: x64 base-image: ubuntu-18.04 ext: ".exe" - target: windows-x86 os: windows arch: x86 base-image: ubuntu-18.04 ext: ".exe" steps: - name: Checkout uses: actions/checkout@v2 - name: Prepare Build Environemnt shell: bash run: | if ${{ matrix.os == 'linux' }}; then sudo apt-get update sudo apt-get -y install qemu-system-arm qemu-user-static else sudo apt-get update sudo apt-get -y install build-essential cmake gcc-i686-linux-gnu sudo apt-get -y install gcc-arm-linux-gnueabihf pkg-config-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross sudo apt-get -y install gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 fi - name: Build shell: bash run: | if ${{ matrix.os == 'linux' }}; then docker pull ${{ matrix.build-image }} docker run --net=host --rm --device /dev/fuse -v $PWD:/mvdsv ${{ matrix.build-image }} bash -c -e 'export ARCH=$(dpkg --print-architecture);export DEBIAN_FRONTEND=noninteractive;mkdir -p /etc/apt/apt.conf.d;echo "APT::Install-Recommends "0"; APT::AutoRemove::RecommendsImportant "false";" >> /etc/apt/apt.conf.d/01lean && apt-get -qqy update && apt-get -qqy dist-upgrade && apt-get -qqy install cmake build-essential libcurl4-openssl-dev && ln -sf "$(which make)" /usr/bin/gmake && cd /mvdsv && ./build_cmake.sh ${{ matrix.target }} && chown -R '$(id -u ${USER})':'$(id -g ${USER})' /mvdsv/build/${{ matrix.target }}||exit 3' else ./build_cmake.sh ${{ matrix.target }} fi - name: Create checksum run: | md5sum build/${{ matrix.target }}/mvdsv${{ matrix.ext }} > build/${{ matrix.target }}/mvdsv.md5 - name: Setup SSH env: SSH_AUTH_SOCK: /tmp/ssh_agent.sock run: | ssh-agent -a $SSH_AUTH_SOCK > /dev/null ssh-add - <<< "${{ secrets.SSH_PRIVATE_KEY }}" - name: Set date run: | sudo timedatectl set-timezone Europe/Amsterdam echo "DATE=$(date "+%Y%m%d-%H%M%S")" >> $GITHUB_ENV - name: Deploy env: SSH_AUTH_SOCK: /tmp/ssh_agent.sock run: | mkdir -p upload/releases/${{ github.ref_name }}/${{ matrix.os }}/${{ matrix.arch }} mkdir -p upload/releases/latest/${{ matrix.os }}/${{ matrix.arch }} cp build/${{ matrix.target }}/mvdsv${{ matrix.ext }} upload/releases/${{ github.ref_name }}/${{ matrix.os }}/${{ matrix.arch }}/mvdsv${{ matrix.ext }} cp build/${{ matrix.target }}/mvdsv.md5 upload/releases/${{ github.ref_name }}/${{ matrix.os }}/${{ matrix.arch }}/mvdsv.md5 cp build/${{ matrix.target }}/mvdsv${{ matrix.ext }} upload/releases/latest/${{ matrix.os }}/${{ matrix.arch }}/mvdsv${{ matrix.ext }} cp build/${{ matrix.target }}/mvdsv.md5 upload/releases/latest/${{ matrix.os }}/${{ matrix.arch }}/mvdsv.md5 sftp -rp -o 'StrictHostKeyChecking no' -o 'UserKnownHostsFile /dev/null' -P ${{ secrets.SFTP_PORT }} ${{ secrets.SFTP_USERNAME }}@${{ secrets.SFTP_HOST }}:/releases <<< $'put -rp upload/releases/*' mvdsv-0.35/.github/workflows/build-and-deploy-snapshot.yml000066400000000000000000000105151427146041000237070ustar00rootroot00000000000000name: build and deploy snapshots on: [push] jobs: build: if: github.repository == 'QW-Group/mvdsv' runs-on: ${{ matrix.base-image }} strategy: fail-fast: false matrix: target: [linux-amd64, linux-i686, linux-armhf, linux-aarch64, windows-x64, windows-x86] include: - target: linux-amd64 os: linux arch: amd64 base-image: ubuntu-latest build-image: amd64/ubuntu:bionic ext: "" - target: linux-i686 os: linux arch: i686 base-image: ubuntu-latest build-image: i386/ubuntu:bionic ext: "" - target: linux-armhf os: linux arch: armhf base-image: ubuntu-latest build-image: arm32v7/ubuntu:bionic ext: "" - target: linux-aarch64 os: linux arch: aarch64 base-image: ubuntu-latest build-image: arm64v8/ubuntu:bionic ext: "" - target: windows-x64 os: windows arch: x64 base-image: ubuntu-18.04 ext: ".exe" - target: windows-x86 os: windows arch: x86 base-image: ubuntu-18.04 ext: ".exe" steps: - name: Checkout uses: actions/checkout@v2 - name: Prepare Build Environemnt shell: bash run: | if ${{ matrix.os == 'linux' }}; then sudo apt-get update sudo apt-get -y install qemu-system-arm qemu-user-static else sudo apt-get update sudo apt-get -y install build-essential cmake gcc-i686-linux-gnu sudo apt-get -y install gcc-arm-linux-gnueabihf pkg-config-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross sudo apt-get -y install gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 fi - name: Build shell: bash run: | if ${{ matrix.os == 'linux' }}; then docker pull ${{ matrix.build-image }} docker run --net=host --rm --device /dev/fuse -v $PWD:/mvdsv ${{ matrix.build-image }} bash -c -e 'export ARCH=$(dpkg --print-architecture);export DEBIAN_FRONTEND=noninteractive;mkdir -p /etc/apt/apt.conf.d;echo "APT::Install-Recommends "0"; APT::AutoRemove::RecommendsImportant "false";" >> /etc/apt/apt.conf.d/01lean && apt-get -qqy update && apt-get -qqy dist-upgrade && apt-get -qqy install cmake build-essential libcurl4-openssl-dev && ln -sf "$(which make)" /usr/bin/gmake && cd /mvdsv && ./build_cmake.sh ${{ matrix.target }} && chown -R '$(id -u ${USER})':'$(id -g ${USER})' /mvdsv/build/${{ matrix.target }}||exit 3' else ./build_cmake.sh ${{ matrix.target }} fi - name: Create checksum run: | md5sum build/${{ matrix.target }}/mvdsv${{ matrix.ext }} > build/${{ matrix.target }}/mvdsv.md5 - name: Setup SSH env: SSH_AUTH_SOCK: /tmp/ssh_agent.sock run: | ssh-agent -a $SSH_AUTH_SOCK > /dev/null ssh-add - <<< "${{ secrets.SSH_PRIVATE_KEY }}" - name: Set date run: | sudo timedatectl set-timezone Europe/Amsterdam echo "DATE=$(date "+%Y%m%d-%H%M%S")" >> $GITHUB_ENV - name: Deploy env: SSH_AUTH_SOCK: /tmp/ssh_agent.sock run: | mkdir -p upload/snapshots/${{ matrix.os }}/${{ matrix.arch }} mkdir -p upload/snapshots/latest/${{ matrix.os }}/${{ matrix.arch }} cp build/${{ matrix.target }}/mvdsv${{ matrix.ext }} upload/snapshots/${{ matrix.os }}/${{ matrix.arch }}/${{ env.DATE }}_${GITHUB_SHA::7}_mvdsv${{ matrix.ext }} cp build/${{ matrix.target }}/mvdsv.md5 upload/snapshots/${{ matrix.os }}/${{ matrix.arch }}/${{ env.DATE }}_${GITHUB_SHA::7}_mvdsv.md5 cp build/${{ matrix.target }}/mvdsv${{ matrix.ext }} upload/snapshots/latest/${{ matrix.os }}/${{ matrix.arch }}/mvdsv${{ matrix.ext }} cp build/${{ matrix.target }}/mvdsv.md5 upload/snapshots/latest/${{ matrix.os }}/${{ matrix.arch }}/mvdsv.md5 sftp -rp -o 'StrictHostKeyChecking no' -o 'UserKnownHostsFile /dev/null' -P ${{ secrets.SFTP_PORT }} ${{ secrets.SFTP_USERNAME }}@${{ secrets.SFTP_HOST }}:/snapshots <<< $'put -rp upload/snapshots/*' mvdsv-0.35/.github/workflows/build-targets.yml000066400000000000000000000054461427146041000214760ustar00rootroot00000000000000name: build targets on: [push,pull_request] jobs: build: if: github.repository == 'QW-Group/mvdsv' runs-on: ${{ matrix.base-image }} strategy: fail-fast: false matrix: target: [linux-amd64, linux-i686, linux-armhf, linux-aarch64, windows-x64, windows-x86] include: - target: linux-amd64 os: linux arch: amd64 base-image: ubuntu-latest build-image: amd64/ubuntu:bionic ext: "" - target: linux-i686 os: linux arch: i686 base-image: ubuntu-latest build-image: i386/ubuntu:bionic ext: "" - target: linux-armhf os: linux arch: armhf base-image: ubuntu-latest build-image: arm32v7/ubuntu:bionic ext: "" - target: linux-aarch64 os: linux arch: aarch64 base-image: ubuntu-latest build-image: arm64v8/ubuntu:bionic ext: "" - target: windows-x64 os: windows arch: x64 base-image: ubuntu-18.04 ext: ".exe" - target: windows-x86 os: windows arch: x86 base-image: ubuntu-18.04 ext: ".exe" steps: - name: Checkout uses: actions/checkout@v2 - name: Prepare Build Environemnt shell: bash run: | if ${{ matrix.os == 'linux' }}; then sudo apt-get update sudo apt-get -y install qemu-system-arm qemu-user-static else sudo apt-get update sudo apt-get -y install build-essential cmake gcc-i686-linux-gnu sudo apt-get -y install gcc-arm-linux-gnueabihf pkg-config-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross sudo apt-get -y install gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 fi - name: Build shell: bash run: | if ${{ matrix.os == 'linux' }}; then docker pull ${{ matrix.build-image }} docker run --net=host --rm --device /dev/fuse -v $PWD:/mvdsv ${{ matrix.build-image }} bash -c -e 'export ARCH=$(dpkg --print-architecture);export DEBIAN_FRONTEND=noninteractive;mkdir -p /etc/apt/apt.conf.d;echo "APT::Install-Recommends "0"; APT::AutoRemove::RecommendsImportant "false";" >> /etc/apt/apt.conf.d/01lean && apt-get -qqy update && apt-get -qqy dist-upgrade && apt-get -qqy install cmake build-essential libcurl4-openssl-dev && ln -sf "$(which make)" /usr/bin/gmake && cd /mvdsv && ./build_cmake.sh ${{ matrix.target }} && chown -R '$(id -u ${USER})':'$(id -g ${USER})' /mvdsv/build/${{ matrix.target }}||exit 3' else ./build_cmake.sh ${{ matrix.target }} fi mvdsv-0.35/.gitignore000066400000000000000000000005201427146041000145640ustar00rootroot00000000000000*.o *.so *.dll *.qvm *.suo *.ncb VM/ Debug*/ Release*/ *.sdf *.user *.filters Makefile config.log config.status src/Makefile.dl src/Makefile.dl32 src/Makefile.vm mvdsv !qwdtools/ .venv/ build_*/* .ninja_deps .ninja_log build.ninja compile_commands.json meson-*/ build/vs/.vs/* build/vs/*.exe build/vs/*.db build/vs/*.def build/vs/*.ilk mvdsv-0.35/CMakeLists.txt000066400000000000000000000100671427146041000153430ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.9.0) set(CMAKE_VERBOSE_MAKEFILE ON) # Set project name and languge. project(mvdsv C) ###################################################################################################### # Set where sources located. set(DIR_SRC "src") # Add sources set(SRC_COMMON "${DIR_SRC}/bothtools.c" "${DIR_SRC}/build.c" "${DIR_SRC}/cmd.c" "${DIR_SRC}/cmodel.c" "${DIR_SRC}/common.c" "${DIR_SRC}/crc.c" "${DIR_SRC}/cvar.c" "${DIR_SRC}/fs.c" "${DIR_SRC}/hash.c" "${DIR_SRC}/mathlib.c" "${DIR_SRC}/md4.c" "${DIR_SRC}/net.c" "${DIR_SRC}/net_chan.c" "${DIR_SRC}/pmove.c" "${DIR_SRC}/pmovetst.c" "${DIR_SRC}/pr2_cmds.c" "${DIR_SRC}/pr2_edict.c" "${DIR_SRC}/pr2_exec.c" "${DIR_SRC}/vm.c" "${DIR_SRC}/vm_interpreted.c" "${DIR_SRC}/vm_x86.c" "${DIR_SRC}/pr_cmds.c" "${DIR_SRC}/pr_edict.c" "${DIR_SRC}/pr_exec.c" "${DIR_SRC}/sha1.c" "${DIR_SRC}/sha3.c" "${DIR_SRC}/sv_ccmds.c" "${DIR_SRC}/sv_demo.c" "${DIR_SRC}/sv_demo_misc.c" "${DIR_SRC}/sv_demo_qtv.c" "${DIR_SRC}/sv_ents.c" "${DIR_SRC}/sv_init.c" "${DIR_SRC}/sv_login.c" "${DIR_SRC}/sv_main.c" "${DIR_SRC}/sv_master.c" "${DIR_SRC}/sv_mod_frags.c" "${DIR_SRC}/sv_move.c" "${DIR_SRC}/sv_nchan.c" "${DIR_SRC}/sv_phys.c" "${DIR_SRC}/sv_save.c" "${DIR_SRC}/sv_send.c" "${DIR_SRC}/sv_user.c" "${DIR_SRC}/vfs_os.c" "${DIR_SRC}/vfs_pak.c" "${DIR_SRC}/world.c" "${DIR_SRC}/zone.c" "${DIR_SRC}/pcre/get.c" "${DIR_SRC}/pcre/pcre.c" ) # Check build target, and included sources if(UNIX) list(APPEND SRC_COMMON "${DIR_SRC}/sv_sys_unix.c" ) else() list(APPEND SRC_COMMON "${DIR_SRC}/sv_sys_win.c" "${DIR_SRC}/sv_windows.c" "${DIR_SRC}/winquake.rc" ) endif() ###################################################################################################### # Check for curl, and include sources and libs, if found find_package(CURL) if(NOT CURL_FOUND) message(STATUS "Curl library not found") else() list(APPEND SRC_COMMON "${DIR_SRC}/central.c" ) endif() ###################################################################################################### # Set base compiler flags set(CFLAGS -Wall) set(LFLAGS) ###################################################################################################### # Set target add_executable(${PROJECT_NAME} ${SRC_COMMON}) set_target_properties(${PROJECT_NAME} PROPERTIES #PREFIX "" # Strip lib prefix. C_VISIBILITY_PRESET hidden # Hide all symbols unless excplicitly marked to export. ) ###################################################################################################### # Set include directories target_include_directories(${PROJECT_NAME} PRIVATE ${CURL_INCLUDE_DIRS}) ###################################################################################################### # Check build target, and included sources and libs if(UNIX) target_link_libraries(${PROJECT_NAME} m) target_link_libraries(${PROJECT_NAME} dl) else() target_link_libraries(${PROJECT_NAME} ws2_32) target_link_libraries(${PROJECT_NAME} winmm) set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc") endif() ###################################################################################################### # Set defines for the build target_compile_definitions(${PROJECT_NAME} PRIVATE SERVERONLY) target_compile_definitions(${PROJECT_NAME} PRIVATE USE_PR2) include (TestBigEndian) TEST_BIG_ENDIAN(IS_BIG_ENDIAN) if(IS_BIG_ENDIAN) target_compile_definitions(${PROJECT_NAME} __BIG_ENDIAN__Q__) message(STATUS "BIG_ENDIAN") else() target_compile_definitions(${PROJECT_NAME} PRIVATE __LITTLE_ENDIAN__Q__) message(STATUS "LITTLE_ENDIAN") endif() if(CURL_FOUND) target_compile_definitions(${PROJECT_NAME} PRIVATE WWW_INTEGRATION) target_link_libraries(${PROJECT_NAME} ${CURL_LIBRARIES}) endif() ###################################################################################################### # Assign compiler flags target_compile_options(${PROJECT_NAME} PRIVATE ${CFLAGS}) target_link_libraries(${PROJECT_NAME} -lpthread) ###################################################################################################### mvdsv-0.35/CODE_OF_CONDUCT.md000066400000000000000000000062331427146041000154020ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at alexandre@nizoux.fr. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ mvdsv-0.35/LICENSE.md000066400000000000000000000355061427146041000142140ustar00rootroot00000000000000The GNU General Public License, Version 2, June 1991 (GPLv2) ============================================================ > 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. 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. mvdsv-0.35/README.md000066400000000000000000000073751427146041000140720ustar00rootroot00000000000000# MVDSV: a QuakeWorld server ![MVDSV Logo](https://raw.githubusercontent.com/QW-Group/mvdsv/master/resources/logo/mvdsv.png) **[MVDSV][mvdsv]** (MultiView Demo SerVer) has been the most popular **QuakeWorld** server in the world for more than a decade because of its ability to record every player's point of view in a server side demo and provide many different game modes to enjoy **QuakeWorld** with. ## Getting Started The following instructions will help you get a **[MVDSV][mvdsv]** server up and running on your local machine from prebuilt binaries. Details on how to compile your own **[MVDSV][mvdsv]** binary will also be included to match specific architectures or for development purposes. ## Supported architectures The following architectures are fully supported by **[MVDSV][mvdsv]** and are available as prebuilt binaries: * Linux amd64 (Intel and AMD 64-bits processors) * Linux i686 (Intel and AMD 32-bit processors) * Linux aarch (ARM 64-bit processors) * Linux armhf (ARM 32-bit processors) * Windows x64 (Intel and AMD 64-bits processors) * Windows x86 (Intel and AMD 32-bit processors) ## Prebuilt binaries You can find the prebuilt binaries on [this download page][mvdsv-builds]. ## Prerequisites None at the moment. ## Installing For more detailed information we suggest looking at the [nQuake server][nquake-linux], which uses **[MVDSV][mvdsv]** and **[KTX][ktx]** as **QuakeWorld** server. ## Building binaries ### Build from source with CMake Assuming you have installed essential build tools and ``CMake`` ```bash mkdir build && cmake -B build . && cmake --build build ``` Build artifacts would be inside ``build/`` directory, for unix like systems it would be ``mvdsv``. You can also use ``build_cmake.sh`` script, it mostly suitable for cross compilation and probably useless for experienced CMake user. Some examples: ``` ./build_cmake.sh linux-amd64 ``` should build MVDSV for ``linux-amd64`` platform, release version, check [cross-cmake](tools/cross-cmake) directory for all platforms ``` B=Debug ./build_cmake.sh linux-amd64 ``` should build MVDSV for linux-amd64 platform with debug ``` V=1 B=Debug ./build_cmake.sh linux-amd64 ``` should build MVDSV for linux-amd64 platform with debug, verbose (useful if you need validate compiler flags) ``` G="Unix Makefiles" ./build_cmake.sh linux-amd64 ``` force CMake generator to be unix makefiles ``` ./build_cmake.sh linux-amd64 ``` build MVDSV for ``linux-amd64`` version, you can provide any platform combinations. ## Versioning For the versions available, see the [tags on this repository][mvdsv-tags]. ## Authors (Listed by last name alphabetic order) * **Ivan Bolsunov** - *qqshka* * **Dominic Evans** - *oldman* * **Anton Gavrilov** - *tonik* * **Dmitry Musatov** - *disconnect* * **Peter Nicol** - *meag* * **Alexandre Nizoux** - *deurk* * **Tero Parkkonen** - *Renzo* * **Vladimir Vladimirovich** - *VVD* Names of those contributors have been lost but they have also helped with this project: *bliP*, *danfe*, *hdworak*, *HighlandeR*, *jhodge*, *kreon*, *SD-Angel*. ## Code of Conduct We try to stick to our code of conduct when it comes to interaction around this project. See the [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) file for details. ## License This project is licensed under the GPL-2.0 License - see the [LICENSE.md](LICENSE.md) file for details. ## Acknowledgments * Thanks to **Jon "bps" Cednert** for the **[MVDSV][mvdsv]** logo. * Thanks to the fine folks on [Quakeworld Discord][discord-qw] for their support and ideas. [mvdsv]: https://github.com/QW-Group/mvdsv [mvdsv-tags]: https://github.com/QW-Group/mvdsv/tags [mvdsv-builds]: https://builds.quakeworld.nu/mvdsv [ktx]: https://github.com/QW-Group/ktx [nquake-linux]: https://github.com/nQuake/server-linux [discord-qw]: http://discord.quake.world/ mvdsv-0.35/build_cmake.sh000077500000000000000000000026411427146041000154000ustar00rootroot00000000000000#!/bin/bash -e # Useful if you willing to stop on first error, also prints what is executed. #set -ex BUILDIR="${BUILDIR:-build}" # Default build dir. # Define target platforms, feel free to comment out if you does not require some of it, # or you can call this script with plaforms list you willing to build on the command line. DEFAULT_PLATFORMS=( linux-amd64 linux-aarch64 linux-armhf linux-i686 windows-x64 windows-x86 ) PLATFORMS=("${@:-${DEFAULT_PLATFORMS[@]}}") # If V variable is not empty then provide -v argument to cmake --build command (verbose output). V="${V:-}" [ ! -z ${V} ] && V="-v" # Overwrite build type with B variable. B="${B:-Release}" [ ! -z ${B} ] && BUILD="-DCMAKE_BUILD_TYPE=${B}" # The maximum number of concurrent processes to use when building. export CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-8}" # Use specified (with G variable) CMake generator or use default generator (most of the time its make) or ninja if found. G="${G:-}" [ -z "${G}" ] && hash ninja >/dev/null 2>&1 && G="Ninja" [ ! -z "${G}" ] && export CMAKE_GENERATOR="${G}" rm -rf ${BUILDIR} mkdir -p ${BUILDIR} # Build platforms one by one. for name in "${PLATFORMS[@]}"; do P="${BUILDIR}/$name" S="${PWD}" mkdir -p "${P}" case "${name}" in * ) # Build native library. pushd "${P}" cmake ${BUILD} -DCMAKE_TOOLCHAIN_FILE="tools/cross-cmake/${name}.cmake" "${S}" cmake --build . ${V} popd ;; esac done mvdsv-0.35/resources/000077500000000000000000000000001427146041000146115ustar00rootroot00000000000000mvdsv-0.35/resources/logo/000077500000000000000000000000001427146041000155515ustar00rootroot00000000000000mvdsv-0.35/resources/logo/mvdsv.png000066400000000000000000002525251427146041000174310ustar00rootroot00000000000000PNG  IHDRGT,sBIT|d_zTXtRaw profile type APP1JOK-LV((OIRc.KKD 04006F@9T(Y)Oh-، IDATx[$Gz2/>W܉3]`3`e`p@n"b^~L;lXr.#4eF+?AD[ (+`]bf}\twUe~~̪>9tLʮc:vQ.hِ^}Յa!((((x XՎ@DCnښ,| ! 2eea7!320&LDzM ڄQV!"DQjT`cMi` &tm ZUVhVQ((3S.V8N]JqJ>z_{\z(r+GV n%x3A2нS%ul.i+K^TJy$N (fkO+ݞTѠZ z=FF?SW˷6hr~տ4+)Z!v+ :S㥔f k53 @ѭ;{WW8|8wt?/Ơ+hPPPPPˍk4XFVm`έpe?VM!S3S-'xBrWÚR0+4eVk pVMPPk%Rge!K!=HĮF%v4uS7~ n{)hPPPPPe-(j%Z t;!LX[a> LK"P/N\'juW)t& 00 *O#$!$ )2t-:٧&9j߻||VtA۬AAAAAAAcP ,N"Rpѽ"k yZw~v!Vt'SҜ)(iPuH 1AqJ;E&)!xz!g-v3щ?B7ҏWvTQFT jbb1؀G :ۦɿwoM>Wb,\.$j5]*f 9iPF%k>JVM}݋]BH%|Nn%oşƯwBqRѠ:ȸ!ئ&0]a>~_]ֿ'b,\HWN;86TXLG`1I[Z☀6?yv4jLQ-/נgX~;ϰz 4$h`4(((((hUb>.g.gW )R-jZi c1%VL֔4.RoqJ@Jhۉj,G!일oN'G_vcnw?o?=5.h= 044J^y*EO1Gtx]C Mr!N?Yֺ9j `䊈 N )!N 14)MaWn2=5f .jcnu? pеnMAAAAAAA+_ g.p.!﯋ߺ]*~-竬,&)tW5@!*oPPF3Hy2u٤ng!D׎ ͫɪ痨X-wm9tv?|E_SѠ1냧xxqzqD`gtkZ=B .i}ˀg7hwq%Q׺P:V̝:a``t+$(]FO&v 킭kRLs]N}s ף_e$]鮪nه >T塞*hPPPPPPИʉ^wnR`E.*W箆R*__J+JĬ:q۱8iЍ+" FO:jB7!' $ZC6MQ\$TOW/F>h3_Iz.CTѠE8k3r]GL?8J [$=!OSyrIj%PݘuFepr1:iЍ!I !HD43I xۏ*$YŸ_Y]I۷>k_j g :=VAAAAAAA-ZGYDm6"*UL,OVi "Y|sE XbVc:I ꠠ vJZClLS 5:9>y _ '*(/[DRfݳⅻ#qGwVYҸej,2H],!Ap)$kz4;=4n{j_i:WW/30gY8|`4((((((hLewuruZ4#] AUsEGw^hn[ ZHG\Km%gJ?Z9A_Oқ-ﶵ|JVcVƕ7бbA;$AI%7rZ@Mgd4%KCvkRw}j[n/CP3LSEAfiy?#jRii3s&Z+Z$[wI:__C*:,?i3VV.ՔtD5X)`$[nerzr6 {'4^tZ VjwS8 g w :dѠVU\+ڣ[NLۉg.+ՈwK{%n!n|.0eMSc1q8q![k4x鎹MhR'E5!'&Į;g{v1OF }}sZiήζZxKt8y]C9XknXeDZ< -nnUX/(aT$i|rbL?kLDDټQ4:jV?4!4Dž1IbOꀂl ]3g34A6'Ut[KqW&T[mW.̻}8 042GdT5Iru?n?`{N*m&SC0K9,E\@rv!d,7 rZs\.6A-ӂ@+~I!&`9fttNTyu%UqBkֱtMx"@jO ѠqW?*!u9cbR +j[QwU:MӞcvcfvzAw|UFZK~rڣUel9[aoVuU?=$#Wo*]$5g`ZUㄹHb:-S_O45[)g` ԙvODE ݢ׮؍U=i*XL3'ibj©C-AJ+]e,gSZg "6G|BqqvqwD%N_lҚ5;H L ̙&yJsˬI%J}&6+P4sckk&% AAAAAA;~QtG}$?m`BFBA "L/JwJm/3}< Ru;e*wX 0=A,Eo£tAܬaOժ5`ʸ%DZ@SO4R9C՞o$Ii^1:E du Pe\n˞d<~µ֣k/_ N: )kTNvYq^y$Q \^Ik4Kr{q:QZYiv `FEhJiNbE/ıҹey~u ;-@Ѡ1U68FhͽRH:7>CMɟczaEG_T: LeNUk$/5H& |+-:սĺwJmπvArF5.AUE\eU$,3:+-lfE6~f<#p5fU&"R¸"Ym51݋4J@0eܣ9G&ٸNO^泶QM:bl&efv1X<{+q-Uӄ4Stڜ_V b̀Isjkv-2*IJӜ̊9f-\j0JL/\Khе$H "ź sw 0tCg7zAny!h5Y|??ʽ WJ:G\]ZL鴨-@b3sew `qTtfIܨ6&]fvV7!]=7F" yv QtW~2R]wC 9ΦS&-ADd'@.!r i-hڀ* ZC\YqP3|+Ek4VΙgfY"-Z6baVd;-OU ͼ9SOnz.ZgѠV?Qvٶ9@Ic)3.tZzOTgS葽_5Zu./$/Z6je֨[ϯ!zƧd*L+_hˉ n NY9>+Rc.$e9`8Z@a!,(mQlcKWs 1f0QVT.q}"DZ'@Bi@2jP=BrlcWB(@(4RnAAAAAAAA7|kg-7&{e*:H.[t%JJ4Xp6mȬ[蜨b QL) XI{DUDrbM _ s-&?lr l9v3 2Ÿ6jB{-qQ[o Bm;z$xV/faZsPv Ԥj2ZfjPVÁPs6Ee9M9ͼe%n2-T}ѠTnR"?çn\*d 59JOoU! Owk@%oI 5cB\HY x˰aU&zuBr*ݏIKԫuW4XWPsei8C+N7sEUsj>^nԆA(+"ӶCh̵ [f HH0ijѠKˮ;&t߽jn>?FʺmoAC r0^_.6; BC/6mњUf7'ԃPXA(lG2QOahPPPPP0Zetsr[6uԀ"12QFˮkpYe7_F5&;{B@@YBry weBMTCh2Ӯ2"w(cm2QbS@hPPPPPPИv 3#[ ;߬o0nS*IU.-/k g%k( \SetυHg9(򧹈[*{lͣ-8D|8,0 Y?Ӧl(ړ i JFZ6,˘=.UKT5bd!Nx}a{NvD5Ve8KV^*t->ZmBMP}!4GBa"|mBf3vB[:y`(e auFB6ČMȒB;dtz{]Xˉq1eCfr(󬄆 x^:߶m˂.XˬR[XPpN\>Tzuh{9|%-Y~f;Wa+!tB,$  8 k 0cvQGk؝]SķVY( IDATarLD6 aB@eD}9`߷dZɃy Ywާ.¥:+h (r ѭLJL׊j愺>q!NWhqluZA(Hb };n}AAAAAAA*3" er.&OgZP7>CWJ*maUi<%r!҇Pv sm^{Ӯ\殺BץP -F{-?;P7gv݄.npwn+i('  |nJ2ӴD+=97OcI%(t8¡)s.+ʺ..|tL.Yc)-`e^?:5( (yzqu Ӗ[TȼstM.SM:3nB]FBRѠ: @* j\ o5fBݲ N~C5TA(ci VЀRТ[# \tGjf+=,+zGmvrU5ʠ" YL,%k<j̇6XZ[[BٕVe\!B,'k] =VV2kcvC9Cs|ߝy(w/t"4pЭBhʣbEBf4P(vE߅O`%- ɌZ^ŝ*KF -(BksB3-\f2`kCry> ʰ T@+v\6KaheQ&x̤Q=w t!@  LѠV\e:(0up)JCOR .pի_DD}yD9ahP]%S+e0jdq86 \>__XN5LeP,6QrPKG{5T߳t .z߅M`ۉ6\PӡN=[2 AҜ hPPPPPP8+N@C%Av]ج .uz%L9UׂP`sqcSU]dJjuu_^B\RP3s7 q EN>z[KKXsmdMԀ!m (v T;y8Wj:O<1nK/]hAAAAAAA7@؝]7n6 k Vqg(N}B샎W̯!PK?øgru,>$w~'w\ f]tvZ *P*sIsc/rOn޼o-s+ϖovb]YB ꆂЊjIUv#hPPPPPЎ_QvɶΚ9?©l?N(KT@(YP} ״h .:237_bMyaѠT> CMBAb>n;{ԾUY;-Ժ4_=~#Xy"踖OVN'":+CvM (-WbNyך u+ %0B jTn*/R[GcNa @W \5aeH^m:-\# M  Hi$dѠ6:#i}4b =@JȖ|cHm~tFԅP̠MzeJʸyk\)#^G~ Bw]i}Y8PK..Cu_ĚhYe$'\wT</` YYasC6*J_P0P8ڞ@(0^ z3AhaC4ݠVn_6@$F"RmkP;y*Ӧ&|&l)GRA,TZSo p)-Ų&n vk&P-sPkRXB9PkN8F\!uϝ}+f]Q8+n ]=aN1t͐=>3MYT!WѠVMwbșlu-2"R8nz-W?"pi.;kAtYBsi>{9 !ӹX2tgk:iq%2sN{ZCVv .[i-`RG\dׁ==|k9TZw) t4^jK` k. `A9`wiT/P='5HGSSjiJ/6Kޛ.Q4_9Yb]d瀧M|!ԵrEyejTpr yD޲.XAkE{оniY[\v[d`Tk0\9fѸwniنZ BHZA 044F[|LY"`Ԉ E\ )U|l@qHV+?k0-|cmiJiQ :nN[r+{ǰV[2e: euS9' 2%\/dh*h?s?;;ka6@xj&p)Wg (BPcy[4QHQ)3`C^@#B4(~TO1%y&"Ju-y9.YC]V@;B~ 6@IPzТ Kn;Ȣտr2G#4Bdn8[ZYr::r.8yafQ󠠠Qґ#G5FGALejPXthOziۺ뺀[AʹJʽ[Yu{u XP. vk6 MQ1e{ @nf랓 LvzRp& Vxr^{7ٜ/BT\we.Ҵ- `mcɥ(?0r0uC[JmBW#Gp x͛ϥ2D}k=uiO׈3_SJp"T!Ϗ|tСD+{Ʌaa3 }>ڪ3/2 zנ57*CٓɌuZF\^:ȒL.Y%.}>e1$ BJ4)Ԥy!WuUyT-^ ڃЌ! e-ȱnjhe ԁRYơ[_o4 4]^IlQ/@׻s.|\.*ۃ QS38&!5-oåyBе,=k7V fqxR-fҬZf׾E'd!MӛUi~R%9`D 7zGQ8AQٯ#o`J<9:v줶~480}6<裋̜ӟ.dԯqRl/#"*,5d] k4݀FYrb(ʕʅI^gw_Lj9)rF6 0 I `2p9S;md ,ZwRV|R+F1@F-E-D Kĸ.9T@sgD)~`ÎóƒIIӀ:ѿy[(הsHD^:л4OOߺ;sIw3(2񋈔R{CRj!MӗNtExZqSg0}~8V̼@DDž/)X p;v^<ʯe5}>V}Y ~Tbߋ~ǡ~EkR$ ȭ;06#t[ Xg.%T(,x]12WNu 9o\ԫ~vjwIynjN Q7T85X30]bp\}4S|U.㸫K20K 4t\Tߊ 0{9S, !B\PgBȵ |͒tVR_{o0OH;D]쬎5tL 4/2+ig}V,9_q`}iKKZ?Asd_UwϙHV?~I)Aq(E=VQ3#M3@J@^RʼGHoba. ~W܃Jz?ƨ^O*k!"lF8q٭ssY eET mAƽv5 ;)I)W?=?1qnE/t8[M.Eumh'/<V@r|Th{fmq5paUۧ\C7~Za tWS4%V?YOԷS+ڱ=uups8eaQw7bQx5:thE/ѳB*E%,V#^Gf0Z'$I$yi:oa>A"U.AR}軍F㻏=?Ot,w_NSLE<q<>ߤ_D_ZL>kw޼Ѝ-뙢T;+-;uA ]˝N;: 0կׯB(Uݾke:"#{btjuLm :l҅W|sC(A A$b[H r kYArNg;RV>-w_. jJ?}'^ZG?¦Am]8]S}~dHZǎ{E)}~=2}葵s\k{>j2!7c2/Q'ou5۱{#w.|R>Եr1q2 D^P\gTP_= $詻jԚ6_h9s ~EIGZ_9L㮛]lK@ p-e?u9I)f_}Zs^ oDk uuhFcqwB}ҝRjeED7qjw@ي(ʴkaذ;BGU :*~{r񨵯H4Vdnj炙]xF}!U};|`LIjwWVO/L !MF"YJgkp6u9|Ks\ BAh9 쟔مP4{IoޝbȮg*Zerܜу/ !q~PT_ C~;ʍ4"(4M?~ng+ϼ>]uXSϷR'O<裏2SID( bft:c5339{=i\Gk Z2ap1㐟D>|s{a&6F`EnZg!kWnͽZ{vj]o^JsQB<`uBhKbkIZF=҇,mJz3`wMcl?ZoNYS[Ç_0O\W,wQEUnX@?G)V ;RVE??H~|su~^}c=*^QJwgTe+xݮQ'_~|x_u?n^W>.^l|g|&f*|knU@TBQI)v@ُ;ɿypy`W}ˈTf *QuŖ)=?zoکyO24j 0Y;^W~{jJoѵ~p%IhbP fۅPٿ~GDJRv=ӛ.6۪5uVC(U؅>pGQO>?_YYyaԬZf>o>ڨ&.031Xe~~~Ndэ~? >eCxR6U,6(HUXtG%5exH端*1gzj~zcO%Ȥ^@&kDqxfۛb7ԅ IDAT>|6Kpμў610Bn/oFVbqLmׅv`O}˗u<gw-VC(HA9Rj01FAIx',<칺d&MSH)RzǞ݈η>__'I)sRʧ;k62| ? Y.2q`؀:F0 k u?= 5fAN11P%r"tb\X5]'\. fc!^gV$Js?aJQ7/=w}S'0Sе&!|*wwO/Z^᧭[M\_ZArl}/jtË|a_Zn^9=1-ё~;NO]Y. Z]CPyAD/_ROW_}ahkc8ucDt|d:|w߮Q(w΢ qn٬6?}ҵv`.?/(!gL.g8{@b2>׮Be6Vwd2=vit>U([V\,nBẁ!J;\QCLvzwDqرcO.>^B̹?CN?377`\O ټQsut 0u(:°>B[z(hF"\:a1BG>ޅ@0jJ˽a j`]ܾi)3Lx?!Ƚ,rvʁԳ0ʵ]}l}ݿ΅Nl8iY4vh|9;[,ޥNNM"COդWPݓs-jZ|SDƿĺnMСCK)_7^h/w0R5Wf :\|رc?Z(%o^>#Um6G&''O;vlx']@F,!81z":Qs5C,J)QQ}ٿ~19H/cHˡQ<\$(fGJSw6UliN2)ĶEٔH-\Rג\NV~׿gfBc{f'Ѽ3kk'OY35CA1+ (i`6sFgko>*轄|L7gOr%M+MmxYArl‡˗Ufp1Qմn] QmD{ ]. qt@Dz[-z^=wPZB\4bzy܇}{Uk˷SO=~|y˺@YeED=QhC=ODV7FȆRi]sRMlA)b[-#ixx.U.F}-ua˿Ь%:`tQO,Wjpn%>27=הҋmsek;˫I)FVm_%7}[Wqu쥿Y;Qӭ偙nTK(B׫JL"lÇ Td!wnNO4.[nUk<0ɓ'y(/b۱;ʿf, @0-׵Aף2ӆ6.J kdKT7Qv,E6r"K * ͪɏ?}dD5j~qP_D$_^YαoG^C;Ԙv)'tqzLNA6 p.?mֺ/o:ߪ ޞw ;Vvߝ9mbXfʃv UVԍ@ M]U ? OÓ듅(JpZ5}j?*^Zyz6Ў#n=96⻃,;c4ߍ;`נ|>gȮDjXiOg yđ0*^ dqpfQ1U#"kGuW$KV e(ks>lzsPbË+N6 B;t6S&l>ʕ * wQЬ6B->]7hw.U5 }q1{O=;&(q7JEv;3/ wf# ܏WgeEWZX]@/]bS5 3/PQʑSUg'"HHf߇*4'Ԗq_nf߯_;:7325ePYf Z"6O0xjߜЪ"~ UoESxm\sM6mz{gZW_Yzq^b枮{NhE;75ZW[Fcu ysJakeB03()T)ujQ۶QRF7a2P !?qgH~GvĨ,fc̟ qFҭnf5e7(au%lDYk݀ѲddDN7E8lT,Pe`P 9OOl2BƜzCFV"%aL r4s$-LokTM拪^2=xtMR>%ן}|1XB[B b4""p,__d O·W>ZV2N[H nD]/N5cÒRe4YDGwPJ- |>c爎j?J4 /Mh] 0Eݥ8,TfYJe^l"2C { eNN{ <4;y\MnFvgcDE%f%7*~{'~ޙ J/$m,X=Ԥ1 &-N4_n%ô?]e~蝓}[a uNTzՕ V3PDG lVYF<[UE4< 9BoܹOzW'OC}R)Hèi(2̋/aۂha~荡Q2[8l j 6VRwV4,=,9&423-btS~5)(̪ |W,B<]0kFr.N{7KM3vW*קla2[.=F'oc63 `m&k(Oej臗ډOðVFmS3DڐuZ_/eYKn h@{Ewix\Fxt=rKǏ?+\jOhQ7n֨G%},@6wxy̼`"zck7a-̠,(03Zé͜YCʌ֏֚NB`8f& 6ky+_JX1!li~&VOT@a ;;no~˱f9[){b VSM??#uׂAg6kκ \wur,bsWT6\KhvAhOmm3ǕȝD6oEтb DFݢiw^VJ-TFs d#{-w !^5DE4hTj"b1#Eign!ϟ?jN~#v #/k!EtS ~aZH=mꗭw,9+m&ح޻Irgb/{ޏ}9 $'Μx9]|Vq;_ؾagpa|!օld8KYx,;X`λ+2g{w뛨|2+՗K@wYKVA&2 +sV͖LQ<WJ{2.W#[O{$m˟(HMP~ H*SJD hkǚdtmY%šH gnʶ}~+6΋+NE/ RlwQ MZr F+'O'/ykn 1}O? gΜ9ߋyAJ9 &eUGBґQ(e4K-K?~uU)>x~ePFOVCII! dDO ^0^{ս+-`@);e8C M 8"',!S788VcSO/Ds6ë͹SR hhGuk c#Sz/Po4V bP;%/XF,PGDkY˷To+TYtz||^̋,hYAD`ٽ5 (/CgffAb~* bꥎq\F)5^%vݽ=2҈0F;1"{Žu|)RH3~gl#YU"}~I'Ya>%^Ŏ;>=zꮱ!RE]˪+̾N)Z/-l+%4]]N7CdѩTBl?x?yQUBuFAѠ3"wP;m*Z=SsssOMDetusܲ+0/ѧ=Spkt}|ac;2˾y#B*:o! ˽ ީq?ɺ{7y$n%H݌'V GBUJEBwA;̪-;$חzoerYHwDCͽ =0Ѳ.zID_Z m۞2FDI)[wĉїܹpYv~+T7IW9}n<==MHu _#=\)˺H)W|˱h/XM*=Q$0thKFh $Es;чgƙB#BhxȮ/d7\P`L9e#{`N I!&$T!1\ד~H '>TVȸi^һkpgSpL -[d+^)i,_8u0#3 A!vP!eT }& *$U3(KJ-kRIVX\֥XS9O^)<ђW[ Tv3",Jʉpi}7– l;rDŽVa2  amֽ#wJۼN >*EB{NDU!N %4WB.?:d.G[l6*ZVݽO/&c"鼗LP;7h8nEQBĜQz<5~*aek A9ϫ&>d/R](VL\hFرQBz$6VӧRMyւmrhcS.RrZ4mוR !/$P7>[ 5y,rtj8FyZcKzZo<|h,YPɛ puI u`f!CZ^L؎l흪SE:#*TH]^?=77e0s2ZΎ$@De; $<zi|fffIWzVnI0YifhHىUPN*/ۏM!hCǸ#}=/.OuA!#>[c+n|9#G'ͽꢿ_XiA uo.zg'PfvQ u^< M%ԩ/HHŵUFAܲvv+T( =:a> nW?A+e_JQw~ޗ?^1<<SEuU}kgD<$FxіȧHљpIDF Ia(ZS3֢(TؔƇK5TPdGF,p:dוPo=2YSBs<h=8Z{hk-O弸 olZ~=G 7wENLEeSB]p~Iw@n+` ",PFr/x`^1gw@֒PB>lMWOv0 Oʨ/+QW{9|O<9+x~d"_/ܰkypnlQ?h13JFw֒*9{")TٹhOC *ZaOx[v9Y-G]ד:R.>3~ Ps;SeR+rHU/I%tAK7U@"JKvrY>0R =L2K!mvgpMt[ MPN8P>9o8 ~=o]h~]^#u̝ ޽oh|/P; K%4*OBbq PcpCjU'U~ rfxEHUٽKzJBsyE*QiհaF;ۉ o=&TFOj= KuzݰP׍7]{Q)e"{QW1}a˶~&3# Å^ٯOKjdL2RbuR~:T$`#&;`,+ي$港p@M[U)2OŵS@2p}[FHR}ceH Nփ;SSUdnPD'>|xO}P7@u<29ThՍM z]}cAeU< 6EcWh9/s";~#zhÊjP =SOVKKK?RVq+=Ξ{%5Fn̼}|/lA6=(sT(n C԰]FvOdҨ~ e[%/bxÆA4s1QR'`vJjہ IKaen_y&lMFSwM c>\PG'aA?-`!L.m+dÓC"oJFcdR^ƔP_PP!D@`ɡ؁2G+՛ vMӈ>% Rǵ:A)UQ({]@Y[a 2<44w=M%[\TIjș^Tf*iRbED5SE$[dIRhN'*n/m,™%Uݛ єw?T ʛ1^lG`h?~zmsr8?}#cC#uAy`ȷxc9k~v|b&FkfGݝ<%N9/>{GB`%w/ ɜPdJ(5gtfff~#ͪ֓PZ֕a \tUQIumO0{IEd=B GR=Skq<8Olﻖ{N)$G$Iř9I3C#A򦑰P9_Y_Z,!eqhϯUQՔo>ۯ]\۔6&$&"sBqXD?4V+R}aGy I IL;诗VjNyt-|3项$I9'tD gY;* ",utfnnn jj/7"*BZ333YqeUF{IO!]/6 -c{T=Jwx"2 |JDrY$"kCj%T8^ )~*Qs{;Rʨ wxn5WB !7xcQ{ӵ=ꖮ3{UZfǑ}k =;2~*Zj|L*)a5jٲRhO 8u :oF@{&²6͐^Yz뚈0-|Xߩ0"`I b]my]0&W uqlj8 [m2uS s10{^B(Rz鑩Xfܚ5mrys%PxOZPE&`ʥuS {<#QPFgffpʧsE;=C{TmAn ts2*(=gu']2zҕRݙ}B/_6ԫQ>x KH"@2]JjfI+x>HbGFq>eKc#(\tj(CDGFᔖ,,C#x񃍋7錧xhig}WPG=퐟/gr(}믾۴ Cѻ&>~ o]/lK-o3F y-ޱ~ljm'CYC@mM/"_<3 ,3^x[9/yԔN, D41_JITj'ܡY"z+J)Wӷ }zפLJs:ZFg˞ye']^{U<,АQEݑ1o'Nh<hZq) B~uRx /0 .LC)Ft!Y prH\*Z84"W7e8Qj]ުDj(y\mXkd*Q/ׯ?s&:"N;8qlb#tm 2 qxr(FX0$M֎84:44>$1 F\.Hh6LFcѼ3d++,gw៛`>'_uZIUw2;gT2@=[f|"]V/Zf~ UQ)oa#{V=q}/ݪ#e+ꆶi/]쇢!333B[a3H)n4Of-GYx*}m4 Tm9ו zzUlmED{Q']BZ&03 ݶ;3372f!-"z_Q#{A%eYVɫsZF5Ց j!RP*;&"W']whٞ{Rʕ{|j"dlQRB'vKB]S>3CCCOj/ٿȠ>2"Hb@Bj&)+pdHRk<]و ¡Z-BH9 &JڵwUI0 !3c3R?պ6tƚHxe#|v;'mT2qp|HVWZdž9>% 7//pc':qtljI+}֊+DDvRͼs_,(eo~=ur̙?O?U׿*eTzw}jyta^7a&()f)lO> =2={j!7eyg>'Oj5ja% l׀E[ͱLQMxR952NRT ы8&*t),bZ0'?K6'\Nfv}+DžUOFM8Q R8NTٍa:-ؚ?_ZPɑy.l8:$Tc5a@0}Zɤ'ݽ :;k7&CwDB @#.*Cuj2э<"wݳ{Ml ÅZ6QyZ7Py1 #̼`;/҄g0eEٷmj)M f^!{*;⋋N]jne)!XJ aLmҪ}̞8^YǪ`tEѩIJ9H^`j^k^]۔M9=Vяt7IhMlqo:c|27=~wY^jW)>t x!jQ|w ҏ&3?gϞ]lZjj! C֦?;A݇5O+'Ug2Eᢘuue'+ED0mე2Qg.6'ͦQ{p0a=|OErSvx&×H؏%6jh$W}&ܪڣN7,"ۑB~Ŀ~c j}ZUeNe!l3V>: \Rj$bvYj$o^hPCrJ޹v9T6TC5}3<:Q߿!wMvV[G`WJn W2n. ͪD$D +suL)[Zzx饗>ucPsټtU0"\獖}!lLUyR^-3UC-\H)o}뙽g>{ԩ<#6r^ɗ\g:@rF_@hd@Deh$٨vݧY&ANH~c;dHd߽lt6RH3>YY Ȁqk+ˬ FxxJrNh򢳫tjJ@yjwOIU%Ԋ`'Ň׿u˞8(ݻh2EB ԱJޅ՛mf\RR/9{,ɛXGB}Cxsl;32NcG-K%)e<&v'RNUB̗Dj-u*SRr>ۯ2*-6Š򉽮D:pd9 e~h4I'b"3$BHQfCN5;SZdcQ#z2K e'==D4Zq-au+"+fPBb(!f NO3jd$>XkRJoSo 2f/Y{"$ac;ٟnQ '7֚7##w 7Z05Zaȯ]Z$^h]$wKn4^j)V CaHI8m{͓$]ޠPMBTԴ']]fyu=amm驩'Ӿ9Y"DWI)A?KD},n`FH)zdz_-(( W_}|#Oi~&t` t9.hR2 OaR yMNvHŧ!ȣk79#"FtY N"<`=|.;ݼvپ6[-rs;<:;h v?:=ƮDL.JњCA=;!Qiׯnt<'iI(Іm%T%"$ e}r˨ |jPKKK=гB}A#Ry{OJ̏sGJU<ӯ˗t%mϔYl6B|e ?Xpd1%idM*]Fdz1nQRj74&O&Leg=[YT}SIBT&3'C:D!CYSerӯJ`IβNBevglIgOLLo!}"z֮M[,+h~tg t-<(Vf|A}a+ [u獖 D2kZ +0/$ݡel tgnuK4'ۃn" ˖,+pibrLCvd0<9䦽Χ&>E]JCLANm%\v͵l=4o6wxHNm1 hX-}ěލپRkW~}<-.7'm㸦{oVBGvn$eQިt{%Ӷޞ{5T#)귿x#o>"nZUݼO8)%0DUfP2 3;B/lK)AT}ekRʥoۥYʥΝ;wɓgZVyzFyQDb8iRK+x<H8DϬWI֛u (4vhd O ǵ(5):flH#$~KDiʎ>BX\sK_k%w D/{3Ef[8c7/L-;cjy=ppxjVC^)Ej'"Ж 6 ՚{Y[$|i[)#ӼyUW-ʶB< X t ),*}8PPKReFńV}C9}FS9fmsllG8_NmthzH] IDATz;eCxSuh͈9sFmڢzZťP6'JpKOLDL=NAt^(qtt];/*ewmWV[%  Z"(dZbkuJXvQy7E IZJ 5u0L9Uv`4%cIjYUu%5wh*?O ^\ b hb`S-ίn_|_Vqqy+X;X_}pf$=t"-k#cgdڱb]\kKBF*YgmWV%iveQQrxXjR%AV 1 pVl?Y|5oGeWD5g*:)ChZ癹zuZYJ=r*YZ0C"w|8z]^J)WΜ9se혋qA|9z;ĿPsV3ynjs{ʇ/̐jn_{hV7&`'=6ti"k ` &E7YJvuSYݩζ"qۙ֬^)nHN-W{yev3ai%޽j*Yjj'H)y!5Dd7O?<ô2j񲷃:^ٯj@[YA?E/&.\v_ P2/ʐJ J#BD|N &G\U !)z?-W`u;r2asnQP?@[QN}Z3Sxt*bwO>?ڊGEA/6-%es[UR$./M;'9mW4+/V܇Mgжuȹ0?KuӾsc+r^DL6vKzp`Y/|2 "_[?ePyfy6+BZctVȇl-;V(e)4h<Vazj262Q]{!/T vԭ=pPcD@"v~Μ GkZv.nfCQav\PeUm[ jw~aeRGqʾtO-ZB?ɚ7ZVn \vhlxTb\LʃOPR2E*=EE`$3AQeV4NW?{\/z&HfHZjꗎyFkdEԾjO#IQ"Y2xLyeKA). XX=Y;"2HɫbBv:8%{TR=1Cƅ+fvM+IA~}<zGHB^Ն5V!M橧Md}r8l w{Ŏ-jþD'+W%Yp~uRB3컹:TBS 3 x*3Ym>sth0 O !덖mYu^fze|e'Ӱej e[ںl#\Yț 恪iQZ/J$k!\FtRHFHIvb=ԚKyFU'lVIc>jpFѱr DHCm.Uk&Xn6[AriL O+szFƂ2ڔqfozOv31+3@1Ǿ`p "ջ0X[kK)N83|m=z4hY~8/Pa'۟ljEH,;fKȑvK"/P lwu!]z?vm#Ut=!OҐRI ;?cw2u(o٢OoV|3m@S3 (NAa{Y#F&&vߍ{ZڭچJN5ٲvPoTmTm;՘'Cu'ݬ2tM0sϝ(s!yT*c b\QlR+%#1ԔQS.n2 Klєl5JA"%ſ-%aGS]RJ;$;K'o9*MLaF>֕ꯉ޻N=8KB LfFbO3[n(n?\Sjʌ١r"!@jYJPÂ-c'Wuvl^WBRC=T*Nr4S4j_Xv]O*\R˾"]hb'/88dN&Hn>q3g%j8GwcSDf&u;%ɬEj %̺2=dD9ۍ(",]ZmI>j )="e|Z h"0Y}wMl]Z-fڣ{k>*d{H[n0fƲLW*Cּru7oV{I]C.LW1;0l $wY[H T NP{ff$ԜZf *ܲPFw) 0m.e'jaa+PB!K:Ū<,KAhKR= l4 ue_v/5坓C"YI8eC ip 6Z$(p]  O=~polq6f+˕N3+m):Q*ߝ[ݖvHB55MOۇF= #+uYsY|oh;AGIH)q{VGEgm沼=/ $Rͻ2'jI2!lE3@ $XIr"f(*U5gܕ!ĉY\]Un&i ?^QYɲ)@xhpÇXCa ^ْ%P9PhOBXYoֶ.}/IƱ}cʔqͰo6/tc{#Pޑ TlRF+T &nG,*a(O|b{B7l=ztֶ+,R.u*TݑvY/(ZCFk guC'9 KUɵZjfsu UF&>0YTǏ!3cCz>>zCl6LsVaʅ^Zn^_or}܊#>nWZ&G}{@B5O h{"JPPS^u@EBPAQ4,d Ph/eGI\xqMH|2:Wݲ7HtETm&ZJ^lO}Jg(^^{^".)ruuumɇpFJҘZCHG`XnJB23Rus2h3߷!_yf ךխwy6w~u> mv^?~' #M/\5dx[hN}z* ^5(s:AcP}+56K["YD̈QO*3$2TyZ[\xۍF21Ƶ퓇f:4Yu*:4raRJeADx}_G#SϽsV1!;A$4'bufAQxHcߘad=IpmV7WV"  C0 CALUB +++R%2ZN~n:uj~lOvJ)WΞ={~Q@@ MHYwI蹆S}E@/!Q(^ZvV,OvW߻ٺxs+=ѨEl'ٻ& ǺQoix*]@dw۟O侃Ás#uSuf{f6iR+;_Z*kFBeMoԵ^~11>E 0Ï<"x/_ej%y6C1-DQXnpۋ+p=ׯ6?6OB}[ellZ|Q̭(s݊jnAuD%Gk&xhfM!h%R '' S#ZbĦ&(z|H-uY8OWY֨Ɏ9!na&y>Fc# WV6yJiFJ~c%?>,낶ue&B}z~*vX+dWoJc%%yˋnYTQ :'{]  ̈drTF#D[iayVbtd@}}Tf/,7/,o3(OcKDfvdoK6qX%tmHAQc< 'g~?ÿŇjB$6|6}e2c{[Ylwvkee!U pNa$nG %Ծ}a/ݱΖJ"_ ChUU Z=ꖌjI]b6~)^[tGCLoFT*i{%ӅMA"34/R<*Ta¶irZ2 *oLL%#=#ա'v+96T_'Gopqs{Sm_f@I.\[8:eVhk Dx_N޹dMő}FvWGDJ\)˳ GŔPȺo4VBG1.^xމ4Lwv={ve)[(sڡjY+Uu/i"'b]ژbqEb([QdBbfn6)qe﯆9ȣmuoI$?ȡ.k~O[]HyEʋctf#atfٲIቚL8޼!{g[ݓP/o΋*Z}ZwYw__Ϟ<6l:qM' 67 ޝͲ{}'j#qqt/_\2wRep\m"M7i7lEke; )6?63ϹtˢQ;pF#u:Hqgp[xmg/1syu+W6ǎ!p~͍{yu.)^̛Ln mgeOTdz:Lth>\TBm+JZuMڮ+ʐ *nRމ"dz01[VE*E*t#)0R4!6NQ*uI+m Pj3iDLsߺ<ҰtawUCoExl Nnd:4WçVqw<+D9>?suJ|[$Kcnq{~n59mHdP&'EkF`bXX+x %N{yby(Z JhNBp#@?+Ũڼ܏#QGR]og Zwys.>nPyahXRLH),|f"2Iq4!]IT?,Ovop~}P@zZgarhd̝^9PBtGfΓ_87νwZӠ+# V\{ W[ݳQ%E83~ѩʆf?v䴠hԢeKK֙p.]p 5r6)+-A)2M]RB5͢o CLzJPʲr.Ys~yg拖 %  IDATRʕsΝzT0H`H3I6Ȕb牪P=Bc€t:e_XJ+4T9|-6Rlk%Iu'B=zksQWU:@<|hrD_;?z5ǑS[\g21vj3Vo]^1&ҫrk_Mg+rщJ([X07l>ξNI-]TB0+@BڀֶlIjG3jl AGGR.0RJBy0,3/E:$e[;Tm%,!W &2\ ZICX`=g6ʶfjD HaTKHST rꪐ"{^zzOޘ}:%dN+E=in1&5FiXcۼ[ B=ezjtX?}3Hv#M]e|>13[TKyfN쑩«a{e]:)w,Yg1ɲj M9t(nЌR&RF+T.I/[s7usF)*TE+THq:j)w[9 deT$Pi0c'VI%"Flm8w6_iHu3FFO@L;D֕vS%TWh9_ fc&7OMsğ_x+ ioDo]i ةy/#iΩX'%F*ܟqh(/bJ% l;.UUj-AXkss-tgzzzVݓFɇhPA]}-339DZzF[iQAM~;tg%@V[8%J~{?񻦇{VA2laUEDf$V&_nf̲t9! LL _|U7_CJD5oސ'E²,k™ =vН&(T.RXb*յfc!%*%J(SGJٞ*eB zꢔrHu> 4OVE8wtTpB+gKz_Rtjp>mԞj#++4ҵɯ4?'o_]kO_cG݌8;,MMcoߊ^ ܩ#J: $樛4r,6mI 5<[i9346"Z)ȬcZfbYNFzͳ̋R9W-Quf;')Rq@UwLuf"32JIJ @.;#5 eKhIE6kK ɗuYs(Xn?')93>]u[-n[U%ԊW/ĉ++? tHz-j!p&IҜPIay;hf`r.k['##5 Z*MXSڥ1 - жyo]%Fynį?L RF+TcLD;3kNr^T ÈZa'4JQR;JL8CSXsA=dbol_ywy{3H)G{mFI{E$IZu *0sjbhBד_rMD {] eO bl%ɟVIu6bax[1aюd6xM) a ooW.5Z_v&ALqENĤ.ݫ}vNEfDy ^!YRqN/N=8_#^ M+q%nyk=F} wN"ayd %t7r%42K?P+JPfiڠ5/gA[`ǥ}g2@ghzדm,> =/ O^vڼPÈ{5BŚ?-HZt(d$eajj|q/jvtz:akE< n}WC:Ig!!9|bs7N$_?yxG'cB3 %=:=J>#A jy$[%ً7H2َH最*j.v$P*Bn;;'YB`%eCg]ET {] JAItiwję:3͈jbxs/t稝'i۵??~uKkɉpκ2;q|f}d43L.f)@wqW~9 (Q,\"jij1ѹn[!Jݭ7kd͐ލÓƁn(CBsL޹= GF J^]o5ZN/q )m; He ʆE  Qph ^Ae{]  [Ըk4we"m* `,v&^|ؕy)v=m1T:?[믭umx5GlǑn-p֎sώ7n U,זc8_\^|LIrߛdҞo^\ Ynx[!q~<bH}c~Ԯ%4w*V %T˛7CD ]#ů_uFUUrAJ9g{-q{eypwր*1zRׂkɬ Sb>F-5үzڶɝVBGWqHVJ(@Ь]~6h b4uR(Vu\޿Pz4uǪ-~Tّl aR'\WfkfBrQo(xΟ3/?_+@!>;e~lQs;ɪ'֕{0T]kۡ⚎^2*n^_8kLpV߽4A3LƱms7%`^)+n] EH R۠Aj `QF}Lv.JIР‘#9V3#fa^|*Y9c7"0J.gPII3 Ym:2qdߍ?G/}BH-N+#2;HB+ܮ2u﨟s!'zZd^T8sy%9q|, u ;۝IP:|B~=w};ηh'I^/5ܶdz$F|HH[c?ym308E:Ucӗ?=ʖ+OJjj[ƃN4ȀjbYJB=p_o_HHrZaORDHe5M i6hР,Năi>V---T] >Q3⳿%ێԺO"s-Bt.WT].Xq,uv8hSz76/\?;|cPZZZӸ ǃ]$U%VC -bЇQw}"83`0h?B1J+@22vsۏr~lǔD[I{Q(z*ֈhjH(<lJLVj9mMo .]:qرԒTFϬkLyӤ>ښf/13ݿQh|gY=gD׏\un;B H@h#U ح -hQgdkJԨb Fa*>|^8ɳW?pwsud . 4ZZ?*ILp 7Nz$vY ?*" N4ȧDiR |cS_]#Դ:_X\C 0/9N״GAF [LP)^"unРAn4iPwQJ= ~}C˨swߩm9~zǝbKm97>jf~63+b;Vt܀[WVJqKDnjE"i:kD,hCx: M=>R'XukH' {'JitͧQř_>98RY_IF=4 EKedPf:B+,a#2X5N%M̦J&lن ZRml`Xcoӆ](ٱӑXMFc5YtWcO}߃sRݮ)QB8s#ٿ IDAT[t`.IxeK7Y8zDXk8VV`?wNԵ%4`O٨*-eA )(Z֣Sh^A*/vVکOv k>xE*y.VVaW"/|?e fW?'I|y |ٗu1[֫u`q 3>kPj '7~tʺޭ+ zJh=TD,0_f׆I)x&FKٽͶ.[jَٷUu/jКIDJ)2/ !̮e4}h|{k'Cx*v(4}~0^mc V'-&Ő>"/FN%B%|yEId@9BURoNEZ%H[6j…ƮG,@Zj\.JkҴQM BZW*r'O]|kn}4*1uVv/e-=&+j7 ~1V=LiWIJDUm_N=wa ̞:QBaHs<%H(uJ(Y%Q0j  `:@DݱRQ0s`( $ ʬuEP0xRRilpJiIF4/JFZ}P6BmZ۩S2׊UO¥C]پT0\zE~U%TճJh#2$d_pW4kK[QBJ49%4kW67$ӣsGPX% [QBs%4VO]ekQCF4h06VVVkONzG­\|… 'G njYȭ# ,#&ȔXC!r0*vdN xX05DMqXWql%;ڑ~{v(r;Wd#ɪNR#1z/soʫZB OHBFLT!&xZ˿zz2y* uϟXK}jjHhXu$=G1mVJ (.4кf6#h|>zXNՍL RpRINĴ\M>4Rp8ܶ2b: IJOعWϗ_{׼rcKQRBGWmM9d i0D)F2S̥}i{u00 qSe439{FY<V7^t{2׷=[MNHhAG+4k"G 4,iZ&#*HO Ӽ^AYQWc3ʨ}U$o*%1bus 6|YQavc^LەzH+kDiһ*t|P%B^N/хZ+:ВdFba_Ԣنu;7rZ*U'+\nlE ^ssI4SJ-Z%4r_$ LWQ E/0\$TzK*vޣ[ƉiUrw}NӰT+}mf:4}UsEi B1EB^9%B 4Bu|ref @r*̅2l"L&q჉TgWQNxĉNH-wr ߨP%eR .7\|tS4K<8)1T?4Դ(<_+E[50PzQjn;D6ER5Ӫ$TV*-(qbWD oZ% 4rȑ#+̼$Ӡ8@es@tZ;wny>4h`Ef*,93r7}W(g0rCBd%E~y-1wHF9g4}Kx |[l=u;VLݴ[ BjfQFuOS:Q$ LaT&u/g$0 rѸdi'H(-}tⵋq5FBS^`upJo# \kI(>+jɔ2b&bܑYRJ>,0 >VG "zdQqǫ' f~z>T!5zlf*4}054hq$IAAY)E層Htr5DC6J lWc6d!]C5eMl -:ĽXs71}wqu@\~L6: O]8}-v -*CìPί,%6$=Њ wrXyQQF4h0.<"hIDRt65Qu9xQ PDL>ȣCv(Ņ Y6$aT(&I/ٌ ݔaA# 6F(?y&$ | P>}sXNiCH +&5Cb+Hѐ_^p+zKUհ$ux=|U}x $I(`Yi$ֈ~"dLt9@xޣa8.+>\Dԝ0'wSIwybi]\YM,# SS@h[W;R(WP=,HL7Z@Cn m=H91pE"F1Lu*&KHkmPO){4}~R (i^tWVV'VAA?Jk%{EQ1VI& F f+C戃dIm rʩ1aѻZʌ_޶ ]W 6bz;ChV0y~(FHCO/2'`~bi"a?H+'Nxێ|њK͔lw`zFI;U(OڇESp% 1KrʐF'ZHKm4! i+3btd2(FEʪ͑2^EEAd rn0@`Ul'kPĬDU^:#T`dM{LB]zlVIBATٸ$™+^Fg:q߻Ih4M$7Gd&EZvδ%vC{w|5m`'(`Tp`G<5a72az~}̈,*AnkZt *~!̉gĖȡ/L(* i&F[$ ;f/_TA=9v86&4ي#Fǩ|#?90NFhmIpyup\*k5/ A W6C$Ԥ3N]\t!GQujYڣJ["IjI(MPIB]r^,Sș50jviРV)Uh}(5D4(ռd4?Fm)=5Ň9ӛx[̨ۢĵ慍4ݦT-ZRSAD&(,|⶧mRRRr7{}(, w4V1~MPv1i>ir,vFG;2}RSךupv ܱddt*fCmahQ<(~2ɼ լzy?>|R+oQBS&E,}abk&!'=WQ gKUyQBpu37?Ra%4&J(nI EfU>u mРVq]k}[f>rz~| 艥ÓcL<ረ?c(jH %eF̫D\{sc T=/RIkj i^*37"O~聎.+"v5 iff>`)Q}_zGGN# P]TǪ> P!U{!xFEq=鸶Nu~:n-M \!1F 4>@70`I0g4'\= IA*ӓ!x4L*)r*ZGw/4q\0Ʉ~lc%r.gyճmgD#[`Y9(W[Sʣs?tE)4N?JZM+_/:вϳ.摁ѽKuzd;7;}7T&>/k TӶP`Jmʓ[VB[TB:bU!26ʽG{w~jJ)Ĩ(>z`Q,.)I§h*V{M;B,n\F3d1h1BHTn5^PID@8(Q q[hTn-e?w7z #86j{u`Kv/Fr}: Kz] - ie>mI b¹ebȞw3|0!E|YʢEVBMh7VWɮ@= }^UQBŻ$'G# IDAT\ SV.i6h`X__?^ӌ+W,Oڇ-`fs D)ǁHJ ӓa'mBʬ5ʼF.-o^[qrdʿՀF,jnP!Np[3Z"rR?)0WA}b zMhPr9Ĩ7#>k5cT~]hU+U(z]-+t8J(vV JJ&. K&VY5hРpv 2:דzrii郓šnB9EA}DOׅnD)RڅaXt4 - Η!UIMڇř]AD&F3٤]6>{4}Z/+B 0'uY=j>/}9cI v ɨZ ̾JG28we51,-쇽hD@N_8}Sv`ۆ;J&j/9txqζP&qmɴ6m]ERv_>yWF-#8u5_YBe;y &?‡/]vj:i "CPAcfɰ%Ab<h(Yַam|W pGR @}.f6h`[Z/?jZ˓v`+z%{U$.znΠ*<ϧZи/ RH^2eU!ct]~1޼yӨ&Z2|lTQ-HczG4 ETS/1mL=rp!֏z~9%Tm_EP*_T3U݇(cz+RB6yƾtqsnE 5 o⪭SBy %Ta5c)pS^㲾 *,j"wfsmo&%۩"0fTmP)n;߻?NLBQ:La[A*(֠?(BUCl*O8;_92|wdbVe ~u?go,%} oߑ Oe9[SBE+Yꉧ*OU6hРp]k}YS|v|f-x^GIRKvك#J'hIUʩk fL.H.dPYVvQuxmmg R\-ΖS#fbQi\`fLH@;S￯ﻷW<r8LV;￳7]4 uv9IFMb"M/?CBưXېн!B. 8/kh n'0f0 (}Q?>1])hލ6q"zR)!EDgo'@mn, Œ3Fڐ3|3'^5??t>ve'RKȁONsOEyg^ąed{Qnv/fF(;n1 H 5h("z:P:0Qc1NgsۉDZ l6' 'ḟ`=N=uFD|)J"@4ްN768.r𢰁d€h:$R$)H&pev_ JJD&3-͚ *Nx bGlf^Y}f?]Թ@GY=̏)[;~:a<5i*a4`39L"8FͥfWW6y*Ps9B(*G;(hHh_KBHאP ˤ?&5w{AJu2^qo ^<Ӎ-JER>}aZq~ևa̼*Am  ۧRMj[,2 E֘L2Q% 'aV:\Zn+ T4DTk}rZt K_O3rADU ? Tnho5L6WӌJV?bqj,Ӽ}n g=`șΉ#+ZKSKm~m_ֻ10 T&vt|/l' 0Td_173AKSqOǭ-[ӷuRyKdqV*:O"-AD0 㒰] WK>?MǕ*Rьkgtb=B1%HLVHG@RL@:7kEIQ$CkLDf]U01w-3Mb)U58V7xj; #T+ ψA @ӣxǾx;2@iIUP25!Z\%c42GS>L~%tܶ3DX:hF9u}]jVи.7EFt`tt& \T4ۦ1PGtT! MH%T$z%=WEu[̃&Q v^+'q ӯ"@{WKMmٲSv`$kj 2Bf~ Svh7gO]^Ei5L.NT4 dɎya)/ uxD,˨EN(WM[SF i^yk%%%4祋Jhq`@[yP (wpJM<۸,^*PY)P uTBPB>~Srm6}wo&Y! 4U\Z/S: f>[XNOB)+c??r}~L|4HRDUCJ4og 2kj8YU3Y0EHkJ;O #B8!IHB^.ߨ²Lf۱)J7Df:WȻL*IFfGHY3S/F GpI1]WqsO?WIG ^:V1ꍗFA~Z5.bM3{8}Y"XJ{CY΄*$YqB -5Yvq.R؇Nu%ԓ?7h nD*DN֖'NZ0v3]I"(PfTRjOMM̙12BL %8^@:{a Ë76/ް/̊ք&Tjpb;(ȏ+o]+&JNjKW6$TWqPa %*^k/۸IeeReK8e:N˫w[ UOQ_yJn[7zWB -%TmH_XO UB3a'}RBCBUDD)RBmGP P@f3 zC! 4y/q0 _g'O0L]I' \mW):&o%LzlʄXd3$.1py ?]Ka\41XX-S\fDB:Je.&^M uɯ]o".QC:AMp$6,CIH#z WP2WHBI/PGW#IBdp% :nx.#wr3}4}h|q]k}R)LXL;=i?v^Snz;W>5i?fDP`h;w5S@ Sqbb?ݻxEU9]aq(g"2Fu98 `.p~ڦʶ^|y5$.R&U,^>L]|).x|JqUT$l'೎h%JqM KIt\5"N mmO ,%$['4hР-GZ/̜M|>4Dt@kc;؊Rk y}F)LܤA?ןo?p۪^?33飭uiV ]"M$pښ{͵Vt$&;Ab{ _809}gInR- c$"7dA;f^fǁۊ,Oځ@[vFDwi::`%L#"C1X&ڑ5Iӛo{nO|fC5Jhz$!-)ٵJT$y~tT{q[g/^]aE'n,l: "7VTjwAuI9_ ZZ+{YA)"#+:Fj[!A[$nlŵ 5 vBo7 faܒY8Kh|n-~'+vQFtG&샙WןtPʰR0KڬҀct@}oc٭l(DHKH,[T{^˃jIqvGDQ-,j)RCS=:<ͼ`64eaFط#Rټ=9je 75+XʴPs7\]$!Є]|E,fja }@FmРa0h+J%Yz˓aq]kDeϺ~3?[ Bl2,!53n ̀v ;t_w~t! rmAHz*)M{^ 7}RڄI(zaE~ff L2R )2{P(ˠLHuCm3jjj6oʲ.T6nYK-PܻWh^_ιԒPڕ͍}EB>IM nTC"l+ѐ 4NxD&WOOڇ`08ADUٯw?mL-S 䀹 RF%b "355s#>wnYeX-H> Q% /_oͅPGzHP)jiͤʘ(DZ R-f@e"r^de.c}v-b CǶB8$Td"+&@IKJޒPŖHha>iޙT]4Y,,1Ϣ, `m6Z6St%NJ":{`bOiCT~p4kt]g"c{|$!-NQԄ*Q]@#G6%$fW^X[.qfL-RI\"t10b ǽcoح`lvZY 6 . VOB]PGܛ}F4hZ/Oڇ-OڇIBk}}'yeccI5JžnF`76~/rֵ8r k|X!(*Љ\8j3v}B]'Gb IDATSZV-f-ՍyH$a< ڤS[U}޽^+ڹ}BQ'AoF_je Y4OZLF@v# (Ìdⴔӓ1 -8[}o_>._%KDDίhs|r&S"8LQ?>W fewHwӁNm6h`G,KNHw]Q~NRj;`p(f~#"0SP~LδVPD3`(&wWzo^BUqY2B}Fj-q'#Ш8HhM6rB`KEWR6M=$H!hj<r|o3g_xہon(p\%T캦ÇK^Zpq4mjhVBEQB ނ:%4.bӠSyR,IϾshT[xG{ZJ}ߨL;=i?&zB:Zb0(ŐZn d"@P ޣl(%CSgo^|s_\_;! iBZR@#Q FVK  9~ VkqT(.:!oaޖ/[0>s3dJBK"ܚ!: sǺ p]XC]:<  tPGF4 -^#h 귏`p z&2)38TDP)m~*VrJxWO(@(%ݕ'<کA9 cq{>~ut8bE!7&j+E%Fq˖jWW;>W&J|7sGFtUn-\o޻\# G:l2'-q+q+3WFU8[=P&pcLuuu+l6Ieq*ZqzgI:̃9@GOi 4qh6M1Ǝ`0xnC)F>h?_V=m%ԹJT4 4IhOwB(!B:DI2FJ "\GD YY@FY Ce:mi 򜙙E-f`" zٛ{b\V$ГG rZb(Ҫta/I(Ʌ>>sgD$[>[GBJ_75V֝!͸ik9M1`\i 5M]*Hu:tc8ll^ܠ|sB "_\ucз*~V F-7n*>{4}8+&n-Oڇi#D)3{"ZfR@9JujilnLY0 RY0&!f DsnqZ̜Ymrʴ525X3(c3eK-'3\}<>pG;2o%!*0+*6FuM^XlyR(J*pڗ ?Č2F#tȏ: 5jEB46s@Z1!okƕ^d2Q6>ABgnjw;t`kAZ1c3KBeXy^I LpߓPwsMf FmРO#c7OOڇi#B:As"ZH# [X)XQ՝Тv\ UPԆ66f@f̦3ZT{(}i%bBX!JiCiv _<4]%'%^>`a@#Y*4%(֭ * h1 #ZbW rr>W$Wf A\h9ܺ!/oR^"ŤOVXJ 1)4uaz{77a/X@ڟ!n+$4 Q$T46j0_ކ6h` 4Stkb?ўYq'` 0é`ر E!U he-s ,j9.0HRMǘ 7M,f( ) ҰRC{4}XfO yy>L;SJ)ZT?9i?v ǁ &90R l:l6NVkTm}}%6Eng'!(fhV;C=x?~kyqf)Ѱ~׽$_4~o䖍=Oq|JR;C](J ML$t"s"6", -BVfBGW6f\o Hsu/y%}} $A,$H$b%P$ȂHUJ*SJJ*IKe;BItJ1$Y-2 ɒEQ(m0x3`7okoG>t f η>7㈒(UJ*fEyg.\c53n$-LvMzyy]I)o I[F ԗBGhfV)V_ʒI|}?z#_t&Age-I ,wӯhJ1P05iF~@O~o^Hmmw}`wt–ǷR/jRjsng2!t=j%؀p c!zuqSuz* pk!@KڄoBy?m`e+n͆=;ޱ|4qci%P.4ChqyX߲u`!HfB.U,-#ͅТWX¼Ban՗S4ajiED]\I1){7J)hZmHyHsu&˥^|{x '/49t{/96Jc R'[F+ɣPg 1sKiiHw-ki1,p& p>ワb;^iNkf!Rh)ۧ? pK@ : ̷ ݀6%Pæ٫*iD-z7Goma#ZSZ~^M}r'&O镌AhrӬІz  W]5@hrRN@X r s,wsueJ~_#'=}K!J1H{MkB P(#n]%G2wD%Jq@{Vy@і,(+(k\h5G(>S~ek7M @ʥY}˵If;Ƥl %x;/= ??>3_lп;|#zWJzʢ-WR%WG ڂOTH mG/oDFw--ܽnS:8(R_6k-B_d O5 :v|i >09R!['/>xk 6/Xsqp*o5FVh"͏ Ľ-Ʌ͈֗<&q`cK|0??ߚSsYN&di*c0F/B܌x@h@0-^^^WMJ'-LRx{JR>rrDD fgBB༔x^(|+7ǧN^FHμtWz뇻+;WX /ڜH[#fKQF3yX ogh k!޹Ֆb*(͗r (K|c+7v$vU+^} 쀧e6Á}ݯNv.B cẹd24g@xFeVTbFwǝmׄPl'nZgY*NwXI&TWBmAn:9fPcYF`="Z.nt=]|?b41rg$3G/)lp\=M+c/^Ovw;r2+Q|0+5z,IGEbm-UBA%}֦/e-@nev ­s޵ⶅ.8:'j@7HgnD1N%^H<;Px/q1f¬`Tn?9|Poov݃Q+l,itC-(s8P{Lptl٬^˲YTOgT=UgAȨe=R(s\/h7E$#a*5в˄м X:BG"Xq*ɯb'nuM, !"ttfYvnJDD)3iaC |{|/WQ$gxxq~p;6) EB,X)!!VhwRU?_HyIw. wC tCzGX)#)4Jvى ?όbQYvfDه^J+t fa;nS-mU; 7!ðeZbfB:jpb'w3L % ?3 }J Ņ|:'ӝl8qJ!*qJAh1B R$u_k/ͯQffוRʻ^aiWۃ ,u1o赡{,f0a' )ewn_{oogz{DVVrb2LI U›o2 / v_6 %0"<'t_Vg0?ƪu X~@.N= (gOcN YwG6;Ahl}ᮕ/˰Qb )lr6$go ,NY VfկA蔭O\5כ.YjAtktT6TI*Ree 5@02 on5^ܤڕR~ }&xV^[~iǁI-pBAܽsᅋPmmuFsGʁڿccm\)!`(߈8&++Q7VJ\ӥ"pu DbnID! 2i+f>7m`Y 3d:vdFkhۗCuVdC_&Nm'R kGޓP5ALBhI> F9 " NExq̤1wI hHt`>C%!m{@C|J)YԗXnTG 3om$<+<~t珞_9y\?Bcs,*Ā";f4*-TXn%SHt݋ (<+ ebt!PmV3&Ty!өOz>3u/4ؚcB aC ueYq5NDXB55foe>AnAakZo/ 2JcR4VB Vzq`2 è.3y%bSnDE#f1"~v*W^:ѐ3vRSp~]w_ۿ'/z2GCpy@VʰJ)B =O/S$@P(NiS;#/UR{1k5U(F&܇AV w6` L*<Ӱs!\ I#=! Z(cݍ(Æ"~ş&i|. ;#Gj.+5^䯕X4 E((ڥ r`LppÅbY7BAI8V e3fcX?BI }_(0XB̓VZ}-M%4P5 g\ONeo* ~@K!fB Nfqs!4ϲ! 0\ZKƓx$N HZ!bA@ipӭ[Ft>z֣JGRAm{PD̼oq&"f>w"Ġ $cuﭿݟ`oЊ'c[]:W65Jo t^iRkdqX)*`5!S=U] B7$̷9ҶJێyZdIF"q10Ľ1bi@CjNkHrk2._7:T:\ u[*5Y%߇-FIqr~Ek}^ N3d3ʿ l+]Ь̂P7zl 4@UGq"9KvDM,̜;\y-ZdBїSоB QaZȲ*ވ[F6tx(>^GIdxS}{[W7./[x[_os WeyAG ޱ[~Wn*,՟J'U60y]6ɶV&ة+h/$,VڄŶBZ]F[)i@@$^jMqunrY!tn]dC IDAT'Xak"|{;9^s޴&JP|XZׯdef2ձSzX[ku>XAh:ۮ,m3`ZZ *ZJd3`Nk^|3d oJ. 9Mppwq nFhOhO@IKlNRcR.j>cƷSlWY8@qCn;lٖM3'qkd{S@`ފUQزoG{$%g<.LZfmlg̚2Aq t X4?0׋YSgJ)F\k[_mu&PT_a˫Iuw/mg/3 ֥]~i_SaB95?5By 0f.C,xmQqIDIFBR,6 ЩLkF5UD$yD2㡠OͿ}m<Oߍ8^HC9wbjۚ%$ wwCm4ӢefQH-Z&KlXFuT]kFg~0x)$n<4jb,86GXlNXi4+\Z! 5꺵v6,][0F[n61P.̓`@hS&r:ZJ8UY9)[zJ(8QXA27_v-uE! 2R&*50嵏(y8VO~w}AMc[Y#Gxm`bMP9*JyI|7!40AGQIELkoif_셄wmrŐu VJw]Q m]jXŭ& F}k 4 򵻘4hNe`Mok4@hƬA(νga:6=?-6pUx{,H38mu9jO@3b7^^^^^^^^p4`vF 8~m~VNQUQ׊m`2)SZ7p5*A'$kgwRL2 dYe@kAtLXmҩ&?K6/w(-,]vZB`RTn?@5! ,, ~lXp50np-!tz5 Rt!TzZv ^^^^^^^^(7Ιp1ؐ;Ow$:qaLFYZ8zlҦѐ #=嶎E> ¥C.%P`橨W-;T y |jbуʯLleoiNImUEA+!Nia]%5{q tf̑^`w䌱Lw|ҙlUͰ2SZWגeI~{CYEu*BMyf}B,PxgCh⏖@XGoa}nFcSJJgWFF.CF' )en{gvoG;{sxvou= 5;xT2i\ʉ0Sh0FrKr4Ds5gFӝə&K^15V;ϐ,xie\C#Uן֩) v(5B d ,- H ^,;c3Jr]uvKK-B_Zê_PlZO)g]6ChSB}}.5]~Wt |x}&(qJ)s y @ ȯ7d0t_-^^^^^^^^@:Q80鶰Np\mw|왭]<  [p+ѯUXAs$]4k97{ۘk@JOHCDU6,ȡt6ڙ[{ɻ41+!Բ㕎I;r5l DT/m@Z䑔%`mc[)ڽ d 5d!1 -מ 7Ϙ ET:*!<5tBg3> BgbzT+<ōx Ze_ VgMWvE4%*J2 1H=L@m`L 2"_i %5MlC3.ss(7H[-¶nS1%Zp][޹ąb&#!w>zw}#yn$DHcG{G[XhooPcۭTQ0Q($81nFè,Jdw"Ġ $7tx_nynܶR+r& D`׏ʰլIͅ 3mʹ%T20ۑ$CrlY[0:[֬@ėIgUBZZBh} a$ P,| D~/j\jiAh(4N8,3b~p܋`1Vs-r7ɵJ6f1L7Qu{Wf׾ Ζ/N!ԙ96a9 6D-^_Pfd"vG L^0 *%UŔèUv?l Ɔ$ڻߺ?~ng0Lh]' T!nب *g) I0H`+M$l_&6"0Vq-p-캗 ;.Y5]\֜}Q\q]p 5A}a+*֐M>#Qs.Ȍdn'IB5.rs'h bncu.Υw TKPÃBhL^Uóvp%Av{ur$ Q&!@Yh4vJ7ZfWFN0ؔ/ڟݳ>GO7#5LZ>|(د&+^¥0`ZrVڽRI2-{1DpdǼRؑAXҥ׭ gK|~fMv5i `*M' 9'Bk+iV]bh*RB~V59?oPklb$ihMP2"͚ƴYkˁikkcnPܓT=GrFZ(֨Rb%JR0R٣0u]wR?@Ip QwƥO|Gv׿qn^Oڝ޼tSo,@3ϟdQHD\5yg_%ȷ 8RJ?@-<ݡنB,:~!!lS)N&jè53% 8'/,ć~}{޺n?;L(F.^oa@}/餣'.4 (@Z*HJ`Luv)2Hpo_qٛ:لS%đb-X%5gBhzд<ʭv-$νfVAjqQ)7봉d̅d[BugpF !*YGPrI]`Q*bd4f <iFrPQ4ih%0 URdx!M$Iqi"U4L,GQ  I-up~јQ///////k%-Y'F|jo{ݜZ#@2 Zү4T)zَ' IIdi޿tqV JŴ^5L Iv`L VnЁ<0hcA`eAlP.d3'K՜jrky¹e4L]f+\*]49mD͂Bh1z}-PjMkeaC=&gZ0K,F`X1: f(LT0`DQ vDJBq$RJIJqd( &3 Ib x;a^^^^^^^^J<j@cƩ"<ۗcw K.d4:^.0᱀6QJa)c4`6htmpD^ +@7⚥k֭bC(F;]t*&\S׽5ar dhqEb"„GKbjlXU2]}60۪ zigQB%J*ԟn9Ц M t%`J2DXd,Te2%3)H *̤p43RXEb"MW*8BKQ///////k f̡IJPA!cS < ?>v}p􅯞=;lG,/׎t\{ DJGa[Uāj{9 BݱF&7ppuN"[0Ȣ"ێյ BAʺݐ*\C%<.jճ@UVZj  =k(V%Pc,)*7PqRU4alB|^(0>'_$ɱH@DE4,v ! dHI!HdH)Ic@J`(z$XqO3ds-;Vz_|o<~?ܼ8Z{ٛߢ+Â"(Q۰W^t%N0AwөkRy1׺rCy1S]jsK!M,l pVS_GZsQΝq3$l5YI 4ݻr4V\"FM . X͒n˭K[52埩hK1ccveI*5R)qBqFUFL,ʘI)@*R%0/J&rS ;L>Q///////U`;1!$Ϸ;c8}=9hRwũm`Ұ"P @ fHpi^;nBJ֐ÖX Ci!r5-~ .]W7!E S֪ . &9\5w{2_6Ӱ.Eiq t@]jmmq\=6,u0Q!IH"2DDT^AA XPb^Vk2SIО F?dqN(NixX^/Z'V7~ jiXHcAw2a׾/~SIhN۝ŵ괱A)2*ua[!R݇smx!HV33C\+h5eil.KιSTȷñ$~ꂤ3"сW^6?B wXjeT>CZBc\lm i2&2S3cjo7fwcRe@K0eLq*8bVwq**xz/FPHY w[FrN&}㰅mpだO_~8<3sѨs!vk"(Kan*e@QU"Y,w)N L wY%hftef eXZF"+XΒE @)qqX画[=9?]_I $@BTk4tA\Y9B(M!=3=w1F2Sj2JUHN292& %%18`%L鈲ǔvt>KivV* +/x)x]Wy򺺒(8hD6CMH<Ǐ.?qٝΉnp8?>rQ<.qΞ$aBG.4bWRXTm{QRM~n( '9uV dLN jՏTōE BmP4@;&-Ho0ذAQhWj >D4n+?ID Y"GI"&8cNRFn?V>RXebbR_Ð{S{n7| _ü|+,HL! [aDyU3tlĹkl(ē R* )X)5K(_| ^X2눲ȭv /)PeTYLRS{:e&I'vmؐCq>p;ooƻ&Iw:zsJ腢 V廿S6B9kV{9Z:gAT4ʴZ =642ik[3+Eyb[Z6Ofx vr7 ̓A"30&YʊR!Sȭ̬2m)ňRx=Q\RnAggso?|ü._2uY[YA4scwӿyb I!ڋ+#Ֆ|; 4ټJ޼>ѳ1&2jAhY4 ,wZ*?,cI@9:K=PT&H ALWڢ,4F4wRF<ih4jO$fdΘR偀  @ũ: Iv1R<1RV8?\u/k"^^^^^^^^1s,g~>ʌL?zqs rw\65Ta Q {w5n&NeH  ʺX0ޘy$jǒf'& q9eJaO`[Q!t0PoURDU R1+n(62ədl7B)J)4Fn^^^^^^^^d&m`;p';m{$'.Nh0j:|A+k gZS &bR,BƊ9ec^@y8LrS|z_//++^^^^^^^^^be`y x7> XW~[[8Ar1D%)9ʼnQ,'@Ϟ ~oX9aol'^0嵯GpHp/>ץO#wp=^//W>^^^^^^^^^$3n2 Xh (b) ? (a) : (b)) #define min(a,b) ((a) < (b) ? (a) : (b)) #endif #define bound(a,b,c) ((a) >= (c) ? (a) : (b) < (a) ? (a) : (b) > (c) ? (c) : (b)) typedef unsigned char byte; // KJB Undefined true and false defined in SciTech's DEBUG.H header #undef true #undef false #ifndef __cplusplus typedef enum qbool_e {false, true} qbool; #else typedef bool qbool; #endif // not used anymore in mvdsv //#define MAX_INFO_STRING 196 #define MAX_SERVERINFO_STRING 512 #define MAX_LOCALINFO_STRING 32768 #define MAX_KEY_STRING 64 #define MAX_EXT_INFO_STRING 1024 #ifndef NULL #define NULL ((void *)0) #endif #define MAX_NUM_ARGVS 50 //============================================================================ #ifdef __cplusplus extern "C" { #endif short ShortSwap (short s); int LongSwap (int l); float FloatSwap (float f); #ifdef __cplusplus } /* extern "C" */ #endif #ifdef __BIG_ENDIAN__Q__ #define BigShort(x) (x) #define BigLong(x) (x) #define BigFloat(x) (x) #define LittleShort(x) ShortSwap(x) #define LittleLong(x) LongSwap(x) #define LittleFloat(x) FloatSwap(x) #elif defined(__LITTLE_ENDIAN__Q__) #define BigShort(x) ShortSwap(x) #define BigLong(x) LongSwap(x) #define BigFloat(x) FloatSwap(x) #define LittleShort(x) (x) #define LittleLong(x) (x) #define LittleFloat(x) (x) #elif defined(__PDP_ENDIAN__Q__) int LongSwapPDP2Big (int l); int LongSwapPDP2Lit (int l); float FloatSwapPDP2Big (float f); float FloatSwapPDP2Lit (float f); #define BigShort(x) ShortSwap(x) #define BigLong(x) LongSwapPDP2Big(x) #define BigFloat(x) FloatSwapPDP2Big(x) #define LittleShort(x) (x) #define LittleLong(x) LongSwapPDP2Lit(x) #define LittleFloat(x) FloatSwapPDP2Lit(x) #else #error Unknown byte order type! #endif //============================================================================ #ifdef _WIN32 #undef strcasecmp #undef strncasecmp #define strcasecmp(s1, s2) _stricmp ((s1), (s2)) #define strncasecmp(s1, s2, n) _strnicmp ((s1), (s2), (n)) int snprintf(char *str, size_t n, char const *fmt, ...); #endif #if (_MSC_VER && (_MSC_VER < 1400)) int vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); #endif #if defined(__linux__) || defined(_WIN32) size_t strlcpy (char *dst, const char *src, size_t siz); size_t strlcat (char *dst, const char *src, size_t siz); #endif #if !defined(__FreeBSD__) && !defined(__APPLE__) && !defined(__DragonFly__) char *strnstr (const char *s, const char *find, size_t slen); char *strcasestr(const char *s, const char *find); #endif #ifdef _WIN32 int strchrn (const char* str, const char c); #endif int Q_atoi (const char *str); float Q_atof (const char *str); //char *Q_ftos (float value); #define Q_rint(x) ((x) > 0 ? (int)((x) + 0.5) : (int)((x) - 0.5)) // does a varargs printf into a temp buffer #define MAX_STRINGS 16 // well, this used not only for va, anyway, static buffers is evil... char *va (const char *format, ...); void *Q_malloc (size_t size); void *Q_calloc (size_t n, size_t size); #define Q_free(ptr) if(ptr) { free(ptr); ptr = NULL; } char *Q_strdup (const char *src); char *COM_StripExtension (char *str); char *COM_FileExtension (const char *in); void COM_DefaultExtension (char *path, const char *extension); float AdjustAngle(float current, float ideal, float fraction); int wildcmp(char *wild, char *string); #endif /* !__BOTHDEFS_H__ */ mvdsv-0.35/src/bothtools.c000066400000000000000000000253161427146041000155560ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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 "qwsvdef.h" /* ============ va does a varargs printf into a temp buffer, so I don't need to have varargs versions of all text functions. ============ */ #define STRING_SIZE 1024 char *va (const char *format, ...) { va_list argptr; static char string[MAX_STRINGS][STRING_SIZE]; static int index1 = 0; index1 &= (MAX_STRINGS - 1); va_start (argptr, format); vsnprintf (string[index1], STRING_SIZE, format, argptr); va_end (argptr); return string[index1++]; } /* ============================================================================ LIBRARY REPLACEMENT FUNCTIONS ============================================================================ */ int Q_atoi (const char *str) { int val; int sign; int c; if (!str) return 0; for (; *str && *str <= ' '; str++); if (*str == '-') { sign = -1; str++; } else { if (*str == '+') str++; sign = 1; } val = 0; // // check for hex // if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) { str += 2; while (1) { c = (int)(unsigned char)*str++; if ( isdigit(c) ) val = (val<<4) + c - '0'; else if ( isxdigit(c) ) val = (val<<4) + tolower(c) - 'a' + 10; else return val*sign; } } // // check for character // if (str[0] == '\'') { return sign * str[1]; } // // assume decimal // while (1) { c = (int)(unsigned char)*str++; if ( !isdigit(c) ) return val*sign; val = val*10 + c - '0'; } return 0; } float Q_atof (const char *str) { double val; int sign; int c; int decimal, total; if (!str) return 0; for (; *str && *str <= ' '; str++); if (*str == '-') { sign = -1; str++; } else { if (*str == '+') str++; sign = 1; } val = 0; // // check for hex // if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) { str += 2; while (1) { c = (int)(unsigned char)*str++; if ( isdigit(c) ) val = (val*16) + c - '0'; else if ( isxdigit(c) ) val = (val*16) + tolower(c) - 'a' + 10; else return val*sign; } } // // check for character // if (str[0] == '\'') { return sign * str[1]; } // // assume decimal // decimal = -1; total = 0; while (1) { c = *str++; if (c == '.') { decimal = total; continue; } if ( !isdigit(c) ) break; val = val*10 + c - '0'; total++; } if (decimal == -1) return val*sign; while (total > decimal) { val /= 10; total--; } return val*sign; } // removes trailing zeros /*char *Q_ftos (float value) { static char str[128]; int i; snprintf (str, sizeof(str), "%f", value); for (i=strlen(str)-1 ; i>0 && str[i]=='0' ; i--) str[i] = 0; if (str[i] == '.') str[i] = 0; return str; }*/ #if defined(_MSC_VER) && (_MSC_VER < 1900) int snprintf(char *buffer, size_t count, char const *format, ...) { int ret; va_list argptr; if (!count) return 0; va_start(argptr, format); ret = _vsnprintf(buffer, count, format, argptr); buffer[count - 1] = 0; va_end(argptr); return ret; } #endif // !(Visual Studio 2015+) #if defined(_MSC_VER) && (_MSC_VER < 1400) int vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) { int ret; if (!count) return 0; ret = _vsnprintf(buffer, count, format, argptr); buffer[count - 1] = 0; return ret; } #endif #if defined(__linux__) || defined(_WIN32) /* * Functions strlcpy, strlcat, strnstr and strcasestr * was copied from FreeBSD 4.10 libc: src/lib/libc/string/ * * // VVD */ size_t strlcpy(char *dst, const char *src, size_t siz) { register char *d = dst; register const char *s = src; register size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0 && --n != 0) { do { if ((*d++ = *s++) == 0) break; } while (--n != 0); } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return(s - src - 1); /* count does not include NUL */ } size_t strlcat(char *dst, const char *src, size_t siz) { register char *d = dst; register const char *s = src; register size_t n = siz; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end */ while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = siz - dlen; if (n == 0) return(dlen + strlen(s)); while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; return(dlen + (s - src)); /* count does not include NUL */ } #endif #if !defined(__FreeBSD__) && !defined(__APPLE__) && !defined(__DragonFly__) char *strnstr (const char *s, const char *find, size_t slen) { char c, sc; size_t len; if ((c = *find++) != '\0') { len = strlen (find); do { do { if ((sc = *s++) == '\0' || slen-- < 1) return (NULL); } while (sc != c); if (len > slen) return (NULL); } while (strncmp (s, find, len) != 0); s--; } return ((char *)s); } /* * Find the first occurrence of find in s, ignore case. */ char *strcasestr(register const char *s, register const char *find) { register char c, sc; register size_t len; if ((c = *find++) != 0) { c = tolower((unsigned char)c); len = strlen(find); do { do { if ((sc = *s++) == 0) return (NULL); } while ((char)tolower((unsigned char)sc) != c); } while (strncasecmp(s, find, len) != 0); s--; } return ((char *)s); } #endif #ifdef _WIN32 int strchrn (const char* str, const char c) { int i = 0; while (*str) if (*str++ == c) ++i; return i; } #endif /* ============================================================================ BYTE ORDER FUNCTIONS ============================================================================ */ /*short ShortSwap (short s) { byte b1,b2; b1 = s&255; b2 = (s>>8)&255; return (b1<<8) + b2; } int LongSwap (int l) { byte b1,b2,b3,b4; b1 = l&255; b2 = (l>>8)&255; b3 = (l>>16)&255; b4 = (l>>24)&255; return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; } float FloatSwap (float f) { union { float f; byte b[4]; } dat1, dat2; dat1.f = f; dat2.b[0] = dat1.b[3]; dat2.b[1] = dat1.b[2]; dat2.b[2] = dat1.b[1]; dat2.b[3] = dat1.b[0]; return dat2.f; }*/ #ifndef id386 #ifdef __cplusplus extern "C" { #endif short ShortSwap (short s) { union { short s; byte b[2]; } dat1, dat2; dat1.s = s; dat2.b[0] = dat1.b[1]; dat2.b[1] = dat1.b[0]; return dat2.s; } int LongSwap (int l) { union { int l; byte b[4]; } dat1, dat2; dat1.l = l; dat2.b[0] = dat1.b[3]; dat2.b[1] = dat1.b[2]; dat2.b[2] = dat1.b[1]; dat2.b[3] = dat1.b[0]; return dat2.l; } float FloatSwap (float f) { union { float f; byte b[4]; } dat1, dat2; dat1.f = f; dat2.b[0] = dat1.b[3]; dat2.b[1] = dat1.b[2]; dat2.b[2] = dat1.b[1]; dat2.b[3] = dat1.b[0]; return dat2.f; } #ifdef __cplusplus } /* extern "C" */ #endif #endif #ifdef __PDP_ENDIAN__Q__ int LongSwapPDP2Big (int l) { union { int l; byte b[4]; } dat1, dat2; dat1.l = l; dat2.b[0] = dat1.b[1]; dat2.b[1] = dat1.b[0]; dat2.b[2] = dat1.b[3]; dat2.b[3] = dat1.b[2]; return dat2.l; } int LongSwapPDP2Lit (int l) { union { int l; short s[2]; } dat1, dat2; dat1.l = l; dat2.s[0] = dat1.s[1]; dat2.s[1] = dat1.s[0]; return dat2.l; } float FloatSwapPDP2Big (float f) { union { float f; byte b[4]; } dat1, dat2; dat1.f = f; dat2.b[0] = dat1.b[1]; dat2.b[1] = dat1.b[0]; dat2.b[2] = dat1.b[3]; dat2.b[3] = dat1.b[2]; return dat2.f; } float FloatSwapPDP2Lit (float f) { union { float f; short s[2]; } dat1, dat2; dat1.f = f; dat2.s[0] = dat1.s[1]; dat2.s[1] = dat1.s[0]; return dat2.f; } #endif /* =================== Q_malloc Use it instead of malloc so that if memory allocation fails, the program exits with a message saying there's not enough memory instead of crashing after trying to use a NULL pointer. It also sets memory to zero. =================== */ void *Q_malloc (size_t size) { void *p = malloc(size); //p = calloc(1, size); //malloc & memset or just calloc? if (!p) Sys_Error ("Q_malloc: Not enough memory free"); memset(p, 0, size); return p; } void *Q_calloc (size_t n, size_t size) { void *p = calloc(n, size); if (!p) Sys_Error ("Q_calloc: Not enough memory free"); return p; } /* =================== Q_strdup =================== */ char *Q_strdup (const char *src) { char *p = strdup(src); if (!p) Sys_Error ("Q_strdup: Not enough memory free"); return p; } /* ============ COM_StripExtension ============ */ char *COM_StripExtension (char *str) { char *p = strrchr(str, '.'); /* truncate extension */ if (p) *p = '\0'; return str; } /* ============ COM_FileExtension ============ */ char *COM_FileExtension (const char *in) { static char exten[8]; int i; in = strrchr(in, '.'); if (!in || strchr(in, '/')) return ""; in++; for (i=0 ; i<7 && *in ; i++,in++) exten[i] = *in; exten[i] = 0; return exten; } /* ================== COM_DefaultExtension If path doesn't have a .EXT, append extension (extension should include the .) ================== */ void COM_DefaultExtension (char *path, const char *extension) { char *src; src = path + strlen (path) - 1; while (*src != '/' && src != path) { if (*src == '.') return; // it has an extension src--; } strlcat (path, extension, MAX_OSPATH); } //===================================================== float AdjustAngle(float current, float ideal, float fraction) { float move = ideal - current; if (move >= 180) move -= 360; else if (move <= -180) move += 360; return current + fraction * move; } //======================================================= int wildcmp(char *wild, char *string) { char *cp=NULL, *mp=NULL; while ((*string) && (*wild != '*')) { if ((*wild != *string) && (*wild != '?')) { return 0; } wild++; string++; } while (*string) { if (*wild == '*') { if (!*++wild) //a * at the end of the wild string matches anything the checked string has { return 1; } mp = wild; cp = string+1; } else if ((*wild == *string) || (*wild == '?')) { wild++; string++; } else { wild = mp; string = cp++; } } while (*wild == '*') { wild++; } return !*wild; } mvdsv-0.35/src/bothtoolsa.s000066400000000000000000000021271427146041000157320ustar00rootroot00000000000000/* bothtools.s x86 assembly language math routines Copyright (C) 2006 VVD (vvd0@sorceforge.net). 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: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #include "asm_i386.h" #include "quakeasm.h" #ifdef id386 .text #define val 4 .globl C(ShortSwap) C(ShortSwap): /* movzwl val(%esp),%eax*/ movw val(%esp),%ax xchgb %al,%ah ret .globl C(LongSwap) C(LongSwap): .globl C(FloatSwap) C(FloatSwap): movl val(%esp),%eax bswap %eax ret #endif /* id386 */ mvdsv-0.35/src/bspfile.h000066400000000000000000000157421427146041000151740ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef __BSPFILE_H__ #define __BSPFILE_H__ // upper design bounds #define MAX_MAP_HULLS 4 #define MAX_MAP_MODELS 512 #define MAX_MAP_BRUSHES 4096 #define MAX_MAP_ENTITIES 1024 #define MAX_MAP_ENTSTRING 65536 #define MAX_MAP_PLANES 8192 #define MAX_MAP_NODES 32767 // because negative shorts are contents #define MAX_MAP_CLIPNODES 32767 // #define MAX_MAP_LEAFS 32767 // #define MAX_MAP_VERTS 65535 #define MAX_MAP_FACES 65535 #define MAX_MAP_MARKSURFACES 65535 #define MAX_MAP_TEXINFO 4096 #define MAX_MAP_EDGES 256000 #define MAX_MAP_SURFEDGES 512000 #define MAX_MAP_MIPTEX 0x200000 #define MAX_MAP_LIGHTING 0x100000 #define MAX_MAP_VISIBILITY 0x100000 // key / value pair sizes #define MAX_KEY 32 #define MAX_VALUE 1024 //============================================================================= #define Q1_BSPVERSION 29 #define HL_BSPVERSION 30 #define Q1_BSPVERSION29a (('2') + ('P' << 8) + ('S' << 16) + ('B' << 24)) #define Q1_BSPVERSION2 (('B') + ('S' << 8) + ('P' << 16) + ('2' << 24)) typedef struct { int fileofs, filelen; } lump_t; #define LUMP_ENTITIES 0 #define LUMP_PLANES 1 #define LUMP_TEXTURES 2 #define LUMP_VERTEXES 3 #define LUMP_VISIBILITY 4 #define LUMP_NODES 5 #define LUMP_TEXINFO 6 #define LUMP_FACES 7 #define LUMP_LIGHTING 8 #define LUMP_CLIPNODES 9 #define LUMP_LEAFS 10 #define LUMP_MARKSURFACES 11 #define LUMP_EDGES 12 #define LUMP_SURFEDGES 13 #define LUMP_MODELS 14 #define HEADER_LUMPS 15 #if defined(_MSC_VER) && !defined(__attribute__) #define __attribute__(A) /**/ #endif typedef struct { float mins[3], maxs[3]; float origin[3]; int headnode[MAX_MAP_HULLS]; int visleafs; // not including the solid leaf 0 int firstface, numfaces; } dmodel_t __attribute__((aligned(1))); typedef struct { int version; lump_t lumps[HEADER_LUMPS]; } dheader_t; typedef struct { int nummiptex; int dataofs[4]; // [nummiptex] } dmiptexlump_t; #define MIPLEVELS 4 typedef struct miptex_s { char name[16]; unsigned width, height; unsigned offsets[MIPLEVELS]; // four mip maps stored } miptex_t; typedef struct { float point[3]; } dvertex_t; // 0-2 are axial planes #define PLANE_X 0 #define PLANE_Y 1 #define PLANE_Z 2 // 3-5 are non-axial planes snapped to the nearest #define PLANE_ANYX 3 #define PLANE_ANYY 4 #define PLANE_ANYZ 5 typedef struct { float normal[3]; float dist; int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate } dplane_t; #define CONTENTS_EMPTY -1 #define CONTENTS_SOLID -2 #define CONTENTS_WATER -3 #define CONTENTS_SLIME -4 #define CONTENTS_LAVA -5 #define CONTENTS_SKY -6 typedef struct { int planenum; short children[2]; // negative numbers are -(leafs+1), not nodes short mins[3]; // for sphere culling short maxs[3]; unsigned short firstface; unsigned short numfaces; // counting both sides } dnode_t; typedef struct { int planenum; int children[2]; // negative numbers are -(leafs+1), not nodes short mins[3]; // for sphere culling short maxs[3]; unsigned int firstface; unsigned int numfaces; // counting both sides } dnode29a_t; typedef struct { int planenum; int children[2]; // negative numbers are -(leafs+1), not nodes float mins[3]; // for sphere culling float maxs[3]; unsigned int firstface; unsigned int numfaces; // counting both sides } dnode_bsp2_t; typedef struct { int planenum; short children[2]; // negative numbers are contents } dclipnode_t; typedef struct { int planenum; int children[2]; // negative numbers are contents } dclipnode29a_t; typedef struct { int planenum; int children[2]; // negative numbers are contents } mclipnode_t; typedef struct texinfo_s { float vecs[2][4]; // [s/t][xyz offset] int miptex; int flags; } texinfo_t; #define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision // note that edge 0 is never used, because negative edge nums are used for // counterclockwise use of the edge in a face typedef struct { unsigned short v[2]; // vertex numbers } dedge_t; typedef struct { unsigned int v[2]; // vertex numbers } dedge29a_t; #define MAXLIGHTMAPS 4 typedef struct { short planenum; short side; int firstedge; // we must support > 64k edges short numedges; short texinfo; // lighting info byte styles[MAXLIGHTMAPS]; int lightofs; // start of [numstyles*surfsize] samples } dface_t; typedef struct { int planenum; int side; int firstedge; // we must support > 64k edges int numedges; int texinfo; // lighting info byte styles[MAXLIGHTMAPS]; int lightofs; // start of [numstyles*surfsize] samples } dface29a_t; #define AMBIENT_WATER 0 #define AMBIENT_SKY 1 #define AMBIENT_SLIME 2 #define AMBIENT_LAVA 3 #define NUM_AMBIENTS 4 // automatic ambient sounds // leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas // all other leafs need visibility info typedef struct { int contents; int visofs; // -1 = no visibility info short mins[3]; // for frustum culling short maxs[3]; unsigned short firstmarksurface; unsigned short nummarksurfaces; byte ambient_level[NUM_AMBIENTS]; } dleaf_t; typedef struct { int contents; int visofs; // -1 = no visibility info short mins[3]; // for frustum culling short maxs[3]; unsigned int firstmarksurface; unsigned int nummarksurfaces; byte ambient_level[NUM_AMBIENTS]; } dleaf29a_t; typedef struct { int contents; int visofs; // -1 = no visibility info float mins[3]; // for frustum culling float maxs[3]; unsigned int firstmarksurface; unsigned int nummarksurfaces; byte ambient_level[NUM_AMBIENTS]; } dleaf_bsp2_t; #endif /* !__BSPFILE_H__ */ mvdsv-0.35/src/build.c000066400000000000000000000022561427146041000146360ustar00rootroot00000000000000/* build.c Build number and version strings Copyright (C) 1996-1997 Id Software, Inc. 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: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #include "qwsvdef.h" /* ======================= VersionStringFull ====================== */ char *VersionStringFull (void) { static char str[256]; if (!str[0]) { if (!strlen(GIT_COMMIT)) { snprintf(str, sizeof(str), SERVER_NAME " " SERVER_VERSION); } else { snprintf(str, sizeof(str), SERVER_NAME " " SERVER_VERSION " (build " GIT_COMMIT "-" QW_PLATFORM_SHORT ")"); } } return str; } mvdsv-0.35/src/central.c000066400000000000000000000476521427146041000152000ustar00rootroot00000000000000 // central.c - communication with central server #include "qwsvdef.h" #include #define GENERATE_CHALLENGE_PATH "Authentication/GenerateChallenge" #define VERIFY_RESPONSE_PATH "Authentication/VerifyResponse" #define CHECKIN_PATH "ServerApi/Checkin" #define MIN_CHECKIN_PERIOD 60 #ifdef _WIN32 #pragma comment(lib, "libcurld.lib") #pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "wldap32.lib") #endif static cvar_t sv_www_address = { "sv_www_address", "" }; static cvar_t sv_www_authkey = { "sv_www_authkey", "" }; static cvar_t sv_www_checkin_period = { "sv_www_checkin_period", "60" }; static CURLM* curl_handle = NULL; static double last_checkin_time; #define MAX_RESPONSE_LENGTH (4096*2) struct web_request_data_s; typedef void(*web_response_func_t)(struct web_request_data_s* req, qbool valid); static void Web_ConstructURL(char* url, const char* path, int sizeof_url); static int utf_encode_string(char* value, char* encoded_value); static void Web_SubmitRequestForm(const char* url, struct curl_httppost *first_form_ptr, struct curl_httppost *last_form_ptr, web_response_func_t callback, const char* requestId, void* internal_data); typedef struct web_request_data_s { CURL* handle; double time_sent; // response from server char response[MAX_RESPONSE_LENGTH]; size_t response_length; // form data struct curl_httppost *first_form_ptr; struct curl_httppost *last_form_ptr; // called when response complete web_response_func_t onCompleteCallback; void* internal_data; char* request_id; // if set, content will be passed to game-mod. will be Q_free()'d struct web_request_data_s* next; } web_request_data_t; static int utf8Encode(char in, char* out1, char* out2) { if (in <= 0x7F) { // <= 127 is encoded as single byte, no translation *out1 = in; return 1; } else { // Two byte characters... 5 bits then 6 *out1 = 0xC0 | ((in >> 6) & 0x1F); *out2 = 0x80 | (in & 0x3F); return 2; } } static web_request_data_t* web_requests; static qbool CheckFileExists(const char* path) { FILE* f; if (!(f = fopen(path, "rb"))) { return false; } fclose(f); return true; } size_t Web_StandardTokenWrite(void* buffer, size_t size, size_t nmemb, void* userp) { web_request_data_t* data = (web_request_data_t*)userp; size_t available = sizeof(data->response) - data->response_length; if (size * nmemb > available) { Con_DPrintf("WWW: Response too large, size*nmemb = %d, available %d\n", size * nmemb, available); return 0; } else if (size * nmemb > 0) { Con_DPrintf("WWW: response received, writing %d bytes\n", size * nmemb); memcpy(data->response + data->response_length, buffer, size * nmemb); data->response_length += size * nmemb; Con_DPrintf("WWW: response_length now %d bytes\n", data->response_length); } return size * nmemb; } typedef struct response_field_s { const char* name; const char** value; } response_field_t; static int ProcessWebResponse(web_request_data_t* req, response_field_t* fields, int field_count) { char* colon, *newline; char* start = req->response; int i; int total_fields = 0; // Response should be multiple lines, :\n // For multi-line values, prefix end of line with $ req->response[req->response_length] = '\0'; while ((colon = strchr(start, ':'))) { newline = strchr(colon, '\n'); if (newline) { while (newline && newline != colon + 1 && *(newline - 1) == '$') { *(newline - 1) = ' '; newline = strchr(newline + 1, '\n'); } if (newline) { *newline = '\0'; } } *colon = '\0'; for (i = 0; i < field_count; ++i) { if (!strcmp(start, fields[i].name)) { *fields[i].value = colon + 1; } } if (newline == NULL) { break; } start = newline + 1; while (*start && (*start == 10 || *start == 13)) { ++start; } ++total_fields; } return total_fields; } void Auth_GenerateChallengeResponse(web_request_data_t* req, qbool valid) { client_t* client = (client_t*) req->internal_data; const char* response = NULL; const char* challenge = NULL; const char* message = NULL; response_field_t fields[] = { { "Result", &response }, { "Challenge", &challenge }, { "Message", &message } }; if (client->login_request_time != req->time_sent) { // Ignore result, subsequent request sent return; } req->internal_data = NULL; ProcessWebResponse(req, fields, sizeof(fields) / sizeof(fields[0])); if (response && !strcmp(response, "Success") && challenge) { char buffer[128]; strlcpy(buffer, "//challenge ", sizeof(buffer)); strlcat(buffer, challenge, sizeof(buffer)); strlcat(buffer, "\n", sizeof(buffer)); strlcpy(client->login_challenge, challenge, sizeof(client->login_challenge)); SV_ClientPrintf2(client, PRINT_HIGH, "Challenge stored...\n", message); ClientReliableWrite_Begin (client, svc_stufftext, 2+strlen(buffer)); ClientReliableWrite_String (client, buffer); } else if (message) { SV_ClientPrintf2(client, PRINT_HIGH, "Error: %s\n", message); } else { // Maybe add CURLOPT_ERRORBUFFER? SV_ClientPrintf2(client, PRINT_HIGH, "Error: unknown error\n"); } } void Auth_ProcessLoginAttempt(web_request_data_t* req, qbool valid) { client_t* client = (client_t*) req->internal_data; const char* response = NULL; const char* login = NULL; const char* preferred_alias = NULL; const char* message = NULL; const char* flag = NULL; const char* confirmation = NULL; response_field_t fields[] = { { "Result", &response }, { "Alias", &preferred_alias }, { "Login", &login }, { "Message", &message }, { "Flag", &flag }, { "Confirmation", &confirmation } }; req->internal_data = NULL; if (client->login_request_time != req->time_sent) { // Ignore result, subsequent request sent return; } ProcessWebResponse(req, fields, sizeof(fields) / sizeof(fields[0])); if (response && !strcmp(response, "Success")) { if (login) { char oldval[MAX_EXT_INFO_STRING]; strlcpy(oldval, Info_Get(&client->_userinfo_ctx_, "*auth"), sizeof(oldval)); strlcpy(client->login, login, sizeof(client->login)); Info_SetStar(&client->_userinfo_ctx_, "*auth", login); ProcessUserInfoChange(client, "*auth", oldval); } if (confirmation) { strlcpy(client->login_confirmation, confirmation, sizeof(client->login_confirmation)); } { char oldval[MAX_EXT_INFO_STRING]; flag = (flag ? flag : ""); strlcpy(oldval, Info_Get(&client->_userinfo_ctx_, "*flag"), sizeof(oldval)); strlcpy(client->login_flag, flag, sizeof(client->login_flag)); Info_SetStar(&client->_userinfo_ctx_, "*flag", flag); ProcessUserInfoChange(client, "*flag", oldval); } preferred_alias = preferred_alias ? preferred_alias : login; if (preferred_alias) { strlcpy(client->login_alias, preferred_alias, sizeof(client->login_alias)); } client->logged_in_via_web = true; SV_LoginWebCheck(client); } else if (message) { SV_ClientPrintf2(client, PRINT_HIGH, "Error: %s\n", message); SV_LoginWebFailed(client); } else { // Maybe add CURLOPT_ERRORBUFFER? SV_ClientPrintf2(client, PRINT_HIGH, "Error: unknown error (invalid response from server)\n"); SV_LoginWebFailed(client); } } void Web_PostResponse(web_request_data_t* req, qbool valid) { if (valid) { const char* broadcast = NULL; const char* upload = NULL; const char* uploadPath = NULL; response_field_t fields[] = { { "Broadcast", &broadcast }, { "UploadPath", &uploadPath }, { "Upload", &upload } }; req->response[req->response_length] = '\0'; Con_DPrintf("Response from web server:\n"); Con_DPrintf(req->response); ProcessWebResponse(req, fields, sizeof(fields) / sizeof(fields[0])); if (broadcast) { // Server is making announcement SV_BroadcastPrintfEx(PRINT_HIGH, 0, "%s\n", broadcast); } if (upload && uploadPath) { if (strstr(uploadPath, "//") || FS_UnsafeFilename(upload)) { Con_Printf("Upload request deemed unsafe, ignoring...\n"); } else { if (!strncmp(upload, "demos/", 6)) { // Ok - could be demo_dir, or full path char demoName[MAX_OSPATH]; if (sv_demoDir.string[0]) { char url[512]; struct curl_httppost *first_form_ptr = NULL; struct curl_httppost *last_form_ptr = NULL; snprintf(demoName, sizeof(demoName), "%s/%s/%s", fs_gamedir, sv_demoDir.string, upload + 6); if (!CheckFileExists(demoName) && sv_demoDirAlt.string[0]) { snprintf(demoName, sizeof(demoName), "%s/%s/%s", fs_gamedir, sv_demoDirAlt.string, upload + 6); } if (CheckFileExists(demoName)) { Web_ConstructURL(url, uploadPath, sizeof(url)); curl_formadd(&first_form_ptr, &last_form_ptr, CURLFORM_PTRNAME, "file", CURLFORM_FILE, demoName, CURLFORM_END ); curl_formadd(&first_form_ptr, &last_form_ptr, CURLFORM_COPYNAME, "localPath", CURLFORM_COPYCONTENTS, upload, CURLFORM_END ); Web_SubmitRequestForm(url, first_form_ptr, last_form_ptr, Web_PostResponse, "upload", NULL); Con_Printf("Uploading %s...\n", demoName); } else { Con_Printf("Couldn't find file %s, ignoring...\n", demoName); } } } else { Con_Printf("Upload request folder not authorised, ignoring...\n"); } } } } else { Con_Printf("Failure contacting central server.\n"); } if (req->internal_data) { Q_free(req->internal_data); } } static void Web_SubmitRequestForm(const char* url, struct curl_httppost *first_form_ptr, struct curl_httppost *last_form_ptr, web_response_func_t callback, const char* requestId, void* internal_data) { CURL* req = curl_easy_init(); web_request_data_t* data = Q_malloc(sizeof(web_request_data_t)); CURLFORMcode code = curl_formadd(&first_form_ptr, &last_form_ptr, CURLFORM_PTRNAME, "authKey", CURLFORM_COPYCONTENTS, sv_www_authkey.string, CURLFORM_END ); data->onCompleteCallback = callback; data->time_sent = curtime; data->internal_data = internal_data; data->handle = req; data->request_id = requestId && requestId[0] ? strdup(requestId) : NULL; data->first_form_ptr = first_form_ptr; curl_easy_setopt(req, CURLOPT_URL, url); curl_easy_setopt(req, CURLOPT_WRITEDATA, data); curl_easy_setopt(req, CURLOPT_WRITEFUNCTION, Web_StandardTokenWrite); if (first_form_ptr) { curl_easy_setopt(req, CURLOPT_POST, 1); curl_easy_setopt(req, CURLOPT_HTTPPOST, first_form_ptr); } curl_multi_add_handle(curl_handle, req); data->next = web_requests; web_requests = data; } void Central_VerifyChallengeResponse(client_t* client, const char* challenge, const char* response) { char url[512]; struct curl_httppost *first_form_ptr = NULL; struct curl_httppost *last_form_ptr = NULL; CURLFORMcode code; if (!sv_www_address.string[0]) { SV_ClientPrintf2(client, PRINT_HIGH, "Remote logins not supported on this server\n"); return; } code = curl_formadd(&first_form_ptr, &last_form_ptr, CURLFORM_PTRNAME, "challenge", CURLFORM_COPYCONTENTS, challenge, CURLFORM_END ); code = curl_formadd(&first_form_ptr, &last_form_ptr, CURLFORM_PTRNAME, "response", CURLFORM_COPYCONTENTS, response, CURLFORM_END ); Web_ConstructURL(url, VERIFY_RESPONSE_PATH, sizeof(url)); client->login_request_time = curtime; Web_SubmitRequestForm(url, first_form_ptr, last_form_ptr, Auth_ProcessLoginAttempt, NULL, client); } void Central_GenerateChallenge(client_t* client, const char* username, qbool during_login) { char url[512]; struct curl_httppost *first_form_ptr = NULL; struct curl_httppost *last_form_ptr = NULL; CURLFORMcode code; if (!sv_www_address.string[0]) { SV_ClientPrintf2(client, PRINT_HIGH, "Remote logins not supported on this server\n"); return; } Web_ConstructURL(url, GENERATE_CHALLENGE_PATH, sizeof(url)); code = curl_formadd(&first_form_ptr, &last_form_ptr, CURLFORM_PTRNAME, "userName", CURLFORM_COPYCONTENTS, username, CURLFORM_END ); code = curl_formadd(&first_form_ptr, &last_form_ptr, CURLFORM_PTRNAME, "status", CURLFORM_COPYCONTENTS, during_login ? "0" : "1", CURLFORM_END ); client->login_request_time = curtime; Web_SubmitRequestForm(url, first_form_ptr, last_form_ptr, Auth_GenerateChallengeResponse, NULL, client); } static void Web_ConstructURL(char* url, const char* path, int sizeof_url) { strlcpy(url, sv_www_address.string, sizeof_url); if (url[strlen(url) - 1] != '/') { strlcat(url, "/", sizeof_url); } while (*path == '/') { ++path; } strlcat(url, path, sizeof_url); } #define MAX_ENCODED_STRINGLEN 128 static qbool Web_AddParametersToRequest(int first_param, struct curl_httppost** first_form_ptr, struct curl_httppost** last_form_ptr) { int i; for (i = first_param; i < Cmd_Argc() - 1; i += 2) { char encoded_value[MAX_ENCODED_STRINGLEN]; int encoded_length = 0; int j; char* name; char* value; CURLFORMcode code; name = Cmd_Argv(i); value = Cmd_Argv(i + 1); if (!strcmp(name, "*internal")) { if (!strcmp(value, "authinfo")) { int login_count = 0; // Also submit authentication challenges/responses for (j = 0; j < MAX_CLIENTS; ++j) { if (svs.clients[j].state > cs_free) { if (svs.clients[j].login[0] && svs.clients[j].logged_in_via_web) { name = va("logins[%d].name", login_count); utf_encode_string(svs.clients[j].login, encoded_value); code = curl_formadd(first_form_ptr, last_form_ptr, CURLFORM_COPYNAME, name, CURLFORM_COPYCONTENTS, encoded_value, CURLFORM_END ); if (code != CURLE_OK) { Con_Printf("Request failed (adding to form)\n"); return false; } name = va("logins[%d].token", login_count); utf_encode_string(svs.clients[j].login_confirmation, encoded_value); code = curl_formadd(first_form_ptr, last_form_ptr, CURLFORM_COPYNAME, name, CURLFORM_COPYCONTENTS, encoded_value, CURLFORM_END ); if (code != CURLE_OK) { Con_Printf("Request failed (adding to form)\n"); return false; } ++login_count; } } } snprintf(encoded_value, sizeof(encoded_value), "%d", login_count); code = curl_formadd(first_form_ptr, last_form_ptr, CURLFORM_COPYNAME, "logincount", CURLFORM_COPYCONTENTS, encoded_value, CURLFORM_END ); if (code != CURLE_OK) { Con_Printf("Request failed (adding to form)\n"); return false; } } continue; } else { utf_encode_string(value, encoded_value); } code = curl_formadd(first_form_ptr, last_form_ptr, CURLFORM_COPYNAME, name, CURLFORM_COPYCONTENTS, encoded_value, CURLFORM_END ); if (code != CURLE_OK) { Con_Printf("Request failed (adding to form)\n"); return false; } } return true; } static void Web_SendRequest(qbool post) { struct curl_httppost *first_form_ptr = NULL; struct curl_httppost *last_form_ptr = NULL; char url[512]; char* requestId = NULL; if (!sv_www_address.string[0]) { Con_Printf("Address not set - functionality disabled\n"); return; } if (Cmd_Argc() < 3) { Con_Printf("Usage: %s ( )*\n", Cmd_Argv(0)); return; } Web_ConstructURL(url, Cmd_Argv(1), sizeof(url)); requestId = Cmd_Argv(2); if (requestId[0]) { requestId = Q_strdup(requestId); } else { requestId = NULL; } if (!Web_AddParametersToRequest(3, &first_form_ptr, &last_form_ptr)) { curl_formfree(first_form_ptr); return; } Web_SubmitRequestForm(url, first_form_ptr, last_form_ptr, Web_PostResponse, requestId, NULL); } int utf_encode_string(char* value, char* encoded_value) { int j; int encoded_length = 0; for (j = 0; j < strlen(value) && encoded_length < MAX_ENCODED_STRINGLEN - 2; ++j) { encoded_length += utf8Encode(value[j], &encoded_value[encoded_length], &encoded_value[encoded_length + 1]); } encoded_value[encoded_length] = '\0'; return encoded_length; } static void Web_GetRequest_f(void) { Web_SendRequest(false); } static void Web_PostRequest_f(void) { Web_SendRequest(true); } static void Web_PostFileRequest_f(void) { struct curl_httppost *first_form_ptr = NULL; struct curl_httppost *last_form_ptr = NULL; char* requestId = NULL; char url[512]; char path[MAX_OSPATH]; CURLFORMcode code = 0; const char* specified; if (!sv_www_address.string[0]) { Con_Printf("Address not set - functionality disabled\n"); return; } if (Cmd_Argc() < 4) { Con_Printf("Usage: %s ( )*\n", Cmd_Argv(0)); return; } requestId = Cmd_Argv(2); if (requestId[0]) { requestId = Q_strdup(requestId); } else { requestId = NULL; } specified = Cmd_Argv(3); if (specified[0] == '*' && specified[1] == '\0') { const char* demoname = SV_MVDDemoName(); if (!sv.mvdrecording || !demoname) { Con_Printf("Not recording demo!\n"); return; } snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(demoname)); } else { if (strstr(specified, ".cfg") || FS_UnsafeFilename(specified)) { Con_Printf("Filename invalid\n"); return; } snprintf(path, MAX_OSPATH, "%s/%s", fs_gamedir, specified); } if (FS_UnsafeFilename(path)) { Con_Printf("Filename invalid\n"); return; } Web_ConstructURL(url, Cmd_Argv(1), sizeof(url)); if (! CheckFileExists(path)) { Con_Printf("Failed to open file\n"); return; } code = curl_formadd(&first_form_ptr, &last_form_ptr, CURLFORM_PTRNAME, "file", CURLFORM_FILE, path, CURLFORM_END ); if (code != CURLE_OK) { Con_Printf("Request failed (adding file to form)\n"); return; } // Optional parameters to be sent to web server if (!Web_AddParametersToRequest(4, &first_form_ptr, &last_form_ptr)) { curl_formfree(first_form_ptr); return; } Web_SubmitRequestForm(url, first_form_ptr, last_form_ptr, Web_PostResponse, requestId, NULL); } void Central_ProcessResponses(void) { CURLMsg* msg; int running_handles = 0; int messages_in_queue = 0; qbool server_busy = false; if (!last_checkin_time) { last_checkin_time = curtime; return; } curl_multi_perform(curl_handle, &running_handles); server_busy = running_handles || GameStarted(); while ((msg = curl_multi_info_read(curl_handle, &messages_in_queue))) { if (msg->msg == CURLMSG_DONE) { CURL* handle = msg->easy_handle; CURLcode result = msg->data.result; web_request_data_t** list_pointer = &web_requests; curl_multi_remove_handle(curl_handle, handle); while (*list_pointer) { web_request_data_t* this = *list_pointer; if (this->handle == handle) { // Remove from queue immediately *list_pointer = this->next; if (this->request_id && !strcmp(this->request_id, "upload")) { this = this; } if (this->onCompleteCallback) { if (result == CURLE_OK) { this->onCompleteCallback(this, true); } else { Con_DPrintf("ERROR: %s\n", curl_easy_strerror(result)); this->onCompleteCallback(this, false); } } else { if (result != CURLE_OK) { Con_DPrintf("ERROR: %s\n", curl_easy_strerror(result)); } else { Con_DPrintf("WEB OK, no callback\n"); } } // free memory curl_formfree(this->first_form_ptr); Q_free(this->request_id); Q_free(this); break; } list_pointer = &this->next; } curl_easy_cleanup(handle); } } if (sv_www_address.string[0] && !server_busy && curtime - last_checkin_time > max(MIN_CHECKIN_PERIOD, sv_www_checkin_period.value)) { char url[512]; Web_ConstructURL(url, CHECKIN_PATH, sizeof(url)); Web_SubmitRequestForm(url, NULL, NULL, Web_PostResponse, NULL, NULL); last_checkin_time = curtime; } else if (server_busy) { last_checkin_time = curtime; } } void Central_Shutdown(void) { if (curl_handle) { curl_multi_cleanup(curl_handle); curl_handle = NULL; } curl_global_cleanup(); } void Central_Init(void) { curl_global_init(CURL_GLOBAL_DEFAULT); Cvar_Register(&sv_www_address); Cvar_Register(&sv_www_authkey); Cvar_Register(&sv_www_checkin_period); curl_handle = curl_multi_init(); if (curl_handle) { Cmd_AddCommand("sv_web_get", Web_GetRequest_f); Cmd_AddCommand("sv_web_post", Web_PostRequest_f); Cmd_AddCommand("sv_web_postfile", Web_PostFileRequest_f); } } mvdsv-0.35/src/central.h000066400000000000000000000010501427146041000151630ustar00rootroot00000000000000 #ifndef CENTRAL_H #define CENTRAL_H void Central_Init(void); void Central_Shutdown(void); void Central_ProcessResponses(void); // void Central_SubmitGame(const char* path); // Creates a challenge/response on the web server after user claims to be 'username' void Central_GenerateChallenge(client_t* client, const char* username, qbool during_login); // Checks with the server if a client's response to a challenge is correct void Central_VerifyChallengeResponse(client_t* client, const char* challenge, const char* response); #endif // !CENTRAL_H mvdsv-0.35/src/cmd.c000066400000000000000000000503501427146041000143000ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // cmd.c -- Quake script command processing module #include "qwsvdef.h" cbuf_t cbuf_main; cbuf_t *cbuf_current = NULL; //============================================================================= /* ============ Cmd_Wait_f Causes execution of the remainder of the command buffer to be delayed until next frame. This allows commands like: bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2" ============ */ void Cmd_Wait_f (void) { if (cbuf_current) cbuf_current->wait = true; } /* ============================================================================= COMMAND BUFFER ============================================================================= */ void Cbuf_AddText (const char *text) { Cbuf_AddTextEx (&cbuf_main, text); } void Cbuf_InsertText (const char *text) { Cbuf_InsertTextEx (&cbuf_main, text); } void Cbuf_Execute () { Cbuf_ExecuteEx (&cbuf_main); } /* ============ Cbuf_Init ============ */ void Cbuf_Init (void) { cbuf_main.text_start = cbuf_main.text_end = MAXCMDBUF / 2; cbuf_main.wait = false; } /* ============ Cbuf_AddText Adds command text at the end of the buffer ============ */ void Cbuf_AddTextEx (cbuf_t *cbuf, const char *text) { size_t len; unsigned int new_start; unsigned int new_bufsize; len = strlen (text); if (cbuf->text_end + len <= MAXCMDBUF) { memcpy (cbuf->text_buf + cbuf->text_end, text, len); cbuf->text_end += len; return; } new_bufsize = cbuf->text_end-cbuf->text_start+len; if (new_bufsize > MAXCMDBUF) { Con_Printf ("Cbuf_AddText: overflow\n"); return; } // Calculate optimal position of text in buffer new_start = (MAXCMDBUF - new_bufsize) / 2; memcpy (cbuf->text_buf + new_start, cbuf->text_buf + cbuf->text_start, cbuf->text_end-cbuf->text_start); memcpy (cbuf->text_buf + new_start + cbuf->text_end-cbuf->text_start, text, len); cbuf->text_start = new_start; cbuf->text_end = cbuf->text_start + new_bufsize; } /* ============ Cbuf_InsertText Adds command text immediately after the current command Adds a \n to the text ============ */ void Cbuf_InsertTextEx (cbuf_t *cbuf, const char *text) { size_t len; unsigned int new_start; unsigned int new_bufsize; len = strlen(text); if (len < cbuf->text_start) { memcpy (cbuf->text_buf + (cbuf->text_start - len - 1), text, len); cbuf->text_buf[cbuf->text_start-1] = '\n'; cbuf->text_start -= len + 1; return; } new_bufsize = cbuf->text_end - cbuf->text_start + len + 1; if (new_bufsize > MAXCMDBUF) { Con_Printf ("Cbuf_InsertText: overflow\n"); return; } // Calculate optimal position of text in buffer new_start = (MAXCMDBUF - new_bufsize) / 2; memmove (cbuf->text_buf + (new_start + len + 1), cbuf->text_buf + cbuf->text_start, cbuf->text_end-cbuf->text_start); memcpy (cbuf->text_buf + new_start, text, len); cbuf->text_buf[new_start + len] = '\n'; cbuf->text_start = new_start; cbuf->text_end = cbuf->text_start + new_bufsize; } /* ============ Cbuf_Execute ============ */ void Cbuf_ExecuteEx (cbuf_t *cbuf) { int i; char *text; char line[1024]; int quotes; int cursize; int semicolon = 0; cbuf_current = cbuf; while (cbuf->text_end > cbuf->text_start) { // find a \n or ; line break text = (char *)cbuf->text_buf + cbuf->text_start; cursize = cbuf->text_end - cbuf->text_start; quotes = 0; for (i = 0; i < cursize; i++) { if (text[i] == '"') quotes++; /* EXPERIMENTAL: Forbid ';' as commands separator, because ktpro didn't quote arguments from admin users. Example: cmd fkick "N;quit" => kick N;quit => server will exit.*/ if (!(quotes & 1) && text[i] == ';') // don't break if inside a quoted string { switch (semicolon) { case 0: case 3: semicolon = 1; break; case 1: semicolon = 2; break; default:; } break; } if (text[i] == '\n') { switch (semicolon) { case 1: case 2: semicolon = 3; break; case 3: semicolon = 0; break; default:; } break; } } // don't execute lines without ending \n; this fixes problems with // partially stuffed aliases not being executed properly if (i < sizeof(line)) { memcpy(line, text, i); line[i] = 0; if (i > 0 && line[i - 1] == '\r') line[i - 1] = 0; // remove DOS ending CR } else { line[0] = 0; Sys_Printf("Cbuf_ExecuteEx: too long\n"); } // delete the text from the command buffer and move remaining commands down // this is necessary because commands (exec, alias) can insert data at the // beginning of the text buffer if (i == cursize) { cbuf->text_start = cbuf->text_end = MAXCMDBUF / 2; } else { i++; cbuf->text_start += i; } // security bugfix in ktpro if (semicolon > 1) Sys_Printf("ATTENTION: possibly tried to use security hole, " "server don't run command after ';'!\nCommand: %s\n", line); else // execute the command line Cmd_ExecuteString (line); if (cbuf->wait) { // skip out while text still remains in buffer, leaving it // for next frame cbuf->wait = false; break; } } cbuf_current = NULL; } /* ============================================================================== SCRIPT COMMANDS ============================================================================== */ /* =============== Cmd_StuffCmds_f Adds command line parameters as script statements Commands lead with a +, and continue until a - or another + quake +prog jctest.qp +cmd amlev1 quake -nosound +cmd amlev1 =============== */ void Cmd_StuffCmds_f (void) { int i, j; int s; char *text, *build, c; // build the combined string to parse from s = 0; for (i = 1; i < com_argc; i++) s += strlen (com_argv[i]) + 1; if (!s) return; text = (char *) Q_malloc (s+1); text[0] = 0; for (i = 1; i < com_argc; i++) { strlcat (text, com_argv[i], s + 1); if (i != com_argc-1) strlcat (text, " ", s + 1); } // pull out the commands build = (char *) Q_malloc (s+1); build[0] = 0; for (i=0 ; i : execute a script file\n"); return; } // FIXME: is this safe freeing the hunk here??? mark = Hunk_LowMark (); f = (char *)FS_LoadHunkFile (Cmd_Argv(1), NULL); if (!f) { Con_Printf ("couldn't exec %s\n",Cmd_Argv(1)); return; } Con_Printf ("execing %s\n",Cmd_Argv(1)); Cbuf_InsertText (f); Hunk_FreeToLowMark (mark); } /* =============== Cmd_Echo_f Just prints the rest of the line to the console =============== */ void Cmd_Echo_f (void) { int i; for (i=1 ; inext) Con_Printf ("%s : %s\n\n", a->name, a->value); return; } s = Cmd_Argv(1); if (strlen(s) >= MAX_ALIAS_NAME) { Con_Printf ("Alias name is too long\n"); return; } #if 0 if ( (var = Cvar_Find(s)) != NULL ) { if (var->flags & CVAR_USER_CREATED) Cvar_Delete (var->name); else { // Con_Printf ("%s is a variable\n"); return; } } #endif key = Com_HashKey (s); // if the alias already exists, reuse it for (a = cmd_alias_hash[key] ; a ; a=a->hash_next) { if (!strcasecmp(a->name, s)) { Q_free (a->value); break; } } if (!a) { a = (cmd_alias_t*) Q_malloc (sizeof(cmd_alias_t)); a->next = cmd_alias; cmd_alias = a; a->hash_next = cmd_alias_hash[key]; cmd_alias_hash[key] = a; } strlcpy (a->name, s, MAX_ALIAS_NAME); // copy the rest of the command line cmd[0] = 0; // start out with a null string for (i=2 ; i 2) strlcat (cmd, " ", sizeof(cmd)); strlcat (cmd, Cmd_Argv(i), sizeof(cmd)); } a->value = Q_strdup (cmd); } qbool Cmd_DeleteAlias (const char *name) { cmd_alias_t *a, *prev; int key; key = Com_HashKey (name); prev = NULL; for (a = cmd_alias_hash[key] ; a ; a = a->hash_next) { if (!strcasecmp(a->name, name)) { // unlink from hash if (prev) prev->hash_next = a->hash_next; else cmd_alias_hash[key] = a->hash_next; break; } prev = a; } if (!a) return false; // not found prev = NULL; for (a = cmd_alias ; a ; a = a->next) { if (!strcasecmp(a->name, name)) { // unlink from alias list if (prev) prev->next = a->next; else cmd_alias = a->next; // free Q_free (a->value); Q_free (a); return true; } prev = a; } Sys_Error ("Cmd_DeleteAlias: alias list broken"); return false; // shut up compiler } void Cmd_UnAlias_f (void) { char *s; if (Cmd_Argc() != 2) { Con_Printf ("unalias : erase an existing alias\n"); return; } s = Cmd_Argv(1); if (strlen(s) >= MAX_ALIAS_NAME) { Con_Printf ("Alias name is too long\n"); return; } if (!Cmd_DeleteAlias(s)) Con_Printf ("Unknown alias \"%s\"\n", s); } // remove all aliases void Cmd_UnAliasAll_f (void) { cmd_alias_t *a, *next; int i; for (a=cmd_alias ; a ; a=next) { next = a->next; Q_free (a->value); Q_free (a); } cmd_alias = NULL; // clear hash for (i=0 ; i<32 ; i++) { cmd_alias_hash[i] = NULL; } } /* ============================================================================= COMMAND EXECUTION ============================================================================= */ static char *cmd_null_string = ""; static cmd_function_t *cmd_hash_array[32]; static cmd_function_t *cmd_functions; // possible commands to execute static tokenizecontext_t cmd_tokenizecontext; int Cmd_ArgcEx(tokenizecontext_t* ctx) { return ctx->cmd_argc; } char* Cmd_ArgvEx(tokenizecontext_t* ctx, int arg) { if (arg >= ctx->cmd_argc || arg < 0) return cmd_null_string; return ctx->cmd_argv[arg]; } // Returns a single string containing argv(1) to argv(argc() - 1) char* Cmd_ArgsEx(tokenizecontext_t* ctx) { return ctx->cmd_args; } // Returns a single string containing argv(start) to argv(argc() - 1) // Unlike Cmd_Args, shrinks spaces between argvs char* Cmd_MakeArgsEx(tokenizecontext_t* ctx, int start) { int i, c; ctx->text[0] = 0; c = Cmd_ArgcEx(ctx); for (i = start; i < c; i++) { if (i > start) strlcat(ctx->text, " ", sizeof(ctx->text) - strlen(ctx->text)); strlcat(ctx->text, Cmd_ArgvEx(ctx, i), sizeof(ctx->text) - strlen(ctx->text)); } return ctx->text; } // Parses the given string into command line tokens. void Cmd_TokenizeStringEx(tokenizecontext_t* ctx, const char* text) { int idx = 0, token_len; memset(ctx, 0, sizeof(*ctx)); while (1) { // skip whitespace while (*text == ' ' || *text == '\t' || *text == '\r') text++; // a newline separates commands in the buffer if (*text == '\n') return; if (!*text) return; if (ctx->cmd_argc == 1) strlcpy(ctx->cmd_args, text, sizeof(ctx->cmd_args)); text = COM_Parse(text); if (!text) return; if (ctx->cmd_argc >= MAX_ARGS) return; token_len = strlen(com_token); // ouch ouch, no more space if (idx + token_len + 1 > sizeof(ctx->argv_buf)) return; ctx->cmd_argv[ctx->cmd_argc] = ctx->argv_buf + idx; strcpy(ctx->cmd_argv[ctx->cmd_argc], com_token); ctx->cmd_argc++; idx += token_len + 1; } } /* ============ Cmd_Argc ============ */ int Cmd_Argc (void) { return Cmd_ArgcEx(&cmd_tokenizecontext); } /* ============ Cmd_Argv ============ */ char *Cmd_Argv (int arg) { return Cmd_ArgvEx(&cmd_tokenizecontext, arg); } /* ============ Cmd_Args Returns a single string containing argv(1) to argv(argc()-1) ============ */ char *Cmd_Args (void) { return Cmd_ArgsEx(&cmd_tokenizecontext); } /* ============ Cmd_TokenizeString Parses the given string into command line tokens. ============ */ void Cmd_TokenizeString (const char *text) { Cmd_TokenizeStringEx(&cmd_tokenizecontext, text); } /* ============ Cmd_AddCommand ============ */ void Cmd_AddCommand (const char *cmd_name, xcommand_t function) { cmd_function_t *cmd; int key; if (host_initialized) // because hunk allocation would get stomped Sys_Error ("Cmd_AddCommand after host_initialized"); // fail if the command is a variable name if (Cvar_Find(cmd_name)) { Con_Printf ("Cmd_AddCommand: %s already defined as a var\n", cmd_name); return; } key = Com_HashKey (cmd_name); // fail if the command already exists for (cmd=cmd_hash_array[key] ; cmd ; cmd=cmd->hash_next) { if (!strcasecmp (cmd_name, cmd->name)) { Con_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name); return; } } cmd = (cmd_function_t *) Hunk_Alloc (sizeof(cmd_function_t)); cmd->name = cmd_name; cmd->function = function; cmd->next = cmd_functions; cmd_functions = cmd; cmd->hash_next = cmd_hash_array[key]; cmd_hash_array[key] = cmd; } /* ============ Cmd_Exists ============ */ qbool Cmd_Exists (const char *cmd_name) { int key; cmd_function_t *cmd; key = Com_HashKey (cmd_name); for (cmd=cmd_hash_array[key] ; cmd ; cmd=cmd->hash_next) { if (!strcasecmp (cmd_name, cmd->name)) return true; } return false; } static int Cmd_CommandCompare (const void *p1, const void *p2) { return strcmp ((*((cmd_function_t **) p1))->name, (*((cmd_function_t **) p2))->name); } static void Cmd_CmdList_f(void) { cmd_function_t **sorted_cmds = NULL; cmd_function_t *cmd; const char *pattern; int i = 0; int num_cmds = 0; int pattern_matched = 0; pattern = (Cmd_Argc() > 1) ? Cmd_Argv(1) : NULL; for (cmd = cmd_functions; cmd; cmd = cmd->next) { num_cmds++; } if (num_cmds > 0) { sorted_cmds = malloc(num_cmds * sizeof(cmd_function_t*)); if (!sorted_cmds) { Sys_Error("Failed to allocate memory"); } } else { Con_Printf("No commands found\n"); return; } for (cmd = cmd_functions; cmd; cmd= cmd->next) { sorted_cmds[i++] = cmd; } qsort(sorted_cmds, num_cmds, sizeof(cmd_function_t *), Cmd_CommandCompare); Con_Printf ("List of commands:\n"); for (i = 0; i < num_cmds; i++) { cmd = sorted_cmds[i]; if (pattern && !Q_glob_match(pattern, cmd->name)) { continue; } Con_Printf ("%s\n", cmd->name); pattern_matched++; } Con_Printf ("------------\n%d/%d %scommands\n", pattern_matched, num_cmds, (pattern) ? "matching " : ""); free(sorted_cmds); } /* ================ Cmd_ExpandString Expands all $cvar expressions to cvar values Note: dest must point to a 1024 byte buffer ================ */ void Cmd_ExpandString (const char *data, char *dest) { unsigned int c; char buf[255]; int i, len; cvar_t *var, *bestvar; int quotes = 0; char *str; int name_length = 0; len = 0; while ( (c = *data) != 0) { if (c == '"') quotes++; if (c == '$' && !(quotes&1)) { data++; // Copy the text after '$' to a temp buffer i = 0; buf[0] = 0; bestvar = NULL; while ((c = *data) > 32) { if (c == '$') break; data++; buf[i++] = c; buf[i] = 0; if ( (var = Cvar_Find(buf)) != NULL ) bestvar = var; if (i >= (int)sizeof(buf)-1) break; // there no more space in buf } if (bestvar) { str = bestvar->string; name_length = strlen(bestvar->name); } else str = NULL; if (str) { // check buffer size if (len + strlen(str) >= 1024-1) break; strlcpy(dest + len, str, 1024 - len); len += strlen(str); i = name_length; while (buf[i]) dest[len++] = buf[i++]; } else { // no matching cvar or macro dest[len++] = '$'; if (len + strlen(buf) >= 1024-1) break; strlcpy (dest + len, buf, 1024 - len); len += strlen(buf); } } else { dest[len] = c; data++; len++; if (len >= 1024-1) break; } }; dest[len] = 0; } /* ============ Cmd_ExecuteString A complete command line has been parsed, so try to execute it FIXME: lookupnoadd the token to speed search? ============ */ extern qbool PR_ConsoleCmd(void); void Cmd_ExecuteString (const char *text) { cmd_function_t *cmd; cmd_alias_t *a; int key; static char buf[1024]; Cmd_ExpandString (text, buf); Cmd_TokenizeString (buf); // execute the command line if (!Cmd_Argc()) return; // no tokens key = Com_HashKey(Cmd_Argv(0)); // check functions for (cmd=cmd_hash_array[key] ; cmd ; cmd=cmd->hash_next) { if (!strcasecmp(Cmd_Argv(0), cmd->name)) { if (cmd->function) cmd->function (); return; } } // check cvars if (Cvar_Command()) return; // check alias for (a=cmd_alias_hash[key] ; a ; a=a->hash_next) { if (!strcasecmp(Cmd_Argv(0), a->name)) { Cbuf_InsertText ("\n"); Cbuf_InsertText (a->value); return; } } if (PR_ConsoleCmd()) return; Con_Printf ("Unknown command \"%s\"\n", Cmd_Argv(0)); } static qbool is_numeric (const char *c) { return (*c >= '0' && *c <= '9') || ((*c == '-' || *c == '+') && (c[1] == '.' || (c[1]>='0' && c[1]<='9'))) || (*c == '.' && (c[1]>='0' && c[1]<='9')); } /* ================ Cmd_If_f ================ */ void Cmd_If_f (void) { int i, c; char *op; qbool result; char buf[256]; c = Cmd_Argc (); if (c < 5) { Con_Printf ("usage: if [else ]\n"); return; } op = Cmd_Argv (2); if (!strcmp(op, "==") || !strcmp(op, "=") || !strcmp(op, "!=") || !strcmp(op, "<>")) { if (is_numeric(Cmd_Argv(1)) && is_numeric(Cmd_Argv(3))) result = Q_atof(Cmd_Argv(1)) == Q_atof(Cmd_Argv(3)); else result = !strcmp(Cmd_Argv(1), Cmd_Argv(3)); if (op[0] != '=') result = !result; } else if (!strcmp(op, ">")) result = Q_atof(Cmd_Argv(1)) > Q_atof(Cmd_Argv(3)); else if (!strcmp(op, "<")) result = Q_atof(Cmd_Argv(1)) < Q_atof(Cmd_Argv(3)); else if (!strcmp(op, ">=")) result = Q_atof(Cmd_Argv(1)) >= Q_atof(Cmd_Argv(3)); else if (!strcmp(op, "<=")) result = Q_atof(Cmd_Argv(1)) <= Q_atof(Cmd_Argv(3)); else if (!strcmp(op, "isin")) result = strstr(Cmd_Argv(3), Cmd_Argv(1)) != NULL; else if (!strcmp(op, "!isin")) result = strstr(Cmd_Argv(3), Cmd_Argv(1)) == NULL; else { Con_Printf ("unknown operator: %s\n", op); Con_Printf ("valid operators are ==, =, !=, <>, >, <, >=, <=, isin, !isin\n"); return; } buf[0] = '\0'; if (result) { for (i=4; i < c ; i++) { if ((i == 4) && !strcasecmp(Cmd_Argv(i), "then")) continue; if (!strcasecmp(Cmd_Argv(i), "else")) break; if (buf[0]) strlcat (buf, " ", sizeof(buf)); strlcat (buf, Cmd_Argv(i), sizeof(buf)); } } else { for (i=4; i < c ; i++) { if (!strcasecmp(Cmd_Argv(i), "else")) break; } if (i == c) return; for (i++ ; i < c ; i++) { if (buf[0]) strlcat (buf, " ", sizeof(buf)); strlcat (buf, Cmd_Argv(i), sizeof(buf)); } } Cbuf_InsertText (buf); } /* ============ Cmd_Init ============ */ void Cmd_Init (void) { // // register our commands // Cmd_AddCommand ("exec",Cmd_Exec_f); Cmd_AddCommand ("echo",Cmd_Echo_f); Cmd_AddCommand ("alias",Cmd_Alias_f); Cmd_AddCommand ("wait", Cmd_Wait_f); Cmd_AddCommand ("cmdlist", Cmd_CmdList_f); Cmd_AddCommand ("unaliasall", Cmd_UnAliasAll_f); Cmd_AddCommand ("unalias", Cmd_UnAlias_f); Cmd_AddCommand ("if", Cmd_If_f); } mvdsv-0.35/src/cmd.h000066400000000000000000000124771427146041000143150ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // cmd.h -- Command buffer and command execution //=========================================================================== /* Any number of commands can be added in a frame, from several different sources. Most commands come from either keybindings or console line input, but remote servers can also send across commands and entire text files can be execed. The + command line options are also added to the command buffer. The game starts with a Cbuf_AddText ("exec quake.rc\n"); Cbuf_Execute (); */ #ifndef __CMD_H__ #define __CMD_H__ #define MAXCMDBUF 65536 typedef struct cbuf_s { char text_buf[MAXCMDBUF]; int text_start; int text_end; qbool wait; } cbuf_t; extern cbuf_t cbuf_main; extern cbuf_t *cbuf_current; void Cbuf_AddTextEx (cbuf_t *cbuf, const char *text); void Cbuf_InsertTextEx (cbuf_t *cbuf, const char *text); void Cbuf_ExecuteEx (cbuf_t *cbuf); void Cbuf_Init (void); // allocates an initial text buffer that will grow as needed void Cbuf_AddText (const char *text); // as new commands are generated from the console or keybindings, // the text is added to the end of the command buffer. void Cbuf_InsertText (const char *text); // when a command wants to issue other commands immediately, the text is // inserted at the beginning of the buffer, before any remaining unexecuted // commands. void Cbuf_Execute (void); // Pulls off \n terminated lines of text from the command buffer and sends // them through Cmd_ExecuteString. Stops when the buffer is empty. // Normally called once per frame, but may be explicitly invoked. // Do not call inside a command function! //=========================================================================== /* Command execution takes a null terminated string, breaks it into tokens, then searches for a command or variable that matches the first token. */ typedef void (*xcommand_t) (void); typedef struct cmd_function_s { struct cmd_function_s *hash_next; struct cmd_function_s *next; const char *name; xcommand_t function; } cmd_function_t; void Cmd_Init (void); void Cmd_AddCommand (const char *cmd_name, xcommand_t function); // called by the init functions of other parts of the program to // register commands and functions to call for them. // The cmd_name is referenced later, so it should not be in temp memory // if function is NULL, the command will be forwarded to the server // as a clc_stringcmd instead of executed locally qbool Cmd_Exists (const char *cmd_name); // used by the cvar code to check for cvar / command name overlap #define MAX_ARGS 80 typedef struct tokenizecontext_s { int cmd_argc; // arguments count char* cmd_argv[MAX_ARGS]; // links to argv_buf[] // FIXME: MAX_COM_TOKEN not defined here, need redesign headers or something char argv_buf[/*MAX_COM_TOKEN*/ 1024]; // here we store data for *cmd_argv[] char cmd_args[/*MAX_COM_TOKEN*/ 1024 * 2]; // here we store original of what we parse, from argv(1) to argv(argc() - 1) char text[/*MAX_COM_TOKEN*/ 1024]; // this is used/overwrite each time we using Cmd_MakeArgs() } tokenizecontext_t; int Cmd_ArgcEx(tokenizecontext_t* ctx); char* Cmd_ArgvEx(tokenizecontext_t* ctx, int arg); //Returns a single string containing argv(1) to argv(argc() - 1) char* Cmd_ArgsEx(tokenizecontext_t* ctx); //Returns a single string containing argv(start) to argv(argc() - 1) //Unlike Cmd_Args, shrinks spaces between argvs char* Cmd_MakeArgsEx(tokenizecontext_t* ctx, int start); //Parses the given string into command line tokens. void Cmd_TokenizeStringEx(tokenizecontext_t* ctx, const char* text); int Cmd_Argc (void); char *Cmd_Argv (int arg); char *Cmd_Args (void); // The functions that execute commands get their parameters with these // functions. Cmd_Argv () will return an empty string, not a NULL // if arg > argc, so string operations are always safe. void Cmd_ExpandString (const char *data, char *dest); // Expands all $cvar or $macro expressions. // dest should point to a 1024-byte buffer void Cmd_TokenizeString (const char *text); // Takes a null terminated string. Does not need to be /n terminated. // breaks the string up into arg tokens. void Cmd_ExecuteString (const char *text); // Parses a single line of text into arguments and tries to execute it // as if it was typed at the console void Cmd_StuffCmds_f (void); //=========================================================================== #define MAX_ALIAS_NAME 32 typedef struct cmd_alias_s { struct cmd_alias_s *hash_next; struct cmd_alias_s *next; char name[MAX_ALIAS_NAME]; char *value; } cmd_alias_t; qbool Cmd_DeleteAlias (const char *name); // return true if successful #define MAX_ARGS 80 #endif /* !__CMD_H__ */ mvdsv-0.35/src/cmodel.c000066400000000000000000001071711427146041000150040ustar00rootroot00000000000000/* 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. */ // cmodel.c - collision model. #ifdef SERVERONLY #include "qwsvdef.h" #else #include "common.h" #include "cvar.h" #endif typedef struct cnode_s { // common with leaf int contents; // 0, to differentiate from leafs struct cnode_s *parent; // node specific mplane_t *plane; struct cnode_s *children[2]; } cnode_t; typedef struct cleaf_s { // common with node int contents; // a negative contents number struct cnode_s *parent; // leaf specific byte ambient_sound_level[NUM_AMBIENTS]; } cleaf_t; static char loadname[32]; // for hunk tags static char map_name[MAX_QPATH]; static unsigned int map_checksum, map_checksum2; static int numcmodels; static cmodel_t map_cmodels[MAX_MAP_MODELS]; static mplane_t *map_planes; static int numplanes; static cnode_t *map_nodes; static int numnodes; static mclipnode_t *map_clipnodes; static int numclipnodes; static cleaf_t *map_leafs; static int numleafs; static int visleafs; static byte map_novis[MAX_MAP_LEAFS/8]; static byte *map_pvs; // fully expanded and decompressed static byte *map_phs; // only valid if we are the server static int map_vis_rowbytes; // for both pvs and phs static int map_vis_rowlongs; // map_vis_rowbytes / 4 static char *map_entitystring; static qbool map_halflife; static mphysicsnormal_t* map_physicsnormals; // must be same number as clipnodes to save reallocations in worst case scenario static byte *cmod_base; // for CM_Load* functions // lumps immediately follow: typedef struct { char lumpname[24]; int fileofs; int filelen; } bspx_lump_t; void* Mod_BSPX_FindLump(bspx_header_t* bspx_header, char* lumpname, int* plumpsize, byte* mod_base); bspx_header_t* Mod_LoadBSPX(int filesize, byte* mod_base); /* =============================================================================== HULL BOXES =============================================================================== */ static hull_t box_hull; static mclipnode_t box_clipnodes[6]; static mplane_t box_planes[6]; /* ** CM_InitBoxHull ** ** Set up the planes and clipnodes so that the six floats of a bounding box ** can just be stored out and get a proper hull_t structure. */ static void CM_InitBoxHull(void) { int side, i; box_hull.clipnodes = box_clipnodes; box_hull.planes = box_planes; box_hull.firstclipnode = 0; box_hull.lastclipnode = 5; for (i = 0; i < 6; i++) { box_clipnodes[i].planenum = i; side = i & 1; box_clipnodes[i].children[side] = CONTENTS_EMPTY; box_clipnodes[i].children[side ^ 1] = (i != 5) ? (i + 1) : CONTENTS_SOLID; box_planes[i].type = i >> 1; box_planes[i].normal[i >> 1] = 1; } } /* ** CM_HullForBox ** ** To keep everything totally uniform, bounding boxes are turned into small ** BSP trees instead of being compared directly. */ hull_t *CM_HullForBox (vec3_t mins, vec3_t maxs) { box_planes[0].dist = maxs[0]; box_planes[1].dist = mins[0]; box_planes[2].dist = maxs[1]; box_planes[3].dist = mins[1]; box_planes[4].dist = maxs[2]; box_planes[5].dist = mins[2]; return &box_hull; } int CM_CachedHullPointContents(hull_t* hull, int num, vec3_t p, float* min_dist) { mclipnode_t* node; mplane_t* plane; float d; *min_dist = 999; while (num >= 0) { if (num < hull->firstclipnode || num > hull->lastclipnode) { if (map_halflife && num == hull->lastclipnode + 1) { return CONTENTS_EMPTY; } Sys_Error("CM_HullPointContents: bad node number"); } node = hull->clipnodes + num; plane = hull->planes + node->planenum; d = PlaneDiff(p, plane); if (d < 0) { *min_dist = min(*min_dist, -d); num = node->children[1]; } else { *min_dist = min(*min_dist, d); num = node->children[0]; } } return num; } int CM_HullPointContents(hull_t *hull, int num, vec3_t p) { mclipnode_t *node; mplane_t *plane; float d; while (num >= 0) { if (num < hull->firstclipnode || num > hull->lastclipnode) { if (map_halflife && num == hull->lastclipnode + 1) { return CONTENTS_EMPTY; } Sys_Error("CM_HullPointContents: bad node number"); } node = hull->clipnodes + num; plane = hull->planes + node->planenum; d = PlaneDiff(p, plane); num = (d < 0) ? node->children[1] : node->children[0]; } return num; } /* =============================================================================== LINE TESTING IN HULLS =============================================================================== */ // 1/32 epsilon to keep floating point happy #define DIST_EPSILON 0.03125 enum { TR_EMPTY, TR_SOLID, TR_BLOCKED }; typedef struct { hull_t *hull; trace_t trace; int leafcount; } hulltrace_local_t; //==================== int RecursiveHullTrace (hulltrace_local_t *htl, int num, float p1f, float p2f, const vec3_t p1, const vec3_t p2) { mplane_t *plane; float t1, t2; mclipnode_t *node; int i; int nearside; float frac, midf; vec3_t mid; int check, oldcheck; hull_t *hull = htl->hull; trace_t *trace = &htl->trace; start: if (num < 0) { // this is a leaf node htl->leafcount++; if (num == CONTENTS_SOLID) { if (htl->leafcount == 1) trace->startsolid = true; return TR_SOLID; } else { if (num == CONTENTS_EMPTY) trace->inopen = true; else trace->inwater = true; return TR_EMPTY; } } // FIXME, check at load time if (num < hull->firstclipnode || num > hull->lastclipnode) { if (map_halflife && num == hull->lastclipnode + 1) return TR_EMPTY; Sys_Error ("RecursiveHullTrace: bad node number"); } node = hull->clipnodes + num; // // find the point distances // plane = hull->planes + node->planenum; if (plane->type < 3) { t1 = p1[plane->type] - plane->dist; t2 = p2[plane->type] - plane->dist; } else { t1 = DotProduct (plane->normal, p1) - plane->dist; t2 = DotProduct (plane->normal, p2) - plane->dist; } // see which sides we need to consider if (t1 >= 0 && t2 >= 0) { num = node->children[0]; // go down the front side goto start; } if (t1 < 0 && t2 < 0) { num = node->children[1]; // go down the back side goto start; } // find the intersection point frac = t1 / (t1 - t2); frac = bound (0, frac, 1); midf = p1f + (p2f - p1f)*frac; for (i = 0; i < 3; i++) mid[i] = p1[i] + frac*(p2[i] - p1[i]); // move up to the node nearside = (t1 < t2) ? 1 : 0; check = RecursiveHullTrace (htl, node->children[nearside], p1f, midf, p1, mid); if (check == TR_BLOCKED) return check; // if we started in solid, allow us to move out to an empty area if (check == TR_SOLID && (trace->inopen || trace->inwater)) return check; oldcheck = check; // go past the node check = RecursiveHullTrace (htl, node->children[1 - nearside], midf, p2f, mid, p2); if (check == TR_EMPTY || check == TR_BLOCKED) return check; if (oldcheck != TR_EMPTY) return check; // still in solid // near side is empty, far side is solid // this is the impact point if (!nearside) { VectorCopy (plane->normal, trace->plane.normal); trace->plane.dist = plane->dist; } else { VectorNegate (plane->normal, trace->plane.normal); trace->plane.dist = -plane->dist; } // put the final point DIST_EPSILON pixels on the near side if (t1 < t2) frac = (t1 + DIST_EPSILON) / (t1 - t2); else frac = (t1 - DIST_EPSILON) / (t1 - t2); frac = bound (0, frac, 1); midf = p1f + (p2f - p1f)*frac; for (i = 0; i < 3; i++) mid[i] = p1[i] + frac*(p2[i] - p1[i]); trace->fraction = midf; VectorCopy (mid, trace->endpos); trace->physicsnormal = (!nearside ? num + 1 : -(num + 1)); return TR_BLOCKED; } trace_t CM_HullTrace (hull_t *hull, vec3_t start, vec3_t end) { int check; // this structure is passed as a pointer to RecursiveHullTrace // so as not to use much stack but still be thread safe hulltrace_local_t htl; htl.hull = hull; htl.leafcount = 0; // fill in a default trace memset (&htl.trace, 0, sizeof(htl.trace)); htl.trace.fraction = 1; htl.trace.startsolid = false; VectorCopy (end, htl.trace.endpos); check = RecursiveHullTrace (&htl, hull->firstclipnode, 0, 1, start, end); if (check == TR_SOLID) { htl.trace.startsolid = htl.trace.allsolid = true; // it would be logical to set fraction to 0, but original id code // would leave it at 1. We emulate that just in case. // (FIXME: is it just QW, or NQ as well?) //htl.trace.fraction = 0; VectorCopy (start, htl.trace.endpos); } return htl.trace; } //=========================================================================== int CM_NumInlineModels (void) { return numcmodels; } char *CM_EntityString (void) { return map_entitystring; } int CM_Leafnum (const cleaf_t *leaf) { assert (leaf); return leaf - map_leafs; } int CM_LeafAmbientLevel (const cleaf_t *leaf, int ambient_channel) { assert ((unsigned)ambient_channel <= NUM_AMBIENTS); assert (leaf); return leaf->ambient_sound_level[ambient_channel]; } // always returns a valid cleaf_t pointer cleaf_t *CM_PointInLeaf (const vec3_t p) { float d; cnode_t *node; mplane_t *plane; if (!numnodes) Host_Error ("CM_PointInLeaf: numnodes == 0"); node = map_nodes; while (1) { if (node->contents < 0) { return (cleaf_t *)node; } plane = node->plane; d = DotProduct(p, plane->normal) - plane->dist; node = (d > 0) ? node->children[0] : node->children[1]; } return NULL; // never reached } byte *CM_LeafPVS (const cleaf_t *leaf) { if (leaf == map_leafs) return map_novis; return map_pvs + (leaf - 1 - map_leafs) * map_vis_rowbytes; } /* ** only the server may call this */ byte *CM_LeafPHS (const cleaf_t *leaf) { if (leaf == map_leafs) return map_novis; if (!map_phs) { return NULL; } return map_phs + (leaf - 1 - map_leafs) * map_vis_rowbytes; } /* ============================================================================= The PVS must include a small area around the client to allow head bobbing or other small motion on the client side. Otherwise, a bob might cause an entity that should be visible to not show up, especially when the bob crosses a waterline. ============================================================================= */ static int fatbytes; static byte fatpvs[MAX_MAP_LEAFS/8]; static vec3_t fatpvs_org; static void AddToFatPVS_r (cnode_t *node) { int i; float d; byte *pvs; mplane_t *plane; while (1) { // if this is a leaf, accumulate the pvs bits if (node->contents < 0) { if (node->contents != CONTENTS_SOLID) { pvs = CM_LeafPVS ( (cleaf_t *)node); for (i=0 ; iplane; d = DotProduct (fatpvs_org, plane->normal) - plane->dist; if (d > 8) node = node->children[0]; else if (d < -8) node = node->children[1]; else { // go down both AddToFatPVS_r (node->children[0]); node = node->children[1]; } } } /* ============= CM_FatPVS Calculates a PVS that is the inclusive or of all leafs within 8 pixels of the given point. ============= */ byte *CM_FatPVS (vec3_t org) { VectorCopy (org, fatpvs_org); fatbytes = (visleafs+31)>>3; memset (fatpvs, 0, fatbytes); AddToFatPVS_r (map_nodes); return fatpvs; } /* ** Recursively build a list of leafs touched by a rectangular volume */ static int leafs_count; static int leafs_maxcount; static qbool leafs_overflow; static int *leafs_list; static int leafs_topnode; static vec3_t leafs_mins, leafs_maxs; static void FindTouchedLeafs_r(const cnode_t *node) { mplane_t *splitplane; cleaf_t *leaf; int sides; while (1) { if (node->contents == CONTENTS_SOLID) { return; } // the node is a leaf if (node->contents < 0) { if (leafs_count == leafs_maxcount) { leafs_overflow = true; return; } leaf = (cleaf_t *)node; leafs_list[leafs_count++] = leaf - map_leafs; return; } // NODE_MIXED splitplane = node->plane; sides = BOX_ON_PLANE_SIDE(leafs_mins, leafs_maxs, splitplane); // recurse down the contacted sides if (sides == 1) { node = node->children[0]; } else if (sides == 2) { node = node->children[1]; } else { if (leafs_topnode == -1) { leafs_topnode = node - map_nodes; } FindTouchedLeafs_r(node->children[0]); node = node->children[1]; } } } /* ** Returns an array filled with leaf nums */ int CM_FindTouchedLeafs (const vec3_t mins, const vec3_t maxs, int leafs[], int maxleafs, int headnode, int *topnode) { leafs_count = 0; leafs_maxcount = maxleafs; leafs_list = leafs; leafs_topnode = -1; leafs_overflow = false; VectorCopy (mins, leafs_mins); VectorCopy (maxs, leafs_maxs); FindTouchedLeafs_r (&map_nodes[headnode]); if (leafs_overflow) { leafs_count = -1; } if (topnode) *topnode = leafs_topnode; return leafs_count; } /* =============================================================================== BRUSHMODEL LOADING =============================================================================== */ static void CM_LoadEntities (lump_t *l) { if (!l->filelen) { map_entitystring = NULL; return; } map_entitystring = (char *) Hunk_AllocName (l->filelen, loadname); memcpy (map_entitystring, cmod_base + l->fileofs, l->filelen); } /* ================= CM_LoadSubmodels ================= */ static void CM_LoadSubmodels (lump_t *l) { dmodel_t *in; cmodel_t *out; int i, j, count; in = (dmodel_t *)(cmod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error ("CM_LoadMap: funny lump size"); count = l->filelen / sizeof(*in); if (count < 1) Host_Error ("Map with no models"); if (count > MAX_MAP_MODELS) Host_Error ("Map has too many models (%d vs %d)", count, MAX_MAP_MODELS); out = map_cmodels; numcmodels = count; visleafs = LittleLong (in[0].visleafs); for (i = 0; i < count; i++, in++, out++) { for (j = 0; j < 3; j++) { // spread the mins / maxs by a pixel out->mins[j] = LittleFloat (in->mins[j]) - 1; out->maxs[j] = LittleFloat (in->maxs[j]) + 1; out->origin[j] = LittleFloat (in->origin[j]); } for (j = 0; j < MAX_MAP_HULLS; j++) { out->hulls[j].planes = map_planes; out->hulls[j].clipnodes = map_clipnodes; out->hulls[j].firstclipnode = LittleLong (in->headnode[j]); out->hulls[j].lastclipnode = numclipnodes - 1; } VectorClear (out->hulls[0].clip_mins); VectorClear (out->hulls[0].clip_maxs); if (map_halflife) { VectorSet (out->hulls[1].clip_mins, -16, -16, -36); VectorSet (out->hulls[1].clip_maxs, 16, 16, 36); VectorSet (out->hulls[2].clip_mins, -32, -32, -36); VectorSet (out->hulls[2].clip_maxs, 32, 32, 36); // not really used VectorSet (out->hulls[3].clip_mins, -16, -16, -18); VectorSet (out->hulls[3].clip_maxs, 16, 16, 18); } else { VectorSet (out->hulls[1].clip_mins, -16, -16, -24); VectorSet (out->hulls[1].clip_maxs, 16, 16, 32); VectorSet (out->hulls[2].clip_mins, -32, -32, -24); VectorSet (out->hulls[2].clip_maxs, 32, 32, 64); } } } /* ================= CM_SetParent ================= */ static void CM_SetParent (cnode_t *node, cnode_t *parent) { node->parent = parent; if (node->contents < 0) return; CM_SetParent (node->children[0], node); CM_SetParent (node->children[1], node); } /* ================= CM_LoadNodes ================= */ static void CM_LoadNodes (lump_t *l) { int i, j, count, p; dnode_t *in; cnode_t *out; in = (dnode_t *)(cmod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error ("CM_LoadMap: funny lump size"); count = l->filelen / sizeof(*in); out = (cnode_t *) Hunk_AllocName ( count*sizeof(*out), loadname); map_nodes = out; numnodes = count; for (i = 0; i < count; i++, in++, out++) { p = LittleLong(in->planenum); out->plane = map_planes + p; for (j=0 ; j<2 ; j++) { p = LittleShort (in->children[j]); out->children[j] = (p >= 0) ? (map_nodes + p) : ((cnode_t *)(map_leafs + (-1 - p))); } } CM_SetParent (map_nodes, NULL); // sets nodes and leafs } static void CM_LoadNodes29a(lump_t *l) { int i, j, count, p; dnode29a_t *in; cnode_t *out; in = (dnode29a_t *)(cmod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("CM_LoadMap: funny lump size"); count = l->filelen / sizeof(*in); out = (cnode_t *)Hunk_AllocName(count * sizeof(*out), loadname); map_nodes = out; numnodes = count; for (i = 0; i < count; i++, in++, out++) { p = LittleLong(in->planenum); out->plane = map_planes + p; for (j = 0; j < 2; j++) { p = LittleLong(in->children[j]); out->children[j] = (p >= 0) ? (map_nodes + p) : ((cnode_t *)(map_leafs + (-1 - p))); } } CM_SetParent(map_nodes, NULL); // sets nodes and leafs } static void CM_LoadNodesBSP2(lump_t *l) { int i, j, count, p; dnode_bsp2_t *in; cnode_t *out; in = (dnode_bsp2_t *)(cmod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("CM_LoadMap: funny lump size"); count = l->filelen / sizeof(*in); out = (cnode_t *)Hunk_AllocName(count * sizeof(*out), loadname); map_nodes = out; numnodes = count; for (i = 0; i < count; i++, in++, out++) { p = LittleLong(in->planenum); out->plane = map_planes + p; for (j = 0; j < 2; j++) { p = LittleLong(in->children[j]); out->children[j] = (p >= 0) ? (map_nodes + p) : ((cnode_t *)(map_leafs + (-1 - p))); } } CM_SetParent(map_nodes, NULL); // sets nodes and leafs } /* ** CM_LoadLeafs */ static void CM_LoadLeafs (lump_t *l) { dleaf_t *in; cleaf_t *out; int i, j, count, p; in = (dleaf_t *)(cmod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error ("CM_LoadMap: funny lump size"); count = l->filelen / sizeof(*in); out = (cleaf_t *) Hunk_AllocName ( count*sizeof(*out), loadname); map_leafs = out; numleafs = count; for (i = 0; i < count; i++, in++, out++) { p = LittleLong(in->contents); out->contents = p; for (j = 0; j < 4; j++) out->ambient_sound_level[j] = in->ambient_level[j]; } } static void CM_LoadLeafs29a (lump_t *l) { dleaf29a_t *in; cleaf_t *out; int i, j, count, p; in = (dleaf29a_t *)(cmod_base + l->fileofs); if (l->filelen % sizeof(*in)) { Host_Error("CM_LoadMap: funny lump size"); } count = l->filelen / sizeof(*in); out = Hunk_AllocName ( count*sizeof(*out), loadname); map_leafs = out; numleafs = count; for (i = 0; i < count; i++, in++, out++) { p = LittleLong(in->contents); out->contents = p; for (j = 0; j < 4; j++) out->ambient_sound_level[j] = in->ambient_level[j]; } } static void CM_LoadLeafsBSP2 (lump_t *l) { dleaf_bsp2_t *in; cleaf_t *out; int i, j, count, p; in = (dleaf_bsp2_t *)(cmod_base + l->fileofs); if (l->filelen % sizeof(*in)) { Host_Error("CM_LoadMap: funny lump size"); } count = l->filelen / sizeof(*in); out = Hunk_AllocName ( count*sizeof(*out), loadname); map_leafs = out; numleafs = count; for (i = 0; i < count; i++, in++, out++) { p = LittleLong(in->contents); out->contents = p; for (j = 0; j < 4; j++) out->ambient_sound_level[j] = in->ambient_level[j]; } } /* ================= CM_LoadClipnodes ================= */ static void CM_LoadClipnodes(lump_t *l) { dclipnode_t *in; mclipnode_t *out; int i, count; in = (dclipnode_t *)(cmod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("CM_LoadMap: funny lump size"); count = l->filelen / sizeof(*in); out = (mclipnode_t *)Hunk_AllocName(count * sizeof(*out), loadname); map_clipnodes = out; numclipnodes = count; for (i = 0; i < count; i++, out++, in++) { out->planenum = LittleLong(in->planenum); out->children[0] = LittleShort(in->children[0]); out->children[1] = LittleShort(in->children[1]); } } static void CM_LoadClipnodesBSP2(lump_t *l) { dclipnode29a_t *in; mclipnode_t *out; int i, count; in = (void *)(cmod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("CM_LoadMap: funny lump size"); count = l->filelen / sizeof(*in); out = Hunk_AllocName(count * sizeof(*out), loadname); map_clipnodes = out; numclipnodes = count; for (i = 0; i < count; i++, out++, in++) { out->planenum = LittleLong(in->planenum); out->children[0] = LittleLong(in->children[0]); out->children[1] = LittleLong(in->children[1]); } } static qbool CM_LoadPhysicsNormalsData(byte* data, int datalength) { mphysicsnormal_t* in = (mphysicsnormal_t*)(data + 8); int i; if (datalength != 8 + sizeof(map_physicsnormals[0]) * numclipnodes) { return false; } #ifndef CLIENTONLY { float* cvars = (float*)data; extern cvar_t pm_rampjump; Cvar_SetValue(&pm_rampjump, LittleFloat(cvars[0])); } #endif // Meag: previously the maximum speed was set here but I don't think it should be map-specific (?) for (i = 0; i < numclipnodes; ++i) { map_physicsnormals[i].normal[0] = LittleFloat(in[i].normal[0]); map_physicsnormals[i].normal[1] = LittleFloat(in[i].normal[1]); map_physicsnormals[i].normal[2] = LittleFloat(in[i].normal[2]); map_physicsnormals[i].flags = PHYSICSNORMAL_SET | (int)LittleLong(in[i].flags); } return true; } static void CM_LoadPhysicsNormals(int filelen) { // Same logic as .lit file support: load from bspx, allow over-ride with .qpn files // As client-side movement prediction will be incorrect if physics normals don't // match, I strongly recommend the .bspx solution bspx_header_t* bspx; int i; qbool bspx_loaded = false; // Allocate memory, all maps default to rampjump off #ifndef CLIENTONLY { extern cvar_t pm_rampjump; Cvar_SetValue(&pm_rampjump, 0); } #endif map_physicsnormals = Hunk_AllocName(numclipnodes * sizeof(map_physicsnormals[0]), loadname); // Try and load from BSPX lump bspx = Mod_LoadBSPX(filelen, cmod_base); if (bspx) { int lumpsize = 0; void* data = Mod_BSPX_FindLump(bspx, "MVDSV_PHYSICSNORMALS", &lumpsize, cmod_base); bspx_loaded = CM_LoadPhysicsNormalsData(data, lumpsize); if (bspx_loaded) { Con_Printf("Loading BSPX physics normals\n"); } } // If not supplied, initialise with default values from clipnodes if (!bspx_loaded) { for (i = 0; i < numclipnodes; ++i) { VectorCopy(map_planes[map_clipnodes[i].planenum].normal, map_physicsnormals[i].normal); map_physicsnormals[i].flags = PHYSICSNORMAL_SET; } } // Now over-ride from external file { char extfile[MAX_OSPATH]; int extfilesize = 0; void* data = NULL; int mark; mark = Hunk_LowMark(); snprintf(extfile, sizeof(extfile), "maps/%s.qpn", loadname); data = FS_LoadHunkFile(extfile, &extfilesize); if (data) { if (CM_LoadPhysicsNormalsData(data, extfilesize)) { Con_Printf("Loading external physics normals\n"); } else { Con_Printf("%s is corrupt or wrong size\n", extfile); } Hunk_FreeToLowMark(mark); } } } /* ================= CM_MakeHull0 Deplicate the drawing hull structure as a clipping hull ================= */ static void CM_MakeHull0(void) { cnode_t *in, *child; mclipnode_t *out; int i, j, count; in = map_nodes; count = numnodes; out = (mclipnode_t *)Hunk_AllocName(count * sizeof(*out), loadname); // fix up hull 0 in all cmodels for (i = 0; i < numcmodels; i++) { map_cmodels[i].hulls[0].clipnodes = out; map_cmodels[i].hulls[0].lastclipnode = count - 1; } // build clipnodes from nodes for (i = 0; i < count; i++, out++, in++) { out->planenum = in->plane - map_planes; for (j = 0; j < 2; j++) { child = in->children[j]; out->children[j] = (child->contents < 0) ? (child->contents) : (child - map_nodes); } } } /* ================= CM_LoadPlanes ================= */ static void CM_LoadPlanes(lump_t *l) { int i, j, count, bits; mplane_t *out; dplane_t *in; in = (dplane_t *)(cmod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("CM_LoadMap: funny lump size"); count = l->filelen / sizeof(*in); out = (mplane_t *)Hunk_AllocName(count * sizeof(*out), loadname); map_planes = out; numplanes = count; for (i = 0; i < count; i++, in++, out++) { bits = 0; for (j = 0; j < 3; j++) { out->normal[j] = LittleFloat(in->normal[j]); if (out->normal[j] < 0) bits |= 1 << j; } out->dist = LittleFloat(in->dist); out->type = LittleLong(in->type); out->signbits = bits; } } /* ** DecompressVis */ static byte *DecompressVis(byte *in) { static byte decompressed[MAX_MAP_LEAFS / 8]; int c, row; byte *out; row = (visleafs + 7) >> 3; out = decompressed; if (!in) { // no vis info, so make all visible while (row) { *out++ = 0xff; row--; } return decompressed; } do { if (*in) { *out++ = *in++; continue; } c = in[1]; in += 2; while (c) { *out++ = 0; c--; } } while (out - decompressed < row); return decompressed; } /* ** CM_BuildPVS ** ** Call after CM_LoadLeafs! */ static void CM_BuildPVS(lump_t *lump_vis, lump_t *lump_leafs) { byte *visdata, *scan; dleaf_t *in; int i; map_vis_rowlongs = (visleafs + 31) >> 5; map_vis_rowbytes = map_vis_rowlongs * 4; map_pvs = (byte *)Hunk_Alloc(map_vis_rowbytes * visleafs); if (!lump_vis->filelen) { memset(map_pvs, 0xff, map_vis_rowbytes * visleafs); return; } // FIXME, add checks for lump_vis->filelen and leafs' visofs visdata = cmod_base + lump_vis->fileofs; // go through all leafs and decompress visibility data in = (dleaf_t *)(cmod_base + lump_leafs->fileofs); in++; // pvs row 0 is leaf 1 scan = map_pvs; for (i = 0; i < visleafs; i++, in++, scan += map_vis_rowbytes) { int p = LittleLong(in->visofs); memcpy(scan, (p == -1) ? map_novis : DecompressVis(visdata + p), map_vis_rowbytes); } } static void CM_BuildPVS29a(lump_t *lump_vis, lump_t *lump_leafs) { byte *visdata, *scan; dleaf29a_t *in; int i; map_vis_rowlongs = (visleafs + 31) >> 5; map_vis_rowbytes = map_vis_rowlongs * 4; map_pvs = (byte *)Hunk_Alloc(map_vis_rowbytes * visleafs); if (!lump_vis->filelen) { memset(map_pvs, 0xff, map_vis_rowbytes * visleafs); return; } // FIXME, add checks for lump_vis->filelen and leafs' visofs visdata = cmod_base + lump_vis->fileofs; // go through all leafs and decompress visibility data in = (dleaf29a_t *)(cmod_base + lump_leafs->fileofs); in++; // pvs row 0 is leaf 1 scan = map_pvs; for (i = 0; i < visleafs; i++, in++, scan += map_vis_rowbytes) { int p = LittleLong(in->visofs); memcpy(scan, (p == -1) ? map_novis : DecompressVis(visdata + p), map_vis_rowbytes); } } static void CM_BuildPVSBSP2(lump_t *lump_vis, lump_t *lump_leafs) { byte *visdata, *scan; dleaf_bsp2_t *in; int i; map_vis_rowlongs = (visleafs + 31) >> 5; map_vis_rowbytes = map_vis_rowlongs * 4; map_pvs = (byte *)Hunk_Alloc(map_vis_rowbytes * visleafs); if (!lump_vis->filelen) { memset(map_pvs, 0xff, map_vis_rowbytes * visleafs); return; } // FIXME, add checks for lump_vis->filelen and leafs' visofs visdata = cmod_base + lump_vis->fileofs; // go through all leafs and decompress visibility data in = (dleaf_bsp2_t *)(cmod_base + lump_leafs->fileofs); in++; // pvs row 0 is leaf 1 scan = map_pvs; for (i = 0; i < visleafs; i++, in++, scan += map_vis_rowbytes) { int p = LittleLong(in->visofs); memcpy(scan, (p == -1) ? map_novis : DecompressVis(visdata + p), map_vis_rowbytes); } } /* ** CM_BuildPHS ** ** Expands the PVS and calculates the PHS (potentially hearable set) ** Call after CM_BuildPVS (so that map_vis_rowbytes & map_vis_rowlongs are set) */ static void CM_BuildPHS (void) { int i, j, k, l, index1, bitbyte; unsigned *dest, *src; byte *scan; map_phs = NULL; if (map_vis_rowbytes * visleafs > 0x100000) { return; } map_phs = (byte *) Hunk_Alloc (map_vis_rowbytes * visleafs); scan = map_pvs; dest = (unsigned *)map_phs; for (i = 0; i < visleafs; i++, dest += map_vis_rowlongs, scan += map_vis_rowbytes) { // copy from pvs memcpy (dest, scan, map_vis_rowbytes); // or in hearable leafs for (j = 0; j < map_vis_rowbytes; j++) { bitbyte = scan[j]; if (!bitbyte) continue; for (k = 0; k < 8; k++) { if (! (bitbyte & (1<= visleafs) continue; src = (unsigned *)map_pvs + index1 * map_vis_rowlongs; for (l = 0; l < map_vis_rowlongs; l++) dest[l] |= src[l]; } } } } /* ** hunk was reset by host, so the data is no longer valid */ void CM_InvalidateMap (void) { map_name[0] = 0; // null out the pointers to turn up any attempt to call CM functions map_planes = NULL; map_nodes = NULL; map_clipnodes = NULL; map_leafs = NULL; map_pvs = NULL; map_phs = NULL; map_entitystring = NULL; map_physicsnormals = NULL; } /* ** CM_LoadMap */ typedef void(*BuildPVSFunction)(lump_t *lump_vis, lump_t *lump_leafs); cmodel_t *CM_LoadMap (char *name, qbool clientload, unsigned *checksum, unsigned *checksum2) { #ifndef CLIENTONLY extern cvar_t sv_bspversion, sv_halflifebsp; #endif unsigned int i; dheader_t *header; unsigned int *buf; unsigned int *padded_buf = NULL; BuildPVSFunction cm_load_pvs_func = CM_BuildPVS; qbool pad_lumps = false; int required_length = 0; int filelen = 0; if (map_name[0]) { assert(!strcmp(name, map_name)); if (checksum) *checksum = map_checksum; *checksum2 = map_checksum2; return &map_cmodels[0]; // still have the right version } // load the file buf = (unsigned int *) FS_LoadTempFile (name, &filelen); if (!buf) Host_Error ("CM_LoadMap: %s not found", name); COM_FileBase (name, loadname); header = (dheader_t *)buf; i = LittleLong (header->version); if (i != Q1_BSPVERSION && i != HL_BSPVERSION && i != Q1_BSPVERSION2 && i != Q1_BSPVERSION29a) Host_Error ("CM_LoadMap: %s has wrong version number (%i should be %i)", name, i, Q1_BSPVERSION); map_halflife = (i == HL_BSPVERSION); #ifndef CLIENTONLY Cvar_SetROM(&sv_halflifebsp, map_halflife ? "1" : "0"); Cvar_SetROM(&sv_bspversion, i == Q1_BSPVERSION || i == HL_BSPVERSION ? "1" : "2"); #endif // swap all the lumps for (i = 0; i < sizeof(dheader_t) / 4; i++) { ((int *)header)[i] = LittleLong(((int *)header)[i]); } // Align the lumps for (i = 0; i < HEADER_LUMPS; ++i) { pad_lumps |= (header->lumps[i].fileofs % 4) != 0; if (header->lumps[i].fileofs < 0 || header->lumps[i].filelen < 0) { Host_Error("CM_LoadMap: %s has invalid lump definitions", name); } if (header->lumps[i].fileofs + header->lumps[i].filelen > filelen || header->lumps[i].fileofs + header->lumps[i].filelen < 0) { Host_Error("CM_LoadMap: %s has invalid lump definitions", name); } required_length += header->lumps[i].filelen; } if (pad_lumps) { int position = 0; int required_size = sizeof(dheader_t) + required_length + HEADER_LUMPS * 4 + 1; padded_buf = Q_malloc(required_size); // Copy header memcpy(padded_buf, buf, sizeof(dheader_t)); header = (dheader_t*)padded_buf; position += sizeof(dheader_t); // Copy lumps: align on 4-byte boundary for (i = 0; i < HEADER_LUMPS; ++i) { if (position % 4) { position += 4 - (position % 4); } if (position + header->lumps[i].filelen > required_size) { Host_Error("CM_LoadMap: %s caused error while aligning lumps", name); } memcpy((byte*)padded_buf + position, ((byte*)buf) + header->lumps[i].fileofs, header->lumps[i].filelen); header->lumps[i].fileofs = position; position += header->lumps[i].filelen; } // Use the new buffer buf = padded_buf; } cmod_base = (byte *)header; // checksum all of the map, except for entities map_checksum = map_checksum2 = 0; for (i = 0; i < HEADER_LUMPS; i++) { if (i == LUMP_ENTITIES) continue; map_checksum ^= LittleLong(Com_BlockChecksum(cmod_base + header->lumps[i].fileofs, header->lumps[i].filelen)); if (i == LUMP_VISIBILITY || i == LUMP_LEAFS || i == LUMP_NODES) continue; map_checksum2 ^= LittleLong(Com_BlockChecksum(cmod_base + header->lumps[i].fileofs, header->lumps[i].filelen)); } if (checksum) *checksum = map_checksum; *checksum2 = map_checksum2; // load into heap CM_LoadPlanes (&header->lumps[LUMP_PLANES]); if (LittleLong(header->version) == Q1_BSPVERSION29a) { CM_LoadLeafs29a(&header->lumps[LUMP_LEAFS]); CM_LoadNodes29a(&header->lumps[LUMP_NODES]); CM_LoadClipnodesBSP2(&header->lumps[LUMP_CLIPNODES]); cm_load_pvs_func = CM_BuildPVS29a; } else if (LittleLong(header->version) == Q1_BSPVERSION2) { CM_LoadLeafsBSP2(&header->lumps[LUMP_LEAFS]); CM_LoadNodesBSP2(&header->lumps[LUMP_NODES]); CM_LoadClipnodesBSP2(&header->lumps[LUMP_CLIPNODES]); cm_load_pvs_func = CM_BuildPVSBSP2; } else { CM_LoadLeafs(&header->lumps[LUMP_LEAFS]); CM_LoadNodes(&header->lumps[LUMP_NODES]); CM_LoadClipnodes(&header->lumps[LUMP_CLIPNODES]); cm_load_pvs_func = CM_BuildPVS; } CM_LoadEntities (&header->lumps[LUMP_ENTITIES]); CM_LoadSubmodels (&header->lumps[LUMP_MODELS]); CM_LoadPhysicsNormals(filelen); CM_MakeHull0 (); cm_load_pvs_func (&header->lumps[LUMP_VISIBILITY], &header->lumps[LUMP_LEAFS]); if (!clientload) // client doesn't need PHS CM_BuildPHS (); strlcpy (map_name, name, sizeof(map_name)); Q_free(padded_buf); return &map_cmodels[0]; } cmodel_t *CM_InlineModel (char *name) { int num; if (!name || name[0] != '*') Host_Error ("CM_InlineModel: bad name"); num = atoi (name+1); if (num < 1 || num >= numcmodels) Host_Error ("CM_InlineModel: bad number"); return &map_cmodels[num]; } void CM_Init (void) { memset (map_novis, 0xff, sizeof(map_novis)); CM_InitBoxHull (); } #ifndef SERVER_ONLY // Allow in-memory modifications to ground normals... void CM_PhysicsNormalSet(int num, float x, float y, float z, int flags) { if (num > 0 && num <= numclipnodes) { VectorSet(map_physicsnormals[num - 1].normal, x, y, z); map_physicsnormals[num - 1].flags = flags; } } // Allow map developer to dump normals void CM_PhysicsNormalDump(FILE* out, float rampjump, float maxgroundspeed) { if (map_physicsnormals) { fwrite(&rampjump, 4, 1, out); fwrite(&maxgroundspeed, 4, 1, out); fwrite(map_physicsnormals, sizeof(*map_physicsnormals) * numclipnodes, 1, out); } } #endif mphysicsnormal_t CM_PhysicsNormal(int num) { mphysicsnormal_t ret; qbool inverse = num < 0; memset(&ret, 0, sizeof(ret)); num = abs(num); if (num > 0 && num <= numclipnodes) { ret = map_physicsnormals[num - 1]; if (inverse) { VectorNegate(ret.normal, ret.normal); } } return ret; } void* Mod_BSPX_FindLump(bspx_header_t* bspx_header, char* lumpname, int* plumpsize, byte* mod_base) { int i; bspx_lump_t* lump; if (!bspx_header) { return NULL; } lump = (bspx_lump_t*)(bspx_header + 1); for (i = 0; i < bspx_header->numlumps; i++, lump++) { if (!strcmp(lump->lumpname, lumpname)) { if (plumpsize) { *plumpsize = lump->filelen; } return mod_base + lump->fileofs; } } return NULL; } bspx_header_t* Mod_LoadBSPX(int filesize, byte* mod_base) { dheader_t* header; bspx_header_t* xheader; bspx_lump_t* lump; int i; int xofs; // find end of last lump header = (dheader_t*)mod_base; xofs = 0; for (i = 0; i < HEADER_LUMPS; i++) { xofs = max(xofs, header->lumps[i].fileofs + header->lumps[i].filelen); } if (xofs + sizeof(bspx_header_t) > filesize) { return NULL; } xheader = (bspx_header_t*)(mod_base + xofs); xheader->numlumps = LittleLong(xheader->numlumps); if (xheader->numlumps < 0 || xofs + sizeof(bspx_header_t) + xheader->numlumps * sizeof(bspx_lump_t) > filesize) { return NULL; } // byte-swap and check sanity lump = (bspx_lump_t*)(xheader + 1); // lumps immediately follow the header for (i = 0; i < xheader->numlumps; i++, lump++) { lump->lumpname[sizeof(lump->lumpname) - 1] = '\0'; // make sure it ends with zero lump->fileofs = LittleLong(lump->fileofs); lump->filelen = LittleLong(lump->filelen); if (lump->fileofs < 0 || lump->filelen < 0 || (unsigned)(lump->fileofs + lump->filelen) >(unsigned)filesize) { return NULL; } } // success return xheader; } mvdsv-0.35/src/cmodel.h000066400000000000000000000072461427146041000150130ustar00rootroot00000000000000/* 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. */ // cmodel.h #ifndef __CMODEL_H__ #define __CMODEL_H__ #include "bspfile.h" // mplane->type enum { SIDE_FRONT = 0, SIDE_BACK = 1, SIDE_ON = 2 }; // plane_t structure // !!! if this is changed, it must be changed in asm_i386.h too !!! typedef struct mplane_s { vec3_t normal; float dist; byte type; // for texture axis selection and fast side tests byte signbits; // signx + signy<<1 + signz<<1 byte pad[2]; } mplane_t; // !!! if this is changed, it must be changed in asm_i386.h too !!! typedef struct { mclipnode_t *clipnodes; mplane_t *planes; int firstclipnode; int lastclipnode; vec3_t clip_mins; vec3_t clip_maxs; } hull_t; typedef struct { vec3_t normal; float dist; } plane_t; typedef struct mphysicsnormal_s { vec3_t normal; int flags; } mphysicsnormal_t; #define PHYSICSNORMAL_SET 1 #define PHYSICSNORMAL_FLIPX 2 #define PHYSICSNORMAL_FLIPY 4 #define PHYSICSNORMAL_FLIPZ 8 mphysicsnormal_t CM_PhysicsNormal(int num); #ifndef SERVER_ONLY void CM_PhysicsNormalSet(int num, float x, float y, float z, int flags); void CM_PhysicsNormalDump(FILE* out, float rampjump, float maxgroundspeed); #endif typedef struct { qbool allsolid; // if true, plane is not valid qbool startsolid; // if true, the initial point was in a solid area qbool inopen, inwater; float fraction; // time completed, 1.0 = didn't hit anything vec3_t endpos; // final position plane_t plane; // surface normal at impact int physicsnormal; // surface normal for physics union { // entity the surface is on int entnum; // for pmove struct edict_s *ent; // for sv_world } e; } trace_t; typedef struct { vec3_t mins, maxs; vec3_t origin; hull_t hulls[MAX_MAP_HULLS]; } cmodel_t; hull_t *CM_HullForBox (vec3_t mins, vec3_t maxs); int CM_HullPointContents (hull_t *hull, int num, vec3_t p); int CM_CachedHullPointContents(hull_t* hull, int num, vec3_t p, float* min_dist); trace_t CM_HullTrace (hull_t *hull, vec3_t start, vec3_t end); struct cleaf_s *CM_PointInLeaf (const vec3_t p); int CM_Leafnum (const struct cleaf_s *leaf); int CM_LeafAmbientLevel (const struct cleaf_s *leaf, int ambient_channel); byte *CM_LeafPVS (const struct cleaf_s *leaf); byte *CM_LeafPHS (const struct cleaf_s *leaf); // only for the server byte *CM_FatPVS (vec3_t org); int CM_FindTouchedLeafs (const vec3_t mins, const vec3_t maxs, int leafs[], int maxleafs, int headnode, int *topnode); char *CM_EntityString (void); int CM_NumInlineModels (void); cmodel_t *CM_InlineModel (char *name); void CM_InvalidateMap (void); cmodel_t *CM_LoadMap (char *name, qbool clientload, unsigned *checksum, unsigned *checksum2); void CM_Init (void); typedef struct bspx_header_s { char id[4]; // 'BSPX' int numlumps; } bspx_header_t; void* Mod_BSPX_FindLump(bspx_header_t* bspx_header, char* lumpname, int* plumpsize, byte* mod_base); bspx_header_t* Mod_LoadBSPX(int filesize, byte* mod_base); #endif /* !__CMODEL_H__ */ mvdsv-0.35/src/common.c000066400000000000000000001070401427146041000150240ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // common.c -- misc functions used in client and server #include "qwsvdef.h" usercmd_t nullcmd; // guarenteed to be zero static char *largv[MAX_NUM_ARGVS + 1]; //============================================================================ /* ============================================================================== MESSAGE IO FUNCTIONS Handles byte ordering and avoids alignment errors ============================================================================== */ #ifdef FTE_PEXT_FLOATCOORDS int msg_coordsize = 2; // 2 or 4. int msg_anglesize = 1; // 1 or 2. float MSG_FromCoord(coorddata c, int bytes) { switch(bytes) { case 2: //encode 1/8th precision, giving -4096 to 4096 map sizes return LittleShort(c.b2)/8.0f; case 4: return LittleFloat(c.f); default: Sys_Error("MSG_FromCoord: not a sane size"); return 0; } } coorddata MSG_ToCoord(float f, int bytes) //return value should be treated as (char*)&ret; { coorddata r; switch(bytes) { case 2: r.b4 = 0; if (f >= 0) r.b2 = LittleShort((short)(f*8+0.5f)); else r.b2 = LittleShort((short)(f*8-0.5f)); break; case 4: r.f = LittleFloat(f); break; default: Sys_Error("MSG_ToCoord: not a sane size"); r.b4 = 0; } return r; } coorddata MSG_ToAngle(float f, int bytes) //return value is NOT byteswapped. { coorddata r; switch(bytes) { case 1: r.b4 = 0; if (f >= 0) r.b[0] = (int)(f*(256.0f/360.0f) + 0.5f) & 255; else r.b[0] = (int)(f*(256.0f/360.0f) - 0.5f) & 255; break; case 2: r.b4 = 0; if (f >= 0) r.b2 = LittleShort((int)(f*(65536.0f/360.0f) + 0.5f) & 65535); else r.b2 = LittleShort((int)(f*(65536.0f/360.0f) - 0.5f) & 65535); break; // case 4: // r.f = LittleFloat(f); // break; default: Sys_Error("MSG_ToAngle: not a sane size"); r.b4 = 0; } return r; } #endif // writing functions void MSG_WriteChar (sizebuf_t *sb, const int c) { byte *buf; #ifdef PARANOID if (c < -128 || c > 127) Sys_Error ("MSG_WriteChar: range error"); #endif buf = (byte *) SZ_GetSpace (sb, 1); buf[0] = c; } void MSG_WriteByte (sizebuf_t *sb, const int c) { byte *buf; #ifdef PARANOID if (c < 0 || c > 255) Sys_Error ("MSG_WriteByte: range error"); #endif buf = (byte *) SZ_GetSpace (sb, 1); buf[0] = c; } void MSG_WriteShort (sizebuf_t *sb, const int c) { byte *buf; #ifdef PARANOID if (c < ((short)0x8000) || c > (short)0x7fff) Sys_Error ("MSG_WriteShort: range error"); #endif buf = (byte *) SZ_GetSpace (sb, 2); buf[0] = c&0xff; buf[1] = c>>8; } void MSG_WriteLong (sizebuf_t *sb, const int c) { byte *buf; buf = (byte *) SZ_GetSpace (sb, 4); buf[0] = c&0xff; buf[1] = (c>>8)&0xff; buf[2] = (c>>16)&0xff; buf[3] = c>>24; } void MSG_WriteFloat (sizebuf_t *sb, const float f) { union { float f; int l; } dat; dat.f = f; dat.l = LittleLong (dat.l); SZ_Write (sb, &dat.l, 4); } void MSG_WriteString (sizebuf_t *sb, const char *s) { if (!s || !*s) SZ_Write (sb, "", 1); else SZ_Write (sb, s, strlen(s)+1); } void MSG_WriteCoord (sizebuf_t *sb, const float f) { #ifdef FTE_PEXT_FLOATCOORDS coorddata i = MSG_ToCoord(f, msg_coordsize); SZ_Write (sb, (void*)&i, msg_coordsize); #else MSG_WriteShort (sb, (int)(f * 8)); #endif } void MSG_WriteLongCoord(sizebuf_t* sb, float f) { f = LittleFloat(f); SZ_Write (sb, (void*)&f, sizeof(f)); } void MSG_WriteAngle (sizebuf_t *sb, const float f) { #ifdef FTE_PEXT_FLOATCOORDS if (msg_anglesize == 2) MSG_WriteAngle16(sb, f); // else if (msg_anglesize==4) // MSG_WriteFloat(sb, f); else #endif MSG_WriteByte (sb, Q_rint(f * 256.0 / 360.0) & 255); } void MSG_WriteAngle16 (sizebuf_t *sb, const float f) { MSG_WriteShort (sb, Q_rint(f*65536.0/360.0) & 65535); } void MSG_WriteDeltaUsercmd (sizebuf_t *buf, const usercmd_t *from, const usercmd_t *cmd, unsigned int mvdsv_extensions) { int bits; // send the movement message bits = 0; if (cmd->angles[0] != from->angles[0]) bits |= CM_ANGLE1; if (cmd->angles[1] != from->angles[1]) bits |= CM_ANGLE2; if (cmd->angles[2] != from->angles[2]) bits |= CM_ANGLE3; if (cmd->forwardmove != from->forwardmove) bits |= CM_FORWARD; if (cmd->sidemove != from->sidemove) bits |= CM_SIDE; if (cmd->upmove != from->upmove) bits |= CM_UP; if (cmd->buttons != from->buttons) bits |= CM_BUTTONS; if (cmd->impulse != from->impulse) bits |= CM_IMPULSE; MSG_WriteByte (buf, bits); if (bits & CM_ANGLE1) MSG_WriteAngle16 (buf, cmd->angles[0]); if (bits & CM_ANGLE2) MSG_WriteAngle16 (buf, cmd->angles[1]); if (bits & CM_ANGLE3) MSG_WriteAngle16 (buf, cmd->angles[2]); if (bits & CM_FORWARD) MSG_WriteShort (buf, cmd->forwardmove); if (bits & CM_SIDE) MSG_WriteShort (buf, cmd->sidemove); if (bits & CM_UP) MSG_WriteShort (buf, cmd->upmove); if (bits & CM_BUTTONS) MSG_WriteByte (buf, cmd->buttons); if (bits & CM_IMPULSE) MSG_WriteByte (buf, cmd->impulse); MSG_WriteByte (buf, cmd->msec); } // reading functions int msg_readcount; qbool msg_badread; void MSG_BeginReading (void) { msg_readcount = 0; msg_badread = false; } int MSG_GetReadCount (void) { return msg_readcount; } // returns -1 and sets msg_badread if no more characters are available int MSG_ReadChar (void) { int c; if (msg_readcount + 1 > net_message.cursize) { msg_badread = true; return -1; } c = (signed char) net_message.data[msg_readcount]; msg_readcount++; return c; } int MSG_ReadByte (void) { int c; if (msg_readcount + 1 > net_message.cursize) { msg_badread = true; return -1; } c = (unsigned char) net_message.data[msg_readcount]; msg_readcount++; return c; } int MSG_ReadShort (void) { int c; if (msg_readcount + 2 > net_message.cursize) { msg_badread = true; return -1; } c = (short) (net_message.data[msg_readcount] + (net_message.data[msg_readcount+1]<<8)); msg_readcount += 2; return c; } int MSG_ReadLong (void) { int c; if (msg_readcount + 4 > net_message.cursize) { msg_badread = true; return -1; } c = net_message.data[msg_readcount] + (net_message.data[msg_readcount+1]<<8) + (net_message.data[msg_readcount+2]<<16) + (net_message.data[msg_readcount+3]<<24); msg_readcount += 4; return c; } float MSG_ReadFloat (void) { union { byte b[4]; float f; int l; } dat; dat.b[0] = net_message.data[msg_readcount]; dat.b[1] = net_message.data[msg_readcount+1]; dat.b[2] = net_message.data[msg_readcount+2]; dat.b[3] = net_message.data[msg_readcount+3]; msg_readcount += 4; dat.l = LittleLong (dat.l); return dat.f; } char *MSG_ReadString (void) { static char string[2048]; int c; size_t l = 0; do { c = MSG_ReadByte (); if (c == 255) // skip these to avoid security problems continue; // with old clients and servers if (c == -1 || c == 0) break; string[l] = c; l++; } while (l < sizeof (string) - 1); string[l] = 0; return string; } char *MSG_ReadStringLine (void) { static char string[2048]; int c; size_t l = 0; do { c = MSG_ReadByte (); if (c == 255) continue; if (c == -1 || c == 0 || c == '\n') break; string[l] = c; l++; } while (l < sizeof (string) - 1); string[l] = 0; return string; } float MSG_ReadCoord (void) { #ifdef FTE_PEXT_FLOATCOORDS coorddata c = {0}; MSG_ReadData(&c, msg_coordsize); return MSG_FromCoord(c, msg_coordsize); #else // FTE_PEXT_FLOATCOORDS return MSG_ReadShort() * (1.0 / 8); #endif // FTE_PEXT_FLOATCOORDS } float MSG_ReadAngle16 (void) { return MSG_ReadShort () * (360.0 / 65536); } float MSG_ReadAngle (void) { #ifdef FTE_PEXT_FLOATCOORDS switch(msg_anglesize) { case 1: return MSG_ReadChar() * (360.0/256); case 2: return MSG_ReadAngle16(); // case 4: // return MSG_ReadFloat(); default: Sys_Error("MSG_ReadAngle: Bad angle size\n"); return 0; } #else // FTE_PEXT_FLOATCOORDS return MSG_ReadChar() * (360.0 / 256); #endif // FTE_PEXT_FLOATCOORDS } void MSG_ReadDeltaUsercmd (const usercmd_t *from, usercmd_t *move) { int bits; memcpy (move, from, sizeof(*move)); bits = MSG_ReadByte (); // read current angles if (bits & CM_ANGLE1) move->angles[0] = MSG_ReadAngle16 (); if (bits & CM_ANGLE2) move->angles[1] = MSG_ReadAngle16 (); if (bits & CM_ANGLE3) move->angles[2] = MSG_ReadAngle16 (); // read movement if (bits & CM_FORWARD) move->forwardmove = MSG_ReadShort (); if (bits & CM_SIDE) move->sidemove = MSG_ReadShort (); if (bits & CM_UP) move->upmove = MSG_ReadShort (); // read buttons if (bits & CM_BUTTONS) move->buttons = MSG_ReadByte (); if (bits & CM_IMPULSE) move->impulse = MSG_ReadByte (); // read time to run command move->msec = MSG_ReadByte (); } void MSG_ReadData (void *data, int len) { int i; for (i = 0 ; i < len ; i++) ((byte *)data)[i] = MSG_ReadByte (); } void MSG_ReadSkip(int bytes) { for ( ; !msg_badread && bytes > 0; bytes--) { MSG_ReadByte (); } } //=========================================================================== void SZ_InitEx (sizebuf_t *buf, byte *data, const int length, qbool allowoverflow) { memset (buf, 0, sizeof (*buf)); buf->data = data; buf->maxsize = length; buf->allowoverflow = allowoverflow; } void SZ_Init (sizebuf_t *buf, byte *data, const int length) { SZ_InitEx (buf, data, length, false); } void SZ_Clear (sizebuf_t *buf) { buf->cursize = 0; buf->overflowed = false; } void *SZ_GetSpace (sizebuf_t *buf, const int length) { void *data; if (buf->cursize + length > buf->maxsize) { if (!buf->allowoverflow) Sys_Error ("SZ_GetSpace: overflow without allowoverflow set (%d/%d/%d)", buf->cursize, length, buf->maxsize); if (length > buf->maxsize) Sys_Error ("SZ_GetSpace: %d/%d is > full buffer size", length, buf->maxsize); // because Con_Printf may be redirected Sys_Printf ("SZ_GetSpace: overflow: cur = %d, len = %d, max = %d\n", buf->cursize, length, buf->maxsize); SZ_Clear (buf); buf->overflowed = true; } data = buf->data + buf->cursize; buf->cursize += length; return data; } void SZ_Write (sizebuf_t *buf, const void *data, int length) { byte* dest = SZ_GetSpace(buf, length); memcpy(dest, data, length); } void SZ_Print (sizebuf_t *buf, const char *data) { int len = strlen(data) + 1; // Remove trailing '\0' if (buf->cursize && !buf->data[buf->cursize - 1]) { --buf->cursize; } SZ_Write(buf, data, len); } //============================================================================ #define TOKENSIZE sizeof(com_token) char com_token[TOKENSIZE]; int com_argc; char **com_argv; com_tokentype_t com_tokentype; /* ============== COM_Parse Parse a token out of a string ============== */ const char *COM_Parse (const char *data) { unsigned char c; int len; len = 0; com_token[0] = 0; if (!data) return NULL; // skip whitespace while (true) { while ((c = *data) == ' ' || c == '\t' || c == '\r' || c == '\n') data++; if (c == 0) return NULL; // end of file; // skip // comments if (c=='/' && data[1] == '/') { while (*data && *data != '\n') data++; } else break; } // handle quoted strings specially if (c == '\"') { data++; while (1) { c = *data++; if (c == '\"' || !c) { com_token[len] = 0; if (!c) data--; return data; } if (len < MAX_COM_TOKEN - 1) { com_token[len] = c; len++; } } } // parse a regular word do { if (len < MAX_COM_TOKEN-1) { com_token[len] = c; len++; } data++; c = *data; } while (c && c != ' ' && c != '\t' && c != '\n' && c != '\r'); com_token[len] = 0; return data; } #define DEFAULT_PUNCTUATION "(,{})(\':;=!><&|+" char *COM_ParseToken (const char *data, const char *punctuation) { int c; int len; if (!punctuation) punctuation = DEFAULT_PUNCTUATION; len = 0; com_token[0] = 0; if (!data) { com_tokentype = TTP_UNKNOWN; return NULL; } // skip whitespace skipwhite: while ((c = *(unsigned char *) data) <= ' ') { if (c == 0) { com_tokentype = TTP_UNKNOWN; return NULL; // end of file; } data++; } // skip // comments if (c == '/') { if (data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } else if (data[1] == '*') { data += 2; while (*data && (*data != '*' || data[1] != '/')) data++; data += 2; goto skipwhite; } } // handle quoted strings specially if (c == '\"') { com_tokentype = TTP_STRING; data++; while (1) { if (len >= TOKENSIZE - 1) { com_token[len] = '\0'; return (char*) data; } c = *data++; if (c=='\"' || !c) { com_token[len] = 0; return (char*) data; } com_token[len] = c; len++; } } com_tokentype = TTP_UNKNOWN; // parse single characters if (strchr (punctuation, c)) { com_token[len] = c; len++; com_token[len] = 0; return (char*) (data + 1); } // parse a regular word do { if (len >= TOKENSIZE - 1) break; com_token[len] = c; data++; len++; c = *data; if (strchr (punctuation, c)) break; } while (c > 32); com_token[len] = 0; return (char*) data; } /* ================ COM_InitArgv ================ */ void COM_InitArgv (int argc, char **argv) { for (com_argc = 0; (com_argc < MAX_NUM_ARGVS) && (com_argc < argc); com_argc++) { largv[com_argc] = (argv[com_argc]) ? argv[com_argc] : ""; } largv[com_argc] = ""; com_argv = largv; } /* ================ COM_CheckParm Returns the position (1 to argc-1) in the program's argument list where the given parameter appears, or 0 if not present ================ */ int COM_CheckParm (const char *parm) { int i; for (i = 1; i < com_argc; i++) { if (!strcmp (parm,com_argv[i])) return i; } return 0; } int COM_Argc (void) { return com_argc; } char *COM_Argv (int arg) { if (arg < 0 || arg >= com_argc) return ""; return com_argv[arg]; } /* ===================================================================== INFO STRINGS ===================================================================== */ /* =============== Info_ValueForKey Searches the string for the given key and returns the associated value, or an empty string. =============== */ char *Info_ValueForKey (char *s, const char *key) { char pkey[512]; static char value[4][512]; // use two buffers so compares // work without stomping on each other static int valueindex; char *o; valueindex = (valueindex + 1) % 4; if (*s == '\\') s++; while (1) { o = pkey; while (*s != '\\') { if (!*s || o >= pkey + sizeof(pkey) - 1) return ""; *o++ = *s++; } *o = 0; s++; o = value[valueindex]; while (*s != '\\' && *s) { if (!*s || o >= value[valueindex] + sizeof(value[valueindex]) - 1) return ""; *o++ = *s++; } *o = 0; if (!strncmp (key, pkey, sizeof(pkey)) ) return value[valueindex]; if (!*s) return ""; s++; } } /* // WARNING: non standard behavior for Info_* function, this function may return NULL while other functions always return at least "" char *Info_KeyNameForKeyNum (char *s, int key) { static char pkey[4][512]; // use two buffers so compares // work without stomping on each other static int keyindex; char *o; keyindex = (keyindex + 1) % 4; if (*s == '\\') s++; // ? if (key < 1) return NULL; while (1) { key--; // get key name o = pkey[keyindex]; while (*s != '\\') { if (!*s || o >= pkey[keyindex] + sizeof(pkey[keyindex]) - 1) return NULL; *o++ = *s++; } *o = 0; s++; // skip key value while (*s != '\\' && *s) s++; if (!key) return pkey[keyindex]; if (!*s) return NULL; s++; } } */ void Info_RemoveKey (char *s, const char *key) { char *start; char pkey[512]; char value[512]; char *o; if (strstr (key, "\\")) { Con_Printf ("Can't use a key with a \\\n"); return; } while (1) { start = s; if (*s == '\\') s++; o = pkey; while (*s != '\\') { if (!*s || o >= pkey + sizeof(pkey) - 1) return; *o++ = *s++; } *o = 0; s++; o = value; while (*s != '\\' && *s) { if (!*s || o >= value + sizeof(value) - 1) return; *o++ = *s++; } *o = 0; if (!strncmp (key, pkey, sizeof(pkey)) ) { memmove (start, s, strlen(s) + 1); // remove this part return; } if (!*s) return; } } void Info_RemovePrefixedKeys (char *start, char prefix) { char *s; char pkey[512]; char value[512]; char *o; s = start; while (1) { if (*s == '\\') s++; o = pkey; while (*s != '\\') { if (!*s || o >= pkey + sizeof(pkey) - 1) return; *o++ = *s++; } *o = 0; s++; o = value; while (*s != '\\' && *s) { if (!*s || o >= value + sizeof(value) - 1) return; *o++ = *s++; } *o = 0; if (pkey[0] == prefix) { Info_RemoveKey (start, pkey); s = start; } if (!*s) return; } } void Info_SetValueForStarKey (char *s, const char *key, const char *value, unsigned int maxsize) { char _new[1024], *v; int c; // extern cvar_t sv_highchars; if (strstr (key, "\\") || strstr (value, "\\") ) { Con_Printf ("Can't use keys or values with a \\\n"); return; } if (strstr (key, "\"") || strstr (value, "\"") ) { Con_Printf ("Can't use keys or values with a \"\n"); return; } if (strlen(key) >= MAX_KEY_STRING || strlen(value) >= MAX_KEY_STRING) { Con_Printf ("Keys and values must be < %d characters.\n", MAX_KEY_STRING); return; } // this next line is kinda trippy if (*(v = Info_ValueForKey(s, key))) { // key exists, make sure we have enough room for new value, if we don't, // don't change it! if (strlen(value) - strlen(v) + strlen(s) >= maxsize) { Con_Printf ("Info string length exceeded (change: key = '%s', old value = '%s', new value = '%s', strlen info = '%d', maxsize = '%d')\nFull info string: '%s'\n", key, v, value, strlen(s), maxsize, s); return; } } Info_RemoveKey (s, key); if (!value || !strlen(value)) return; snprintf (_new, sizeof(_new), "\\%s\\%s", key, value); if (strlen(_new) + strlen(s) >= maxsize) { Con_Printf ("Info string length exceeded (add: key = '%s', value = '%s', strlen info = '%d', maxsize = '%d')\nFull info string: '%s'\n", key, value, strlen(s), maxsize, s); return; } // only copy ascii values s += strlen(s); v = _new; while (*v) { c = (unsigned char)*v++; /* if (!(int)sv_highchars.value) { c &= 127; if (c < 32 || c > 127) continue; } */ // c &= 127; // strip high bits if (c > 13) // && c < 127) *s++ = c; } *s = 0; } void Info_SetValueForKey (char *s, const char *key, const char *value, unsigned int maxsize) { if (key[0] == '*') { Con_Printf ("Can't set * keys\n"); return; } Info_SetValueForStarKey (s, key, value, maxsize); } void Info_Print (char *s) { char key[512]; char value[512]; char *o; int l; if (*s == '\\') s++; while (*s) { o = key; while (*s && *s != '\\') *o++ = *s++; l = o - key; if (l < 20) { memset (o, ' ', 20-l); key[20] = 0; } else *o = 0; Con_Printf ("%s ", key); if (!*s) { Con_Printf ("MISSING VALUE\n"); return; } o = value; s++; while (*s && *s != '\\') *o++ = *s++; *o = 0; if (*s) s++; Con_Printf ("%s\n", value); } } void Info_CopyStarKeys (const char *from, char *to, unsigned int maxsize) { char key[512]; char value[512]; char *o; if (*from == '\\') from++; while (*from) { o = key; while (*from && *from != '\\') *o++ = *from++; *o = 0; o = value; from++; while (*from && *from != '\\') *o++ = *from++; *o = 0; if (*from) from++; if (key[0] == '*') Info_SetValueForStarKey (to, key, value, maxsize); } } //============================================================ // // Alternative variant manipulation with info strings // //============================================================ // this is seems to be "better" than Com_HashKey() static unsigned long Info_HashKey (const char *str) { unsigned long hash = 0; int c; // the (c&~32) makes it case-insensitive // hash function known as sdbm, used in gawk while ((c = *str++)) hash = (c &~ 32) + (hash << 6) + (hash << 16) - hash; return hash; } // used internally static info_t *_Info_Get (ctxinfo_t *ctx, const char *name) { info_t *a; int key; if (!ctx || !name || !name[0]) return NULL; key = Info_HashKey (name) % INFO_HASHPOOL_SIZE; for (a = ctx->info_hash[key]; a; a = a->hash_next) if (!strcasecmp(name, a->name)) return a; return NULL; } char *Info_Get(ctxinfo_t *ctx, const char *name) { static char value[4][512]; static int valueindex = 0; info_t *a = _Info_Get(ctx, name); if ( a ) { valueindex = (valueindex + 1) % 4; strlcpy(value[valueindex], a->value, sizeof(value[0])); return value[valueindex]; } else { return ""; } } qbool Info_SetStar (ctxinfo_t *ctx, const char *name, const char *value) { info_t *a; int key; if (!value) value = ""; if (!ctx || !name || !name[0]) return false; // empty value, instead of set just remove it if (!value[0]) { return Info_Remove(ctx, name); } if (strchr(name, '\\') || strchr(value, '\\')) return false; if (strchr(name, 128 + '\\') || strchr(value, 128 + '\\')) return false; if (strchr(name, '"') || strchr(value, '"')) return false; if (strchr(name, '\r') || strchr(value, '\r')) // bad for print functions return false; if (strchr(name, '\n') || strchr(value, '\n')) // bad for print functions return false; if (strchr(name, '$') || strchr(value, '$')) // variable expansion may be exploited, escaping this return false; if (strchr(name, ';') || strchr(value, ';')) // interpreter may be haxed, escaping this return false; if (strlen(name) >= MAX_KEY_STRING || strlen(value) >= MAX_KEY_STRING) return false; // too long name/value, its wrong key = Info_HashKey(name) % INFO_HASHPOOL_SIZE; // if already exists, reuse it for (a = ctx->info_hash[key]; a; a = a->hash_next) if (!strcasecmp(name, a->name)) { Q_free (a->value); break; } // not found, create new one if (!a) { if (ctx->cur >= ctx->max) return false; // too much infos a = (info_t *) Q_malloc (sizeof(info_t)); a->next = ctx->info_list; ctx->info_list = a; a->hash_next = ctx->info_hash[key]; ctx->info_hash[key] = a; ctx->cur++; // increase counter // copy name a->name = Q_strdup (name); } // copy value #if 0 { // unfortunatelly evil users use non printable/control chars, so that does not work well a->value = Q_strdup (value); } #else { // skip some control chars, doh char v_buf[MAX_KEY_STRING] = {0}, *v = v_buf; int i; for (i = 0; value[i]; i++) // len of 'value' should be less than MAX_KEY_STRING according to above checks { if ((unsigned char)value[i] > 13) *v++ = value[i]; } *v = 0; a->value = Q_strdup (v_buf); } #endif // hrm, empty value, remove it then if (!a->value[0]) { return Info_Remove(ctx, name); } return true; } qbool Info_Set (ctxinfo_t *ctx, const char *name, const char *value) { if (!value) value = ""; if (!ctx || !name || !name[0]) return false; if (name[0] == '*') { Con_Printf ("Can't set * keys [%s]\n", name); return false; } return Info_SetStar (ctx, name, value); } // used internally static void _Info_Free(info_t *a) { if (!a) return; Q_free (a->name); Q_free (a->value); Q_free (a); } qbool Info_Remove (ctxinfo_t *ctx, const char *name) { info_t *a, *prev; int key; if (!ctx || !name || !name[0]) return false; key = Info_HashKey (name) % INFO_HASHPOOL_SIZE; prev = NULL; for (a = ctx->info_hash[key]; a; a = a->hash_next) { if (!strcasecmp(name, a->name)) { // unlink from hash if (prev) prev->hash_next = a->hash_next; else ctx->info_hash[key] = a->hash_next; break; } prev = a; } if (!a) return false; // not found prev = NULL; for (a = ctx->info_list; a; a = a->next) { if (!strcasecmp(name, a->name)) { // unlink from info list if (prev) prev->next = a->next; else ctx->info_list = a->next; // free _Info_Free(a); ctx->cur--; // decrease counter return true; } prev = a; } Sys_Error("Info_Remove: info list broken"); return false; // shut up compiler } // remove all infos void Info_RemoveAll (ctxinfo_t *ctx) { info_t *a, *next; if (!ctx) return; for (a = ctx->info_list; a; a = next) { next = a->next; // free _Info_Free(a); } ctx->info_list = NULL; ctx->cur = 0; // set counter to 0 // clear hash memset (ctx->info_hash, 0, sizeof(ctx->info_hash)); } qbool Info_Convert(ctxinfo_t *ctx, char *str) { char name[MAX_KEY_STRING], value[MAX_KEY_STRING], *start; if (!ctx) return false; for ( ; str && str[0]; ) { if (!(str = strchr(str, '\\'))) break; start = str; // start of name if (!(str = strchr(start + 1, '\\'))) // end of name break; strlcpy(name, start + 1, min(str - start, (int)sizeof(name))); start = str; // start of value str = strchr(start + 1, '\\'); // end of value strlcpy(value, start + 1, str ? min(str - start, (int)sizeof(value)) : (int)sizeof(value)); Info_SetStar(ctx, name, value); } return true; } qbool Info_ReverseConvert(ctxinfo_t *ctx, char *str, int size) { info_t *a; int next_size; if (!ctx) return false; if (!str || size < 1) return false; str[0] = 0; for (a = ctx->info_list; a; a = a->next) { if (!a->value[0]) continue; // empty next_size = size - 2 - strlen(a->name) - strlen(a->value); if (next_size < 1) { // sigh, next snprintf will not fit return false; } snprintf(str, size, "\\%s\\%s", a->name, a->value); str += (size - next_size); size = next_size; } return true; } qbool Info_CopyStar(ctxinfo_t *ctx_from, ctxinfo_t *ctx_to) { info_t *a; if (!ctx_from || !ctx_to) return false; if (ctx_from == ctx_to) return true; // hrm for (a = ctx_from->info_list; a; a = a->next) { if (a->name[0] != '*') continue; // not a star key // do we need check status of this function? Info_SetStar (ctx_to, a->name, a->value); } return true; } void Info_PrintList(ctxinfo_t *ctx) { info_t *a; int cnt = 0; if (!ctx) return; for (a = ctx->info_list; a; a = a->next) { Con_Printf("%-20s %s\n", a->name, a->value); cnt++; } Con_DPrintf("%d infos\n", cnt); } //============================================================================ static byte chktbl[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, // Only the first 512 bytes of the table are initialized, the rest // is just zeros. // This is an idiocy in QW but we can't change this, or checksums // will not match. }; /* ==================== COM_BlockSequenceCRCByte For proxy protecting ==================== */ byte COM_BlockSequenceCRCByte (byte *base, int length, int sequence) { unsigned short crc; byte *p; byte chkb[60 + 4]; p = chktbl + ((unsigned int) sequence % (sizeof (chktbl) - 4)); if (length > 60) length = 60; memcpy (chkb, base, length); chkb[length] = (sequence & 0xff) ^ p[0]; chkb[length+1] = p[1]; chkb[length+2] = ((sequence>>8) & 0xff) ^ p[2]; chkb[length+3] = p[3]; length += 4; crc = CRC_Block (chkb, length); crc &= 0xff; return crc; } //============================================================================ static qbool Q_glob_match_after_star (const char *pattern, const char *text) { char c, c1; const char *p = pattern, *t = text; while ((c = *p++) == '?' || c == '*') { if (c == '?' && *t++ == '\0') return false; } if (c == '\0') return true; for (c1 = ((c == '\\') ? *p : c); ; ) { if (tolower(*t) == c1 && Q_glob_match (p - 1, t)) return true; if (*t++ == '\0') return false; } } /* Match a pattern against a string. Based on Vic's Q_WildCmp, which is based on Linux glob_match. Works like glob_match, except that sets ([]) are not supported. A match means the entire string TEXT is used up in matching. In the pattern string, `*' matches any sequence of characters, `?' matches any character. Any other character in the pattern must be matched exactly. To suppress the special syntactic significance of any of `*?\' and match the character exactly, precede it with a `\'. */ qbool Q_glob_match (const char *pattern, const char *text) { char c; while ((c = *pattern++) != '\0') { switch (c) { case '?': if (*text++ == '\0') return false; break; case '\\': if (tolower (*pattern++) != tolower (*text++)) return false; break; case '*': return Q_glob_match_after_star (pattern, text); default: if (tolower (c) != tolower (*text++)) return false; } } return (*text == '\0'); } //============================================================================ /* ========== Com_HashKey ========== */ int Com_HashKey (const char *name) { int v; unsigned char c; v = 0; while ( (c = *name++) != 0 ) v += c &~ 32; // make it case insensitive return v % 32; } //============================================================================ static char q_normalize_chartbl[256]; static qbool q_normalize_chartbl_init; static void Q_normalizetext_Init (void) { int i; for (i = 0; i < 32; i++) q_normalize_chartbl[i] = q_normalize_chartbl[i + 128] = '#'; for (i = 32; i < 128; i++) q_normalize_chartbl[i] = q_normalize_chartbl[i + 128] = i; // special cases q_normalize_chartbl[10] = 10; q_normalize_chartbl[13] = 13; // dot q_normalize_chartbl[5 ] = q_normalize_chartbl[14 ] = q_normalize_chartbl[15 ] = q_normalize_chartbl[28 ] = q_normalize_chartbl[46 ] = '.'; q_normalize_chartbl[5 + 128] = q_normalize_chartbl[14 + 128] = q_normalize_chartbl[15 + 128] = q_normalize_chartbl[28 + 128] = q_normalize_chartbl[46 + 128] = '.'; // numbers for (i = 18; i < 28; i++) q_normalize_chartbl[i] = q_normalize_chartbl[i + 128] = i + 30; // brackets q_normalize_chartbl[16] = q_normalize_chartbl[16 + 128]= '['; q_normalize_chartbl[17] = q_normalize_chartbl[17 + 128] = ']'; q_normalize_chartbl[29] = q_normalize_chartbl[29 + 128] = q_normalize_chartbl[128] = '('; q_normalize_chartbl[31] = q_normalize_chartbl[31 + 128] = q_normalize_chartbl[130] = ')'; // left arrow q_normalize_chartbl[127] = '>'; // right arrow q_normalize_chartbl[141] = '<'; // '=' q_normalize_chartbl[30] = q_normalize_chartbl[129] = q_normalize_chartbl[30 + 128] = '='; q_normalize_chartbl_init = true; } /* ================== Q_normalizetext returns readable extended quake names ================== */ char *Q_normalizetext (char *str) { unsigned char *i; if (!q_normalize_chartbl_init) Q_normalizetext_Init(); for (i = (unsigned char*)str; *i; i++) *i = q_normalize_chartbl[*i]; return str; } /* ================== Q_redtext returns extended quake names ================== */ unsigned char *Q_redtext (unsigned char *str) { unsigned char *i; for (i = str; *i; i++) if (*i > 32 && *i < 128) *i |= 128; return str; } //<- /* ================== Q_yelltext returns extended quake names (yellow numbers) ================== */ unsigned char *Q_yelltext (unsigned char *str) { unsigned char *i; for (i = str; *i; i++) { if (*i >= '0' && *i <= '9') *i += 18 - '0'; else if (*i > 32 && *i < 128) *i |= 128; else if (*i == 13) *i = ' '; } return str; } //===================================================================== // "GPL map" support. If we encounter a map with a known "GPL" CRC, // we fake the CRC so that, on the client side, the CRC of the original // map is transferred to the server, and on the server side, comparison // of clients' CRC is done against the orignal one typedef struct { const char *mapname; int original; int gpl; } csentry_t; static csentry_t table[] = { // CRCs for AquaShark's "simpletextures" maps { "dm1", 0xc5c7dab3, 0x7d37618e }, { "dm2", 0x65f63634, 0x7b337440 }, { "dm3", 0x15e20df8, 0x912781ae }, { "dm4", 0x9c6fe4bf, 0xc374df89 }, { "dm5", 0xb02d48fd, 0x77ca7ce5 }, { "dm6", 0x5208da2b, 0x200c8b5d }, { "end", 0xbbd4b4a5, 0xf89b12ae }, // this is the version with the extra room { NULL, 0, 0 }, }; int Com_TranslateMapChecksum (const char *mapname, int checksum) { csentry_t *p; // Con_Printf ("Map checksum (%s): 0x%x\n", mapname, checksum); for (p = table; p->mapname; p++) if (!strcmp(p->mapname, mapname)) { if (checksum == p->gpl) return p->original; else return checksum; } return checksum; } qbool COM_FileExists (char *path) { FILE *fexists = NULL; // Try opening the file to see if it exists. fexists = fopen(path, "rb"); // The file exists. if (fexists) { // Make sure the file is closed. fclose (fexists); return true; } return false; } int Q_namecmp(const char* s1, const char* s2) { if (s1 == NULL && s2 == NULL) return 0; if (s1 == NULL) return -1; if (s2 == NULL) return 1; while (*s1 || *s2) { if (tolower(*s1 & 0x7f) != tolower(*s2 & 0x7f)) { return tolower(*s1 & 0x7f) - tolower(*s2 & 0x7f); } s1++; s2++; } return 0; } mvdsv-0.35/src/common.h000066400000000000000000000165121427146041000150340ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // common.h -- general definitions #ifndef __COMMON_H__ #define __COMMON_H__ //============================================================================ typedef struct sizebuf_s { qbool allowoverflow; // if false, do a Sys_Error qbool overflowed; // set to true if the buffer size failed byte *data; int maxsize; int cursize; } sizebuf_t; #ifdef SERVERONLY #define MSG_HasOverflowHandler(m) (0) #else #define MSG_HasOverflowHandler(msg) (msg->overflow_handler != NULL) #endif void SZ_Clear (sizebuf_t *buf); void SZ_InitEx (sizebuf_t *buf, byte *data, const int length, qbool allowoverflow); void SZ_Init (sizebuf_t *buf, byte *data, const int length); void *SZ_GetSpace (sizebuf_t *buf, const int length); void SZ_Write (sizebuf_t *buf, const void *data, int length); void SZ_Print (sizebuf_t *buf, const char *data); //============================================================================ #ifdef FTE_PEXT_FLOATCOORDS typedef union { //note: reading from packets can be misaligned int b4; float f; short b2; char b[4]; } coorddata; extern int msg_coordsize; // 2 or 4. extern int msg_anglesize; // 1 or 2. float MSG_FromCoord(coorddata c, int bytes); coorddata MSG_ToCoord(float f, int bytes); //return value should be treated as (char*)&ret; coorddata MSG_ToAngle(float f, int bytes); //return value is NOT byteswapped. #endif extern struct usercmd_s nullcmd; void MSG_WriteChar (sizebuf_t *sb, const int c); void MSG_WriteByte (sizebuf_t *sb, const int c); void MSG_WriteShort (sizebuf_t *sb, const int c); void MSG_WriteLong (sizebuf_t *sb, const int c); void MSG_WriteFloat (sizebuf_t *sb, const float f); void MSG_WriteString (sizebuf_t *sb, const char *s); void MSG_WriteCoord (sizebuf_t *sb, const float f); void MSG_WriteAngle (sizebuf_t *sb, const float f); void MSG_WriteAngle16 (sizebuf_t *sb, const float f); void MSG_WriteLongCoord (sizebuf_t* sb, float f); void MSG_WriteDeltaUsercmd (sizebuf_t *sb, const struct usercmd_s *from, const struct usercmd_s *cmd, unsigned int mvdsv_extensions); extern int msg_readcount; extern qbool msg_badread; // set if a read goes beyond end of message void MSG_BeginReading (void); int MSG_GetReadCount(void); int MSG_ReadChar (void); int MSG_ReadByte (void); int MSG_ReadShort (void); int MSG_ReadLong (void); float MSG_ReadFloat (void); char *MSG_ReadString (void); char *MSG_ReadStringLine (void); float MSG_ReadCoord (void); float MSG_ReadAngle (void); float MSG_ReadAngle16 (void); void MSG_ReadDeltaUsercmd (const struct usercmd_s *from, struct usercmd_s *cmd); void MSG_ReadData (void *data, int len); void MSG_ReadSkip(int bytes); //============================================================================ char *Q_normalizetext (char *name); //bliP: red to white text unsigned char *Q_redtext (unsigned char *str); //bliP: white to red text unsigned char *Q_yelltext (unsigned char *str); //VVD: white to red text and yellow numbers //============================================================================ #define MAX_COM_TOKEN 1024 extern char com_token[MAX_COM_TOKEN]; typedef enum {TTP_UNKNOWN, TTP_STRING} com_tokentype_t; const char *COM_Parse (const char *data); char *COM_ParseToken (const char *data, const char *punctuation); extern int com_argc; extern char **com_argv; int COM_CheckParm (const char *parm); char *COM_Argv (int arg); // range and null checked int COM_Argc (void); void COM_InitArgv (int argc, char **argv); //============================================================ // Alternative variant manipulation with info strings //============================================================ #define INFO_HASHPOOL_SIZE 256 #define MAX_CLIENT_INFOS 128 typedef struct info_s { struct info_s *hash_next; struct info_s *next; char *name; char *value; } info_t; typedef struct ctxinfo_s { info_t *info_hash[INFO_HASHPOOL_SIZE]; info_t *info_list; int cur; // current infos int max; // max infos } ctxinfo_t; // return value for given key char *Info_Get(ctxinfo_t *ctx, const char *name); // set value for given key qbool Info_Set (ctxinfo_t *ctx, const char *name, const char *value); // set value for given star key qbool Info_SetStar (ctxinfo_t *ctx, const char *name, const char *value); // remove given key qbool Info_Remove (ctxinfo_t *ctx, const char *name); // remove all infos void Info_RemoveAll (ctxinfo_t *ctx); // convert old way infostring to new way: \name\qqshka\noaim\1 to hashed variant qbool Info_Convert(ctxinfo_t *ctx, char *str); // convert new way to old way qbool Info_ReverseConvert(ctxinfo_t *ctx, char *str, int size); // copy star keys from ont ctx to other qbool Info_CopyStar(ctxinfo_t *ctx_from, ctxinfo_t *ctx_to); // just print all key value pairs void Info_PrintList(ctxinfo_t *ctx); //============================================================================ //char *Info_KeyNameForKeyNum (char *s, int key); char *Info_ValueForKey (char *s, const char *key); void Info_RemoveKey (char *s, const char *key); void Info_RemovePrefixedKeys (char *start, char prefix); void Info_SetValueForKey (char *s, const char *key, const char *value, unsigned int maxsize); void Info_SetValueForStarKey (char *s, const char *key, const char *value, unsigned int maxsize); void Info_Print (char *s); void Info_CopyStarKeys (const char *from, char *to, unsigned int maxsize); unsigned Com_BlockChecksum (void *buffer, int length); void Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf); byte COM_BlockSequenceCRCByte (byte *base, int length, int sequence); //============================================================================ qbool Q_glob_match (const char *pattern, const char *text); //============================================================================ int Com_HashKey (const char *name); //============================================================================ int Com_TranslateMapChecksum (const char *mapname, int checksum); //============================================================================ // // QTV shared defs between client and server. // typedef enum { QUL_NONE = 0, // QUL_ADD, // user joined QUL_CHANGE, // user changed something like name or something QUL_DEL // user dropped } qtvuserlist_t; typedef struct qtvuser_s { int id; // unique user id char name[MAX_KEY_STRING]; // client name, well must be unique too struct qtvuser_s *next; // next qtvuser_s struct in our list } qtvuser_t; //============================================================================ qbool COM_FileExists(char *path); // Name comparison: case insensitive, red/white text insensitive int Q_namecmp(const char* s1, const char* s2); #endif /* !__COMMON_H__ */ mvdsv-0.35/src/crc.c000066400000000000000000000073411427146041000143060ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ /* crc.c */ #ifdef SERVERONLY #include "qwsvdef.h" #else #include "common.h" #endif #include "crc.h" // this is a 16 bit, non-reflected CRC using the polynomial 0x1021 // and the initial and final xor values shown below... in other words, the // CCITT standard CRC used by XMODEM #define CRC_INIT_VALUE 0xffff #define CRC_XOR_VALUE 0x0000 static unsigned short crctable[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 }; void CRC_Init(unsigned short *crcvalue) { *crcvalue = CRC_INIT_VALUE; } void CRC_ProcessByte(unsigned short *crcvalue, byte data) { *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; } unsigned short CRC_Value(unsigned short crcvalue) { return crcvalue ^ CRC_XOR_VALUE; } unsigned short CRC_Block (byte *start, unsigned int count) { unsigned short crc; CRC_Init (&crc); while (count--) crc = (crc << 8) ^ crctable[(crc >> 8) ^ *start++]; return crc; } void CRC_AddBlock (unsigned short *crcvalue, byte *start, int count) { while (count--) CRC_ProcessByte(crcvalue, *start++); } mvdsv-0.35/src/crc.h000066400000000000000000000020741427146041000143110ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ /* crc.h */ #ifndef __CRC_H__ #define __CRC_H__ void CRC_Init(unsigned short *crcvalue); void CRC_ProcessByte(unsigned short *crcvalue, byte data); unsigned short CRC_Value(unsigned short crcvalue); unsigned short CRC_Block (byte *start, unsigned int count); void CRC_AddBlock (unsigned short *crcvalue, byte *start, int count); #endif /* !__CRC_H__ */ mvdsv-0.35/src/cvar.c000066400000000000000000000235631427146041000144760ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // cvar.c -- dynamic variable tracking #include "qwsvdef.h" static cvar_t *cvar_hash[32]; static cvar_t *cvar_vars; static char *cvar_null_string = ""; /* ============ Cvar_Next Use this to walk through all vars. ============ */ cvar_t *Cvar_Next (cvar_t *var) { if (var) return var->next; else return cvar_vars; } /* ============ Cvar_GetFlags ============ */ int Cvar_GetFlags (cvar_t *var) { return var->flags; } /* ============ Cvar_Find ============ */ cvar_t *Cvar_Find (const char *var_name) { cvar_t *var; int key; key = Com_HashKey (var_name); for (var=cvar_hash[key] ; var ; var=var->hash_next) if (!strcasecmp (var_name, var->name)) return var; return NULL; } /* ============ Cvar_Value ============ */ float Cvar_Value (const char *var_name) { cvar_t *var; var = Cvar_Find (var_name); if (!var) return 0; return Q_atof (var->string); } /* ============ Cvar_String ============ */ char *Cvar_String (const char *var_name) { cvar_t *var; var = Cvar_Find (var_name); if (!var) return cvar_null_string; return var->string; } void SV_SendServerInfoChange(char *key, char *value); char* Cvar_ServerInfoValue(char* key, char* value) { // force serverinfo "0" vars to be "". // meag: deathmatch is a special case as clients default 'not in serverinfo' to non-coop if (!strcmp(value, "0") && strcmp(key, "deathmatch")) { return ""; } return value; } /* ============ Cvar_Set ============ */ void Cvar_Set (cvar_t *var, char *value) { static qbool changing = false; char *tmp; if (!var) return; // force serverinfo "0" vars to be "". if ((var->flags & CVAR_SERVERINFO) && !strcmp(value, "0")) value = ""; if (var->flags & CVAR_ROM) return; if (var->OnChange && !changing) { qbool cancel = false; changing = true; var->OnChange(var, value, &cancel); changing = false; if (cancel) return; // change rejected. } // dup string first (before free) since 'value' and 'var->string' can point at the same memory area. tmp = Q_strdup(value); // free the old value string Q_free (var->string); // assign new value. var->string = tmp; var->value = Q_atof (var->string); if (var->flags & CVAR_SERVERINFO) { SV_ServerinfoChanged (var->name, var->string); } } /* ============ Cvar_SetROM ============ */ void Cvar_SetROM (cvar_t *var, char *value) { int saved_flags; if (!var) return; saved_flags = var->flags; var->flags &= ~CVAR_ROM; Cvar_Set (var, value); var->flags = saved_flags; } /* ============ Cvar_SetByName ============ */ void Cvar_SetByName (const char *var_name, char *value) { cvar_t *var; var = Cvar_Find (var_name); if (!var) { // there is an error in C code if this happens Con_DPrintf ("Cvar_Set: variable %s not found\n", var_name); return; } Cvar_Set (var, value); } /* ============ Cvar_SetValue ============ */ void Cvar_SetValue (cvar_t *var, const float value) { char val[32]; int i; snprintf (val, sizeof (val), "%f", value); for (i = strlen (val) - 1; i > 0 && val[i]=='0'; i--) val[i] = 0; if (val[i] == '.') val[i] = 0; Cvar_Set (var, val); } /* ============ Cvar_SetValueByName ============ */ void Cvar_SetValueByName (const char *var_name, const float value) { char val[32]; snprintf (val, sizeof (val), "%.8g", value); Cvar_SetByName (var_name, val); } /* ============ Cvar_Register Adds a freestanding variable to the variable list. ============ */ void Cvar_Register (cvar_t *variable) { char *value; int key; // first check to see if it has already been defined if (Cvar_Find (variable->name)) { Con_Printf ("Can't register variable %s, already defined\n", variable->name); return; } // check for overlap with a command if (Cmd_Exists (variable->name)) { Con_Printf ("Cvar_Register: %s is a command\n", variable->name); return; } // link the variable in key = Com_HashKey (variable->name); variable->hash_next = cvar_hash[key]; cvar_hash[key] = variable; variable->next = cvar_vars; cvar_vars = variable; // set it through the function to be consistent value = variable->string; variable->string = Q_strdup(""); Cvar_SetROM (variable, value); } /* ============ Cvar_Command Handles variable inspection and changing from the console ============ */ qbool Cvar_Command (void) { int i, c; cvar_t *v; char string[1024]; // check variables v = Cvar_Find (Cmd_Argv(0)); if (!v) return false; // perform a variable print or set c = Cmd_Argc(); if (c == 1) { Con_Printf ("\"%s\" is \"%s\"\n", v->name, v->string); return true; } string[0] = 0; for (i=1 ; i < c ; i++) { if (i > 1) strlcat (string, " ", sizeof(string)); strlcat (string, Cmd_Argv(i), sizeof(string)); } Cvar_Set (v, string); return true; } /* ============= Cvar_Toggle_f ============= */ void Cvar_Toggle_f (void) { cvar_t *var; if (Cmd_Argc() != 2) { Con_Printf ("toggle : toggle a cvar on/off\n"); return; } var = Cvar_Find (Cmd_Argv(1)); if (!var) { Con_Printf ("Unknown variable \"%s\"\n", Cmd_Argv(1)); return; } Cvar_Set (var, var->value ? "0" : "1"); } /* =============== Cvar_CvarList_f =============== List all cvars TODO: allow cvar name mask as a parameter, e.g. cvarlist cl_* */ static int Cvar_CvarCompare (const void *p1, const void *p2) { return strcmp ((*((cvar_t **) p1))->name, (*((cvar_t **) p2))->name); } static void Cvar_CvarList_f(void) { cvar_t **sorted_cvars = NULL; cvar_t *var; const char *pattern; int i = 0; int num_cvars = 0; int pattern_matched = 0; pattern = (Cmd_Argc() > 1) ? Cmd_Argv(1) : NULL; for (var = cvar_vars; var; var = var->next) { num_cvars++; } if (num_cvars > 0) { sorted_cvars = malloc(num_cvars * sizeof(cvar_t*)); if (!sorted_cvars) { Sys_Error("Failed to allocate memory"); } } else { Con_Printf("No cvars found\n"); return; } for (var = cvar_vars; var; var = var->next) { sorted_cvars[i++] = var; } qsort(sorted_cvars, num_cvars, sizeof(cvar_t *), Cvar_CvarCompare); Con_Printf("List of cvars:\n"); for (i = 0; i < num_cvars; i++) { var = sorted_cvars[i]; if (pattern && !Q_glob_match(pattern, var->name)) { continue; } Con_Printf("%c %s %s\n", var->flags & CVAR_SERVERINFO ? 's' : ' ', var->name, var->string); pattern_matched++; } Con_Printf("------------\n%d/%d %svariables\n", pattern_matched, num_cvars, (pattern) ? "matching " : ""); free(sorted_cvars); } /* =========== Cvar_Create =========== */ cvar_t *Cvar_Create (const char *name, char *string, int cvarflags) { cvar_t *v; int key; v = Cvar_Find(name); if (v) return v; v = (cvar_t *) Q_malloc (sizeof(cvar_t)); // Cvar doesn't exist, so we create it v->next = cvar_vars; cvar_vars = v; key = Com_HashKey (name); v->hash_next = cvar_hash[key]; cvar_hash[key] = v; v->name = (char *) Q_strdup (name); v->string = (char *) Q_strdup (string); v->flags = cvarflags; v->value = Q_atof (v->string); v->OnChange = NULL; return v; } /* =========== Cvar_Delete =========== returns true if the cvar was found (and deleted) */ qbool Cvar_Delete (const char *name) { cvar_t *var, *prev; int key; key = Com_HashKey (name); prev = NULL; for (var = cvar_hash[key] ; var ; var=var->hash_next) { if (!strcasecmp(var->name, name)) { // unlink from hash if (prev) prev->hash_next = var->next; else cvar_hash[key] = var->next; break; } prev = var; } if (!var) return false; prev = NULL; for (var = cvar_vars ; var ; var=var->next) { if (!strcasecmp(var->name, name)) { // unlink from cvar list if (prev) prev->next = var->next; else cvar_vars = var->next; // free Q_free (var->string); Q_free (var->name); Q_free (var); return true; } prev = var; } Sys_Error ("Cvar list broken"); return false; // shut up compiler } //DP_CON_SET void Cvar_Set_f (void) { cvar_t *var; char *var_name; if (Cmd_Argc() != 3) { Con_Printf ("usage: set \n"); return; } var_name = Cmd_Argv (1); var = Cvar_Find (var_name); if (var) { Cvar_Set (var, Cmd_Argv(2)); } else { if (Cmd_Exists(var_name)) { Con_Printf ("\"%s\" is a command\n", var_name); return; } #if 0 // delete alias with the same name if it exists Cmd_DeleteAlias (var_name); #endif var = Cvar_Create (var_name, Cmd_Argv(2), CVAR_USER_CREATED); } } void Cvar_Inc_f (void) { int c; cvar_t *var; float delta; c = Cmd_Argc(); if (c != 2 && c != 3) { Con_Printf ("inc [value]\n"); return; } var = Cvar_Find (Cmd_Argv(1)); if (!var) { Con_Printf ("Unknown variable \"%s\"\n", Cmd_Argv(1)); return; } if (c == 3) delta = Q_atof (Cmd_Argv(2)); else delta = 1; Cvar_SetValue (var, var->value + delta); } //#define CVAR_DEBUG #ifdef CVAR_DEBUG static void Cvar_Hash_Print_f (void) { int i, count; cvar_t *cvar; Con_Printf ("Cvar hash:\n"); for (i = 0; i<32; i++) { count = 0; for (cvar = cvar_hash[i]; cvar; cvar=cvar->hash_next, count++); Con_Printf ("%i: %i\n", i, count); } } #endif void Cvar_Init (void) { Cmd_AddCommand ("cvarlist", Cvar_CvarList_f); Cmd_AddCommand ("cvardump", Cvar_CvarList_f); Cmd_AddCommand ("toggle", Cvar_Toggle_f); Cmd_AddCommand ("set", Cvar_Set_f); //DP_CON_SET Cmd_AddCommand ("inc", Cvar_Inc_f); #ifdef CVAR_DEBUG Cmd_AddCommand ("cvar_hash_print", Cvar_Hash_Print_f); #endif } mvdsv-0.35/src/cvar.h000066400000000000000000000077121427146041000145010ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // cvar.h #ifndef __CVAR_H__ #define __CVAR_H__ /* cvar_t variables are used to hold scalar or string variables that can be changed or displayed at the console or prog code as well as accessed directly in C code. it is sufficient to initialize a cvar_t with just the first two fields, or you can add a ,true flag for variables that you want saved to the configuration file when the game is quit: cvar_t r_draworder = {"r_draworder","1"}; cvar_t scr_screensize = {"screensize","1",CVAR_ARCHIVE}; Cvars must be registered before use, or they will have a 0 value instead of the float interpretation of the string. Generally, all cvar_t declarations should be registered in the apropriate init function before any console commands are executed: Cvar_Register (&host_framerate); C code usually just references a cvar in place: if ( r_draworder.value ) It could optionally ask for the value to be looked up for a string name: if (Cvar_Value ("r_draworder")) Interpreted prog code can access cvars with the cvar(name) or cvar_set (name, value) internal functions: teamplay = cvar("teamplay"); cvar_set ("registered", "1"); The user can access cvars from the console in two ways: r_draworder prints the current value r_draworder 0 sets the current value to 0 Cvars are restricted from having the same names as commands to keep this interface from being ambiguous. */ // cvar flags #define CVAR_NONE (0) #define CVAR_SERVERINFO (1<<0) // mirrored to serverinfo #define CVAR_ROM (1<<1) // read only #define CVAR_USER_CREATED (1<<2) // created by a set command typedef struct cvar_s { char *name; char *string; int flags; void (*OnChange) (struct cvar_s *var, char *value, qbool *cancel); float value; struct cvar_s *hash_next; struct cvar_s *next; } cvar_t; #define Cvar_SetCurrentGroup(...) // ezquake compatibility #define Cvar_ResetCurrentGroup(...) // ezquake compatibility void Cvar_Register (cvar_t *variable); // registers a cvar that already has the name, string, and optionally the // archive elements set. void Cvar_Set (cvar_t *var, char *value); // equivalent to " " typed at the console void Cvar_SetROM (cvar_t *var, char *value); // force a set even if the cvar is read only void Cvar_SetByName (const char *var_name, char *value); // equivalent to " " typed at the console void Cvar_SetValue (cvar_t *var, const float value); // expands value to a string and calls Cvar_Set void Cvar_SetValueByName (const char *var_name, const float value); // expands value to a string and calls Cvar_Set float Cvar_Value (const char *var_name); // returns 0 if not defined or non numeric char *Cvar_String (const char *var_name); // returns an empty string if not defined qbool Cvar_Command (void); // called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known // command. Returns true if the command was a variable reference that // was handled. (print or change) // Use this to walk through all vars cvar_t *Cvar_Next (cvar_t *var); int Cvar_GetFlags (cvar_t *var); cvar_t *Cvar_Find (const char *var_name); qbool Cvar_Delete (const char *name); cvar_t *Cvar_Create (const char *name, char *string, int cvarflags); void Cvar_Init (void); char* Cvar_ServerInfoValue(char* key, char* value); #endif /* !__CVAR_H__ */ mvdsv-0.35/src/fs.c000066400000000000000000000534701427146041000141530ustar00rootroot00000000000000/* 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 "qwsvdef.h" /* All of Quake's data access is through a hierchal file system, but the contents of the file system can be transparently merged from several sources. The "base directory" is the path to the directory holding the quake.exe and all game directories. The sys_* files pass this to host_init in quakeparms_t->basedir. This can be overridden with the "-basedir" command line parm to allow code debugging in a different directory. The base directory is only used during filesystem initialization. The "game directory" is the first tree on the search path and directory that all generated files (savegames, screenshots, demos, config files) will be saved to. This can be overridden with the "-game" command line parameter. The game directory can never be changed while quake is executing. This is a precaution against having a malicious server instruct clients to write files over areas they shouldn't. */ typedef enum { FS_LOAD_NONE = 1, FS_LOAD_FILE_PAK = 2, FS_LOAD_FILE_ALL = FS_LOAD_FILE_PAK } FS_Load_File_Types; /* ============================================================================= VARIABLES ============================================================================= */ // WARNING: if you add some FS related global variable // then made appropriate change to FS_ShutDown() too, if required. static char fs_basedir[MAX_OSPATH]; // c:/quake char fs_gamedir[MAX_OSPATH]; // c:/quake/qw static char fs_gamedirfile[MAX_QPATH]; // qw tf ctf and etc. In other words single dir name without path static searchpath_t *fs_searchpaths = NULL; static searchpath_t *fs_base_searchpaths = NULL; // without gamedirs hashtable_t *filesystemhash = NULL; qbool filesystemchanged = true; int fs_hash_dups = 0; int fs_hash_files = 0; cvar_t fs_cache = {"fs_cache", "1"}; /* ============================================================================= FORWARD DEFINITION ============================================================================= */ static searchpath_t *FS_AddPathHandle(char *probablepath, searchpathfuncs_t *funcs, void *handle, qbool copyprotect, qbool istemporary, FS_Load_File_Types loadstuff); /* ============================================================================= COMMANDS ============================================================================= */ /* ============ FS_Path_f ============ */ static void FS_Path_f (void) { searchpath_t *search; Con_Printf ("Current search path:\n"); for (search = fs_searchpaths; search ; search = search->next) { if (search == fs_base_searchpaths) Con_Printf ("----------\n"); search->funcs->PrintPath(search->handle); } } /* ============================================================================= FUNCTIONS ============================================================================= */ /* ================ FS_GetCleanPath ================ */ static const char *FS_GetCleanPath(const char *pattern, char *outbuf, int outlen) { char *s; if (strchr(pattern, '\\')) { strlcpy(outbuf, pattern, outlen); pattern = outbuf; Con_Printf("Warning: \\ characters in filename %s\n", pattern); for (s = (char*)pattern; (s = strchr(s, '\\')); s++) *s = '/'; } if (*pattern == '/' || strstr(pattern, "../") || strstr(pattern, "..\\") || strstr(pattern, ":")) Con_Printf("Error: absolute path in filename %s\n", pattern); else return pattern; return NULL; } /* ============ FS_FlushFSHash Flush FS hash and mark FS as changed, so FS_FLocateFile() will be forced to call FS_RebuildFSHash(). ============ */ void FS_FlushFSHash(void) { if (filesystemhash) { Hash_Flush(filesystemhash); } filesystemchanged = true; } /* ============ FS_RebuildFSHash Rebuild FS hash. ============ */ static void FS_RebuildFSHash(void) { searchpath_t *search; if (!filesystemhash) { filesystemhash = Hash_InitTable(1024); } else { FS_FlushFSHash(); } fs_hash_dups = 0; fs_hash_files = 0; for (search = fs_searchpaths ; search ; search = search->next) { search->funcs->BuildHash(search->handle); } filesystemchanged = false; Con_DPrintf("FS_RebuildFSHash: %i unique files, %i duplicates\n", fs_hash_files, fs_hash_dups); } /* ============ FS_FLocateFile Finds the file in the search path. Look FSLF_ReturnType_e definition so you know that it returns. ============ */ int FS_FLocateFile(const char *filename, FSLF_ReturnType_e returntype, flocation_t *loc) { int depth = 0; int len; searchpath_t *search; char cleanpath[MAX_OSPATH]; void *pf = NULL; filename = FS_GetCleanPath(filename, cleanpath, sizeof(cleanpath)); if (!filename) goto fail; if (fs_cache.value) { if (filesystemchanged) FS_RebuildFSHash(); pf = Hash_GetInsensitive(filesystemhash, filename); if (!pf) goto fail; } // // search through the path, one element at a time. // for (search = fs_searchpaths ; search ; search = search->next) { if (search->funcs->FindFile(search->handle, loc, filename, pf)) { if (loc) { loc->search = search; len = loc->len; } else { len = 0; } goto out; } depth += (search->funcs == &osfilefuncs || returntype == FSLFRT_DEPTH_ANYPATH); } fail: if (loc) loc->search = NULL; depth = 0x7fffffff; // NOTE: weird, we return it on fail in some cases, may cause mistakes by user. len = -1; out: /* Debug printing removed * if (len>=0) { if (loc) Con_Printf("Found %s:%i\n", loc->rawname, loc->len); else Con_Printf("Found %s\n", filename); } else Con_Printf("Failed\n"); */ if (returntype == FSLFRT_IFFOUND) return len != -1; else if (returntype == FSLFRT_LENGTH) return len; else return depth; } // internal struct, no point to expose it outside. typedef struct { searchpathfuncs_t *funcs; searchpath_t *parentpath; char *parentdesc; } wildpaks_t; /* ================ FS_AddWildDataFiles Add pack files masked by wildcards, e.g. '*.pak' . That allow add not only packs named like pak0.pak pak1.pak but with random names too. ================ */ static int FS_AddWildDataFiles (char *descriptor, int size, void *vparam) { wildpaks_t *param = vparam; vfsfile_t *vfs; searchpathfuncs_t *funcs = param->funcs; searchpath_t *search; void *pak; char pakfile[MAX_OSPATH]; flocation_t loc = {0}; snprintf (pakfile, sizeof (pakfile), "%s%s", param->parentdesc, descriptor); for (search = fs_searchpaths; search; search = search->next) { if (search->funcs != funcs) continue; if (!strcasecmp((char*)search->handle, pakfile)) //assumption: first member of structure is a char array return true; //already loaded (base paths?) } search = param->parentpath; if (!search->funcs->FindFile(search->handle, &loc, descriptor, NULL)) return true; //not found.. vfs = search->funcs->OpenVFS(search->handle, &loc, "rb"); if (!vfs) return true; pak = funcs->OpenNew (vfs, pakfile); if (!pak) { VFS_CLOSE(vfs); return true; } snprintf (pakfile, sizeof (pakfile), "%s%s/", param->parentdesc, descriptor); FS_AddPathHandle(pakfile, funcs, pak, true, false, FS_LOAD_FILE_ALL); return true; } /* ================ FS_AddDataFiles Load pack files. ================ */ static void FS_AddDataFiles(char *pathto, searchpath_t *parent, char *extension, searchpathfuncs_t *funcs) { int i; char pakfile[MAX_OSPATH]; wildpaks_t wp = {0}; // first load all the numbered pak files. for (i = 0; ; i++) { void *handle; vfsfile_t *vfs; flocation_t loc = {0}; snprintf (pakfile, sizeof(pakfile), "pak%i.%s", i, extension); if (!parent->funcs->FindFile(parent->handle, &loc, pakfile, NULL)) break; //not found.. snprintf (pakfile, sizeof(pakfile), "%spak%i.%s", pathto, i, extension); vfs = parent->funcs->OpenVFS(parent->handle, &loc, "rb"); if (!vfs) break; handle = funcs->OpenNew (vfs, pakfile); if (!handle) { VFS_CLOSE(vfs); break; } snprintf (pakfile, sizeof(pakfile), "%spak%i.%s/", pathto, i, extension); FS_AddPathHandle(pakfile, funcs, handle, true, false, FS_LOAD_FILE_ALL); } // now load the random ones. snprintf (pakfile, sizeof (pakfile), "*.%s", extension); wp.funcs = funcs; wp.parentdesc = pathto; wp.parentpath = parent; parent->funcs->EnumerateFiles(parent->handle, pakfile, FS_AddWildDataFiles, &wp); } /* ================ FS_AddPathHandle Adds searchpath and load pack files in it if requested. ================ */ static searchpath_t *FS_AddPathHandle(char *probablepath, searchpathfuncs_t *funcs, void *handle, qbool copyprotect, qbool istemporary, FS_Load_File_Types loadstuff) { searchpath_t *search; // allocate new search path and init it. search = (searchpath_t*)Q_malloc (sizeof(searchpath_t)); search->copyprotected = copyprotect; search->istemporary = istemporary; search->handle = handle; search->funcs = funcs; // link seach path in. search->next = fs_searchpaths; fs_searchpaths = search; // mark file system is changed. filesystemchanged = true; // add any data files too. if (loadstuff & FS_LOAD_FILE_PAK) FS_AddDataFiles(probablepath, search, "pak", &packfilefuncs);//q1/hl/h2/q2 return search; } /* ================ FS_AddGameDirectory Sets fs_gamedir, adds the directory to the head of the path, then loads and adds pak0.pak pak1.pak ... ================ */ static void FS_AddGameDirectory (char *dir, FS_Load_File_Types loadstuff) { char *p; searchpath_t *search; if ((p = strrchr(dir, '/'))) strlcpy (fs_gamedirfile, ++p, sizeof (fs_gamedirfile)); else strlcpy (fs_gamedirfile, dir, sizeof (fs_gamedirfile)); strlcpy (fs_gamedir, dir, sizeof (fs_gamedir)); for (search = fs_searchpaths; search; search = search->next) { if (search->funcs != &osfilefuncs) continue; // ignore packs and such. if (!strcasecmp(search->handle, fs_gamedir)) return; //already loaded (base paths?) } // add the directory to the search path FS_AddPathHandle (va("%s/", dir), &osfilefuncs, Q_strdup(dir), false, false, loadstuff); } /* ============ FS_SetGamedir Sets the gamedir and path to a different directory. That is basically handy wrapper around FS_AddGameDirectory() for "gamedir" command. ============ */ void FS_SetGamedir (char *dir, qbool force) { if (strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\") || strstr(dir, ":")) { Con_Printf ("Gamedir should be a single filename, not a path\n"); return; } if (!force && !strcmp(fs_gamedirfile, dir)) return; // Still the same, unless we forced. // FIXME: do we need it? since it will be set in FS_AddGameDirectory(). strlcpy (fs_gamedirfile, dir, sizeof(fs_gamedirfile)); // Free up any current game dir info. FS_FlushFSHash(); // free up any current game dir info while (fs_searchpaths != fs_base_searchpaths) { searchpath_t *next; fs_searchpaths->funcs->ClosePath(fs_searchpaths->handle); next = fs_searchpaths->next; Q_free (fs_searchpaths); fs_searchpaths = next; } // mark file system is changed. filesystemchanged = true; // Flush all data, so it will be forced to reload. // well, mvdsv does not use cache anyway. // Cache_Flush (); // FIXME: do we need it? since it will be set in FS_AddGameDirectory(). snprintf (fs_gamedir, sizeof (fs_gamedir), "%s/%s", fs_basedir, dir); FS_AddGameDirectory(va("%s/%s", fs_basedir, dir), FS_LOAD_FILE_ALL); } /* ================ FS_InitModule Add commands and cvars. ================ */ void FS_InitModule(void) { Cmd_AddCommand ("path", FS_Path_f); Cvar_Register(&fs_cache); } /* ================ FS_Init ================ */ void FS_InitEx(void) { int i; char *s; FS_ShutDown(); if ((i = COM_CheckParm ("-basedir")) && i < COM_Argc() - 1) { // -basedir // Overrides the system supplied base directory (under id1) strlcpy (fs_basedir, COM_Argv(i + 1), sizeof(fs_basedir)); } else { #if 0 // FIXME: made fs_basedir equal to cwd Sys_getcwd(fs_basedir, sizeof(fs_basedir) - 1); #else strlcpy (fs_basedir, ".", sizeof(fs_basedir)); #endif } // replace backslahes with slashes. for (s = fs_basedir; (s = strchr(s, '\\')); s++) *s = '/'; // remove terminating slash if any. i = (int)strlen(fs_basedir) - 1; if (i >= 0 && fs_basedir[i] == '/') fs_basedir[i] = 0; // start up with id1 by default FS_AddGameDirectory(va("%s/%s", fs_basedir, "id1"), FS_LOAD_FILE_ALL); FS_AddGameDirectory(va("%s/%s", fs_basedir, "qw"), FS_LOAD_FILE_ALL); // any set gamedirs will be freed up to here fs_base_searchpaths = fs_searchpaths; // the user might want to override default game directory if (!(i = COM_CheckParm ("-game"))) i = COM_CheckParm ("+gamedir"); if (i && i < COM_Argc() - 1) { FS_SetGamedir (COM_Argv(i + 1), true); // FIXME: move in FS_SetGamedir() instead!!! Info_SetValueForStarKey (svs.info, "*gamedir", COM_Argv(i + 1), MAX_SERVERINFO_STRING); } } /* ================ FS_Init ================ */ void FS_Init( void ) { FS_InitModule(); // init commands and variables. FS_InitEx(); } /* ================ FS_ShutDown ================ */ void FS_ShutDown( void ) { // free data while (fs_searchpaths) { searchpath_t *next = fs_searchpaths->next; Q_free (fs_searchpaths); // FIXME: close handles and such!!! fs_searchpaths = next; } // flush all data, so it will be forced to reload // well, mvdsv does not use cache anyway. // Cache_Flush (); // reset globals fs_base_searchpaths = fs_searchpaths = NULL; fs_gamedir[0] = 0; fs_gamedirfile[0] = 0; fs_basedir[0] = 0; // FIXME: //hashtable_t *filesystemhash = NULL; //qbool filesystemchanged = true; //int fs_hash_dups = 0; //int fs_hash_files = 0; } /* ================ FS_OpenVFS This should be how all files are opened. ================ */ vfsfile_t *FS_OpenVFS(const char *filename, char *mode, relativeto_t relativeto) { flocation_t loc = {0}; vfsfile_t *vfs = NULL; char cleanname[MAX_OSPATH]; char fullname[MAX_OSPATH]; //blanket-bans filename = FS_GetCleanPath(filename, cleanname, sizeof(cleanname)); if (!filename) return NULL; if (strcmp(mode, "rb")) if (strcmp(mode, "wb")) if (strcmp(mode, "ab")) return NULL; //urm, unable to write/append /* General opening of files */ switch (relativeto) { case FS_NONE_OS: //OS access only, no paks, open file as is snprintf(fullname, sizeof(fullname), "%s", filename); return VFSOS_Open(fullname, mode); case FS_GAME_OS: //OS access only, no paks snprintf(fullname, sizeof(fullname), "%s/%s/%s", fs_basedir, fs_gamedirfile, filename); if (strchr(mode, 'w') || strchr(mode, 'a')) FS_CreatePath(fullname); // FIXME: It should be moved to VFSOS_Open() itself? return VFSOS_Open(fullname, mode); case FS_GAME: // snprintf(fullname, sizeof(fullname), "%s/%s/%s", fs_basedir, fs_gamedirfile, filename); // That an error attempt to write with FS_GAME, since file can be in pack file. redirect. if (strchr(mode, 'w') || strchr(mode, 'a')) return FS_OpenVFS(filename, mode, FS_GAME_OS); // Search on path, try to open if found. if (FS_FLocateFile(filename, FSLFRT_IFFOUND, &loc)) return loc.search->funcs->OpenVFS(loc.search->handle, &loc, mode); return NULL; case FS_BASE_OS: //OS access only, no paks snprintf(fullname, sizeof(fullname), "%s/%s", fs_basedir, filename); return VFSOS_Open(fullname, mode); case FS_ANY: // That an error attempt to write with FS_ANY, since file can be in pack file. vfs = FS_OpenVFS(filename, mode, FS_GAME); if (vfs) return vfs; vfs = FS_OpenVFS(filename, mode, FS_NONE_OS); if (vfs) return vfs; return NULL; default: Sys_Error("FS_OpenVFS: Bad relative path (%i)", relativeto); break; } return NULL; } //======================================================================= void VFS_CHECKCALL (struct vfsfile_s *vf, void *fld, char *emsg) { if (!fld) Sys_Error("%s", emsg); } void VFS_CLOSE (struct vfsfile_s *vf) { assert(vf); VFS_CHECKCALL(vf, vf->Close, "VFS_CLOSE"); vf->Close(vf); } unsigned long VFS_TELL (struct vfsfile_s *vf) { assert(vf); VFS_CHECKCALL(vf, vf->Tell, "VFS_TELL"); return vf->Tell(vf); } unsigned long VFS_GETLEN (struct vfsfile_s *vf) { assert(vf); VFS_CHECKCALL(vf, vf->GetLen, "VFS_GETLEN"); return vf->GetLen(vf); } /** * VFS_SEEK() reposition a stream * If whence is set to SEEK_SET, SEEK_CUR, or SEEK_END, the offset is * relative to the start of the file, the current position indicator, or * end-of-file, respectively. * Return Value * Upon successful completion, VFS_SEEK(), returns 0. * Otherwise, -1 is returned */ int VFS_SEEK (struct vfsfile_s *vf, unsigned long pos, int whence) { assert(vf); VFS_CHECKCALL(vf, vf->Seek, "VFS_SEEK"); return vf->Seek(vf, pos, whence); } int VFS_READ (struct vfsfile_s *vf, void *buffer, int bytestoread, vfserrno_t *err) { assert(vf); VFS_CHECKCALL(vf, vf->ReadBytes, "VFS_READ"); return vf->ReadBytes(vf, buffer, bytestoread, err); } int VFS_WRITE (struct vfsfile_s *vf, const void *buffer, int bytestowrite) { assert(vf); VFS_CHECKCALL(vf, vf->WriteBytes, "VFS_WRITE"); return vf->WriteBytes(vf, buffer, bytestowrite); } void VFS_FLUSH (struct vfsfile_s *vf) { assert(vf); if(vf->Flush) vf->Flush(vf); } // return null terminated string char *VFS_GETS(struct vfsfile_s *vf, char *buffer, int buflen) { char in; char *out = buffer; int len = buflen-1; assert(vf); VFS_CHECKCALL(vf, vf->ReadBytes, "VFS_GETS"); // if (len == 0) // return NULL; // FIXME: I am not sure how to handle this better if (len <= 0) Sys_Error("VFS_GETS: len <= 0"); while (len > 0) { if (!VFS_READ(vf, &in, 1, NULL)) { if (len == buflen-1) return NULL; *out = '\0'; return buffer; } if (in == '\n') break; *out++ = in; len--; } *out = '\0'; return buffer; } qbool VFS_COPYPROTECTED(struct vfsfile_s *vf) { assert(vf); return vf->copyprotected; } /* ============================================================================= LEGACY FUNCTIONS ============================================================================= */ /* ================ FS_FileLength ================ */ long FS_FileLength (FILE *f) { long pos, end; pos = ftell (f); fseek (f, 0, SEEK_END); end = ftell (f); fseek (f, pos, SEEK_SET); return end; } /* ============ FS_FileBase ============ */ void FS_FileBase (char *in, char *out) { char *begin, *end; int len; if (!(end = strrchr (in, '.'))) end = in + strlen (in); if (!(begin = strchr (in, '/'))) begin = in; else begin++; len = end - begin + 1; if (len < 1) strlcpy (out, "?model?", 8); else strlcpy (out, begin, min (len, MAX_OSPATH)); } /* ============ FS_WriteFile The filename will be prefixed by the current game directory ============ */ void FS_WriteFile (char *filename, void *data, int len) { FILE *f; char name[MAX_OSPATH]; snprintf (name, MAX_OSPATH, "%s/%s", fs_gamedir, filename); f = fopen (name, "wb"); if (!f) { Sys_mkdir (fs_gamedir); f = fopen (name, "wb"); if (!f) Sys_Error ("Error opening %s", filename); } Sys_Printf ("FS_WriteFile: %s\n", name); fwrite (data, 1, len, f); fclose (f); } /* ============ FS_CreatePath Only used for CopyFile and download ============ */ void FS_CreatePath(char *path) { char *s, save; if (!*path) return; for (s = path + 1; *s; s++) { #ifdef _WIN32 if (*s == '/' || *s == '\\') { #else if (*s == '/') { #endif save = *s; *s = 0; Sys_mkdir(path); *s = save; } } } /* ============ FS_LoadFile Filename are relative to the quake directory. Always appends a 0 byte to the loaded data. ============ */ static byte *FS_LoadFile (char *path, void *allocator, int *file_length) { vfsfile_t *f = NULL; vfserrno_t err; flocation_t loc = {0}; byte *buf; int len; // Look for it in the filesystem or pack files. FS_FLocateFile(path, FSLFRT_LENGTH, &loc); if (loc.search) { f = loc.search->funcs->OpenVFS(loc.search->handle, &loc, "rb"); } if (!f) return NULL; // FIXME: should we use loc.len instead? len = VFS_GETLEN(f); if (file_length) *file_length = len; if (allocator == Hunk_AllocName) { char base[32]; // Extract the filename base name for hunk tag. FS_FileBase (path, base); buf = (byte *) Hunk_AllocName (len + 1, base); } else if (allocator == Hunk_TempAlloc) { buf = (byte *) Hunk_TempAlloc (len + 1); } #if 0 else if (allocator == Q_malloc) { buf = Q_malloc (len + 1); } #endif else { Sys_Error ("FS_LoadFile: bad usehunk\n"); return NULL; } if (!buf) { Sys_Error ("FS_LoadFile: not enough space for %s\n", path); return NULL; } buf[len] = 0; VFS_READ(f, buf, len, &err); VFS_CLOSE(f); return buf; } byte *FS_LoadHunkFile (char *path, int *len) { return FS_LoadFile (path, Hunk_AllocName, len); } byte *FS_LoadTempFile (char *path, int *len) { return FS_LoadFile (path, Hunk_TempAlloc, len); } /* ============ FS_NextPath Iterate along searchpaths (no packs). ============ */ char *FS_NextPath (char *prevpath) { searchpath_t *s; char *prev; if (!prevpath) return fs_gamedir; prev = fs_gamedir; for (s = fs_searchpaths; s ; s = s->next) { if (s->funcs != &osfilefuncs) continue; if (prevpath == prev) return s->handle; prev = s->handle; } return NULL; } /* =========== FS_UnsafeFilename Returns true if user-specified path is unsafe =========== */ qbool FS_UnsafeFilename(const char* fileName) { return !fileName || !*fileName || // invalid name. fileName[1] == ':' || // dos filename absolute path specified - reject. *fileName == '\\' || *fileName == '/' || // absolute path was given - reject. strstr(fileName, ".."); } mvdsv-0.35/src/fs.h000066400000000000000000000031371427146041000141530ustar00rootroot00000000000000/* 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. */ #ifndef __FS_H__ #define __FS_H__ /* ============================================================================= VARIABLES ============================================================================= */ extern char fs_gamedir[MAX_OSPATH]; /* ============================================================================= FUNCTION PROTOTYPES ============================================================================= */ void FS_Init (void); void FS_ShutDown(void); long FS_FileLength (FILE *f); void FS_FileBase (char *in, char *out); #define COM_FileBase FS_FileBase // ezquake compatibility void FS_WriteFile (char *filename, void *data, int len); byte *FS_LoadTempFile (char *path, int *len); byte *FS_LoadHunkFile (char *path, int *len); void FS_CreatePath (char *path); char *FS_NextPath (char *prevpath); void FS_SetGamedir (char *dir, qbool force); qbool FS_UnsafeFilename(const char* name); #endif /* !__FS_H__ */ mvdsv-0.35/src/g_public.h000066400000000000000000000150771427146041000153350ustar00rootroot00000000000000/* * QW262 * Copyright (C) 2004 [sd] angel * * This code is based on Q3 VM code by Id Software, Inc. * * * 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 * * */ #ifndef __G_PUBLIC_H__ #define __G_PUBLIC_H__ // Copyright (C) 1999-2000 Id Software, Inc. // // g_public.h -- game module information visible to server #define GAME_API_VERSION 16 /* * Changes in GAME_API_VERSION 16: * - server edict data removed from game edict: typedef struct shared_edict_s { entvars_t v;} edict_t; * - SetSting works for PR1 only * - ported VM from Q3 * - QVM mods should get mapname and client netnames with infokey trap * * mod should get client netname in GAME_CLIENT_CONNECT call: * self = PROG_TO_EDICT(g_globalvars.self); * self->s.v.netname = netnames[NUM_FOR_EDICT(self)-1]; * infokey( self, "netname", self->s.v.netname, 32); * * mod should get mapname in GAME_START_FRAME call: * if (framecount == 0) * { * infokey(world, "mapname", mapname, sizeof(mapname)); * infokey(world, "modelname", worldmodel, sizeof(worldmodel)); * world->model = worldmodel; * } * * infokey( world, "mapname", mapname, sizeof(mapname) ); * * - QVM GAME_CLIENT_USERINFO_CHANGED call now have integer paramater * * called with 0 before changing, with 1 after changing * mod must update client netname in call with param 1 and key = "name" */ //=============================================================== // !!! new traps comes to end of list !!! // // system traps provided by the main engine // typedef enum { //============== general Quake services ================== G_GETAPIVERSION, // ( void); G_DPRINT, // ( const char *string ); // print message on the local console G_ERROR, // ( const char *string ); // abort the game G_GetEntityToken, G_SPAWN_ENT, G_REMOVE_ENT, G_PRECACHE_SOUND, G_PRECACHE_MODEL, G_LIGHTSTYLE, G_SETORIGIN, G_SETSIZE, G_SETMODEL, G_BPRINT, G_SPRINT, G_CENTERPRINT, G_AMBIENTSOUND, G_SOUND, G_TRACELINE, G_CHECKCLIENT, G_STUFFCMD, G_LOCALCMD, G_CVAR, G_CVAR_SET, G_FINDRADIUS, G_WALKMOVE, G_DROPTOFLOOR, G_CHECKBOTTOM, G_POINTCONTENTS, G_NEXTENT, G_AIM, G_MAKESTATIC, G_SETSPAWNPARAMS, G_CHANGELEVEL, G_LOGFRAG, G_GETINFOKEY, G_MULTICAST, G_DISABLEUPDATES, G_WRITEBYTE, G_WRITECHAR, G_WRITESHORT, G_WRITELONG, G_WRITEANGLE, G_WRITECOORD, G_WRITESTRING, G_WRITEENTITY, G_FLUSHSIGNON, g_memset, g_memcpy, g_strncpy, g_sin, g_cos, g_atan2, g_sqrt, g_floor, g_ceil, g_acos, G_CMD_ARGC, G_CMD_ARGV, G_TraceCapsule, G_FSOpenFile, G_FSCloseFile, G_FSReadFile, G_FSWriteFile, G_FSSeekFile, G_FSTellFile, G_FSGetFileList, G_CVAR_SET_FLOAT, G_CVAR_STRING, G_Map_Extension, G_strcmp, G_strncmp, G_stricmp, G_strnicmp, G_Find, G_executecmd, G_conprint, G_readcmd, G_redirectcmd, G_Add_Bot, G_Remove_Bot, G_SetBotUserInfo, G_SetBotCMD, G_QVMstrftime, G_CMD_ARGS, G_CMD_TOKENIZE, g_strlcpy, g_strlcat, G_MAKEVECTORS, G_NEXTCLIENT, G_PRECACHE_VWEP_MODEL, G_SETPAUSE, G_SETUSERINFO, G_MOVETOGOAL, G_VISIBLETO, _G__LASTAPI } gameImport_t; // !!! new things comes to end of list !!! // // functions exported by the game subsystem // typedef enum { GAME_INIT, // ( int levelTime, int randomSeed, int restart ); // init and shutdown will be called every single level // The game should call G_GET_ENTITY_TOKEN to parse through all the // entity configuration text and spawn gentities. GAME_LOADENTS, GAME_SHUTDOWN, // (void); GAME_CLIENT_CONNECT, // ( int clientNum ,int isSpectator); // ( int clientNum, qbool firstTime, qbool isBot ); // return NULL if the client is allowed to connect, otherwise return // a text string with the reason for denial GAME_PUT_CLIENT_IN_SERVER, //GAME_CLIENT_BEGIN, // ( int clientNum ,int isSpectator); GAME_CLIENT_USERINFO_CHANGED, // ( int clientNum,int isSpectator ); GAME_CLIENT_DISCONNECT, // ( int clientNum,int isSpectator ); GAME_CLIENT_COMMAND, // ( int clientNum,int isSpectator ); GAME_CLIENT_PRETHINK, GAME_CLIENT_THINK, // ( int clientNum,int isSpectator ); GAME_CLIENT_POSTTHINK, GAME_START_FRAME, // ( int levelTime ); GAME_SETCHANGEPARMS, //self GAME_SETNEWPARMS, GAME_CONSOLE_COMMAND, // ( void ); GAME_EDICT_TOUCH, //(self,other) GAME_EDICT_THINK, //(self,other=world,time) GAME_EDICT_BLOCKED, //(self,other) // ConsoleCommand will be called when a command has been issued // that is not recognized as a builtin function. // The game can issue trap_argc() / trap_argv() commands to get the command // and parameters. Return qfalse if the game doesn't recognize it as a command. GAME_CLIENT_SAY, // ( int isTeamSay ); GAME_PAUSED_TIC, // ( int duration_msec ); // duration is in msecs GAME_CLEAR_EDICT, // (self) } gameExport_t; typedef enum { F_INT, F_FLOAT, F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL // F_GSTRING, // string on disk, pointer in memory, TAG_GAME F_VECTOR, F_ANGLEHACK, // F_ENTITY, // index on disk, pointer in memory // F_ITEM, // index on disk, pointer in memory // F_CLIENT, // index on disk, pointer in memory F_IGNORE } fieldtype_t; typedef struct { stringptr_t name; int ofs; fieldtype_t type; } field_t; typedef struct { int name; int ofs; int type; } field_vm_t; typedef struct { intptr_t ents; int sizeofent; intptr_t global; intptr_t fields; int APIversion; int maxentities; } gameData_t; typedef struct { int ents_p; int sizeofent; int global_p; int fields_p; int APIversion; int maxentities; } gameData_vm_t; typedef int fileHandle_t; typedef enum { FS_READ_BIN, FS_READ_TXT, FS_WRITE_BIN, FS_WRITE_TXT, FS_APPEND_BIN, FS_APPEND_TXT } fsMode_t; typedef enum { FS_SEEK_CUR, FS_SEEK_END, FS_SEEK_SET } fsOrigin_t; #endif /* !__G_PUBLIC_H__ */ mvdsv-0.35/src/hash.c000066400000000000000000000173061427146041000144640ustar00rootroot00000000000000/* Copyright (C) 2011 ezQuake team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef SERVERONLY #include "qwsvdef.h" #else #include // <-- only needed for Hash_BucketStats #include #include #include #include "quakedef.h" #include "q_shared.h" #include "hash.h" #endif hashtable_t *Hash_InitTable(int numbucks) { hashtable_t *table; table = Q_malloc(sizeof(*table)); table->bucket = (bucket_t**)Q_calloc(numbucks, sizeof(bucket_t *)); table->numbuckets = numbucks; return table; } void Hash_ShutdownTable(hashtable_t* table) { int i; if (!table) { return; } for (i = 0; i < table->numbuckets; ++i) { bucket_t* list = table->bucket[i]; while (list) { bucket_t* next = list->next; if (list->flags & HASH_BUCKET_KEYSTRING_OWNED) { Q_free(list->keystring); } Q_free(list); list = next; } } Q_free(table->bucket); Q_free(table); } /* http://www.cse.yorku.ca/~oz/hash.html * djb2 * This algorithm (k=33) was first reported by dan bernstein many years ago * in comp.lang.c. another version of this algorithm (now favored by bernstein) * uses xor: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why * it works better than many other constants, prime or not) has never been * adequately explained. */ int Hash_Key(char *name, int modulus) { unsigned int key = 5381; for (key = 5381; *name; name++) key = ((key << 5) + key) + *name; /* key * 33 + c */ return (int) (key % modulus); } int Hash_KeyInsensitive(const char *name, int modulus) { unsigned int key = 5381; for (key = 5381; *name; name++) key = ((key << 5) + key) + tolower(*name); /* key * 33 + c */ return (int) (key % modulus); } #if 0 int Hash_Key(char *name, int modulus) { //fixme: optimize. unsigned int key; for (key=0;*name; name++) key += ((key<<3) + (key>>28) + *name); return (int)(key%modulus); } int Hash_KeyInsensitive(char *name, int modulus) { //fixme: optimize. unsigned int key; for (key=0;*name; name++) { if (*name >= 'A' && *name <= 'Z') key += ((key<<3) + (key>>28) + (*name-'A'+'a')); else key += ((key<<3) + (key>>28) + *name); } return (int)(key%modulus); } #endif void *Hash_Get(hashtable_t *table, char *name) { int bucknum = Hash_Key(name, table->numbuckets); bucket_t *buck; buck = table->bucket[bucknum]; while(buck) { if (!STRCMP(name, buck->keystring)) return buck->data; buck = buck->next; } return NULL; } void *Hash_GetInsensitive(hashtable_t *table, const char *name) { int bucknum = Hash_KeyInsensitive(name, table->numbuckets); bucket_t *buck; buck = table->bucket[bucknum]; while(buck) { if (!strcasecmp(name, buck->keystring)) return buck->data; buck = buck->next; } return NULL; } void *Hash_GetKey(hashtable_t *table, char *key) { int bucknum = ((uintptr_t) key) % table->numbuckets; bucket_t *buck; buck = table->bucket[bucknum]; while(buck) { if (buck->keystring == key) return buck->data; buck = buck->next; } return NULL; } void *Hash_GetNext(hashtable_t *table, char *name, void *old) { int bucknum = Hash_Key(name, table->numbuckets); bucket_t *buck; buck = table->bucket[bucknum]; while(buck) { if (!STRCMP(name, buck->keystring)) { if (buck->data == old) //found the old one break; } buck = buck->next; } if (!buck) return NULL; buck = buck->next;//don't return old while(buck) { if (!STRCMP(name, buck->keystring)) return buck->data; buck = buck->next; } return NULL; } void *Hash_GetNextInsensitive(hashtable_t *table, char *name, void *old) { int bucknum = Hash_KeyInsensitive(name, table->numbuckets); bucket_t *buck; buck = table->bucket[bucknum]; while(buck) { if (!STRCMP(name, buck->keystring)) { if (buck->data == old) //found the old one break; } buck = buck->next; } if (!buck) return NULL; buck = buck->next;//don't return old while(buck) { if (!STRCMP(name, buck->keystring)) return buck->data; buck = buck->next; } return NULL; } void *Hash_Add(hashtable_t *table, char *name, void *data) { int bucknum = Hash_Key(name, table->numbuckets); bucket_t *buck = (bucket_t *) Q_malloc(sizeof(bucket_t)); char *keystring = Q_strdup(name); buck->data = data; buck->keystring = keystring; buck->next = table->bucket[bucknum]; buck->flags = HASH_BUCKET_KEYSTRING_OWNED; table->bucket[bucknum] = buck; return buck; } void *Hash_AddInsensitive(hashtable_t *table, char *name, void *data) { int bucknum = Hash_KeyInsensitive(name, table->numbuckets); bucket_t *buck = (bucket_t *) Q_malloc(sizeof(bucket_t)); char *keystring = Q_strdup(name); buck->data = data; buck->keystring = keystring; buck->next = table->bucket[bucknum]; buck->flags = HASH_BUCKET_KEYSTRING_OWNED; table->bucket[bucknum] = buck; return buck; } void *Hash_AddKey(hashtable_t *table, char *key, void *data, bucket_t *buck) { int bucknum = ((uintptr_t) key) % table->numbuckets; buck->data = data; buck->keystring = key; buck->next = table->bucket[bucknum]; table->bucket[bucknum] = buck; return buck; } void Hash_Remove(hashtable_t *table, char *name) { int bucknum = Hash_Key(name, table->numbuckets); bucket_t *buck; buck = table->bucket[bucknum]; if (!STRCMP(name, buck->keystring)) { table->bucket[bucknum] = buck->next; Q_free(buck->keystring); Q_free(buck); return; } while(buck->next) { if (!STRCMP(name, buck->next->keystring)) { buck->next = buck->next->next; Q_free(buck->next->keystring); Q_free(buck->next); return; } buck = buck->next; } return; } void Hash_RemoveData(hashtable_t *table, char *name, void *data) { int bucknum = Hash_Key(name, table->numbuckets); bucket_t *buck; buck = table->bucket[bucknum]; if (buck->data == data) if (!STRCMP(name, buck->keystring)) { table->bucket[bucknum] = buck->next; Q_free(buck->keystring); Q_free(buck); return; } while(buck->next) { if (buck->next->data == data) if (!STRCMP(name, buck->next->keystring)) { buck->next = buck->next->next; Q_free(buck->next->keystring); Q_free(buck->next); return; } buck = buck->next; } return; } void Hash_RemoveKey(hashtable_t *table, char *key) { int bucknum = ((uintptr_t) key) % table->numbuckets; bucket_t *buck; buck = table->bucket[bucknum]; if (buck->keystring == key) { table->bucket[bucknum] = buck->next; Q_free(buck->keystring); Q_free(buck); return; } while(buck->next) { if (buck->next->keystring == key) { buck->next = buck->next->next; Q_free(buck->next->keystring); Q_free(buck->next); return; } buck = buck->next; } return; } void Hash_Flush(hashtable_t *table) { int i; for (i = 0; i < table->numbuckets; i++) { bucket_t *bucket, *next; bucket = table->bucket[i]; table->bucket[i] = NULL; while (bucket) { next = bucket->next; Q_free(bucket->keystring); Q_free(bucket); bucket = next; } } return; } #if 0 void Hash_BucketStats(hashtable_t *table) { int i; bucket_t *buck; int bucket_count; for (i = 0; i < table->numbuckets; i++) { bucket_count = 0; for (buck = table->bucket[i]; buck; buck = buck->next) { bucket_count++; } printf("table[%d] = %d\n", i, bucket_count); } } #endif mvdsv-0.35/src/hash.h000066400000000000000000000040431427146041000144630ustar00rootroot00000000000000/* Copyright (C) 2011 ezQuake team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ //============================= //David's hash tables //string based. #ifndef __HASH_H__ #define __HASH_H__ #define STRCMP(s1,s2) (((*s1)!=(*s2)) || strcmp(s1+1,s2+1)) //saves about 2-6 out of 120 - expansion of idea from fastqcc typedef struct bucket_s { void *data; char *keystring; struct bucket_s *next; int flags; } bucket_t; #define HASH_BUCKET_KEYSTRING_OWNED 1 typedef struct hashtable_s { int numbuckets; bucket_t **bucket; } hashtable_t; hashtable_t *Hash_InitTable(int numbucks); void Hash_ShutdownTable(hashtable_t* table); int Hash_Key(char *name, int modulus); void *Hash_Get(hashtable_t *table, char *name); void *Hash_GetInsensitive(hashtable_t *table, const char *name); void *Hash_GetKey(hashtable_t *table, char *key); void *Hash_GetNext(hashtable_t *table, char *name, void *old); void *Hash_GetNextInsensitive(hashtable_t *table, char *name, void *old); void *Hash_Add(hashtable_t *table, char *name, void *data); void *Hash_AddInsensitive(hashtable_t *table, char *name, void *data); void Hash_Remove(hashtable_t *table, char *name); void Hash_RemoveData(hashtable_t *table, char *name, void *data); void Hash_RemoveKey(hashtable_t *table, char *key); void *Hash_AddKey(hashtable_t *table, char *key, void *data, bucket_t *buck); void Hash_Flush(hashtable_t *table); #if 0 /* Print some stats on the bucket distrubution */ void Hash_BucketStats(hashtable_t *table); #endif #endif // __HASH_H__ mvdsv-0.35/src/log.h000066400000000000000000000025161427146041000143240ustar00rootroot00000000000000/* Copyright (C) 2004 VVD (vvd0@sorceforge.net). 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. */ #ifndef __LOG_H__ #define __LOG_H__ enum { MIN_LOG = 0, CONSOLE_LOG = 0, ERROR_LOG, RCON_LOG, TELNET_LOG, FRAG_LOG, PLAYER_LOG, MOD_FRAG_LOG, MAX_LOG}; typedef struct log_s { FILE *sv_logfile; char *command; char *file_name; char *message_off; char *message_on; xcommand_t function; int log_level; } log_t; extern log_t logs[MAX_LOG]; extern cvar_t frag_log_type; extern cvar_t telnet_log_level; //bliP: logging void SV_Logfile (int sv_log, qbool newlog); void SV_LogPlayer(client_t *cl, char *msg, int level); //<- void SV_Write_Log(int sv_log, int level, char *msg); #endif /* !__LOG_H__ */ mvdsv-0.35/src/math.s000066400000000000000000000216131427146041000145060ustar00rootroot00000000000000/* math.s x86 assembly language math routines Copyright (C) 1996-1997 Id Software, Inc. 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: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #include "asm_i386.h" #include "quakeasm.h" #ifdef id386 .data .align 4 Ljmptab: .long Lcase0, Lcase1, Lcase2, Lcase3 .long Lcase4, Lcase5, Lcase6, Lcase7 .text .extern C(BOPS_Error) #define EMINS 4+4 #define EMAXS 4+8 #define P 4+12 .align 2 .globl C(BoxOnPlaneSide) C(BoxOnPlaneSide): pushl %ebx movl P(%esp),%edx movl EMINS(%esp),%ecx xorl %eax,%eax movl EMAXS(%esp),%ebx movb pl_signbits(%edx),%al cmpb $8,%al jge Lerror flds pl_normal(%edx) // p->normal[0] fld %st(0) // p->normal[0] | p->normal[0] jmp *Ljmptab(,%eax,4) //dist1= p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; //dist2= p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; Lcase0: fmuls (%ebx) // p->normal[0]*emaxs[0] | p->normal[0] flds pl_normal+4(%edx) // p->normal[1] | p->normal[0]*emaxs[0] | // p->normal[0] fxch %st(2) // p->normal[0] | p->normal[0]*emaxs[0] | // p->normal[1] fmuls (%ecx) // p->normal[0]*emins[0] | // p->normal[0]*emaxs[0] | p->normal[1] fxch %st(2) // p->normal[1] | p->normal[0]*emaxs[0] | // p->normal[0]*emins[0] fld %st(0) // p->normal[1] | p->normal[1] | // p->normal[0]*emaxs[0] | // p->normal[0]*emins[0] fmuls 4(%ebx) // p->normal[1]*emaxs[1] | p->normal[1] | // p->normal[0]*emaxs[0] | // p->normal[0]*emins[0] flds pl_normal+8(%edx) // p->normal[2] | p->normal[1]*emaxs[1] | // p->normal[1] | p->normal[0]*emaxs[0] | // p->normal[0]*emins[0] fxch %st(2) // p->normal[1] | p->normal[1]*emaxs[1] | // p->normal[2] | p->normal[0]*emaxs[0] | // p->normal[0]*emins[0] fmuls 4(%ecx) // p->normal[1]*emins[1] | // p->normal[1]*emaxs[1] | // p->normal[2] | p->normal[0]*emaxs[0] | // p->normal[0]*emins[0] fxch %st(2) // p->normal[2] | p->normal[1]*emaxs[1] | // p->normal[1]*emins[1] | // p->normal[0]*emaxs[0] | // p->normal[0]*emins[0] fld %st(0) // p->normal[2] | p->normal[2] | // p->normal[1]*emaxs[1] | // p->normal[1]*emins[1] | // p->normal[0]*emaxs[0] | // p->normal[0]*emins[0] fmuls 8(%ebx) // p->normal[2]*emaxs[2] | // p->normal[2] | // p->normal[1]*emaxs[1] | // p->normal[1]*emins[1] | // p->normal[0]*emaxs[0] | // p->normal[0]*emins[0] fxch %st(5) // p->normal[0]*emins[0] | // p->normal[2] | // p->normal[1]*emaxs[1] | // p->normal[1]*emins[1] | // p->normal[0]*emaxs[0] | // p->normal[2]*emaxs[2] faddp %st(0),%st(3) //p->normal[2] | // p->normal[1]*emaxs[1] | // p->normal[1]*emins[1]+p->normal[0]*emins[0]| // p->normal[0]*emaxs[0] | // p->normal[2]*emaxs[2] fmuls 8(%ecx) //p->normal[2]*emins[2] | // p->normal[1]*emaxs[1] | // p->normal[1]*emins[1]+p->normal[0]*emins[0]| // p->normal[0]*emaxs[0] | // p->normal[2]*emaxs[2] fxch %st(1) //p->normal[1]*emaxs[1] | // p->normal[2]*emins[2] | // p->normal[1]*emins[1]+p->normal[0]*emins[0]| // p->normal[0]*emaxs[0] | // p->normal[2]*emaxs[2] faddp %st(0),%st(3) //p->normal[2]*emins[2] | // p->normal[1]*emins[1]+p->normal[0]*emins[0]| // p->normal[0]*emaxs[0]+p->normal[1]*emaxs[1]| // p->normal[2]*emaxs[2] fxch %st(3) //p->normal[2]*emaxs[2] + // p->normal[1]*emins[1]+p->normal[0]*emins[0]| // p->normal[0]*emaxs[0]+p->normal[1]*emaxs[1]| // p->normal[2]*emins[2] faddp %st(0),%st(2) //p->normal[1]*emins[1]+p->normal[0]*emins[0]| // dist1 | p->normal[2]*emins[2] jmp LSetSides //dist1= p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; //dist2= p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; Lcase1: fmuls (%ecx) // emins[0] flds pl_normal+4(%edx) fxch %st(2) fmuls (%ebx) // emaxs[0] fxch %st(2) fld %st(0) fmuls 4(%ebx) // emaxs[1] flds pl_normal+8(%edx) fxch %st(2) fmuls 4(%ecx) // emins[1] fxch %st(2) fld %st(0) fmuls 8(%ebx) // emaxs[2] fxch %st(5) faddp %st(0),%st(3) fmuls 8(%ecx) // emins[2] fxch %st(1) faddp %st(0),%st(3) fxch %st(3) faddp %st(0),%st(2) jmp LSetSides //dist1= p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; //dist2= p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; Lcase2: fmuls (%ebx) // emaxs[0] flds pl_normal+4(%edx) fxch %st(2) fmuls (%ecx) // emins[0] fxch %st(2) fld %st(0) fmuls 4(%ecx) // emins[1] flds pl_normal+8(%edx) fxch %st(2) fmuls 4(%ebx) // emaxs[1] fxch %st(2) fld %st(0) fmuls 8(%ebx) // emaxs[2] fxch %st(5) faddp %st(0),%st(3) fmuls 8(%ecx) // emins[2] fxch %st(1) faddp %st(0),%st(3) fxch %st(3) faddp %st(0),%st(2) jmp LSetSides //dist1= p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; //dist2= p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; Lcase3: fmuls (%ecx) // emins[0] flds pl_normal+4(%edx) fxch %st(2) fmuls (%ebx) // emaxs[0] fxch %st(2) fld %st(0) fmuls 4(%ecx) // emins[1] flds pl_normal+8(%edx) fxch %st(2) fmuls 4(%ebx) // emaxs[1] fxch %st(2) fld %st(0) fmuls 8(%ebx) // emaxs[2] fxch %st(5) faddp %st(0),%st(3) fmuls 8(%ecx) // emins[2] fxch %st(1) faddp %st(0),%st(3) fxch %st(3) faddp %st(0),%st(2) jmp LSetSides //dist1= p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; //dist2= p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; Lcase4: fmuls (%ebx) // emaxs[0] flds pl_normal+4(%edx) fxch %st(2) fmuls (%ecx) // emins[0] fxch %st(2) fld %st(0) fmuls 4(%ebx) // emaxs[1] flds pl_normal+8(%edx) fxch %st(2) fmuls 4(%ecx) // emins[1] fxch %st(2) fld %st(0) fmuls 8(%ecx) // emins[2] fxch %st(5) faddp %st(0),%st(3) fmuls 8(%ebx) // emaxs[2] fxch %st(1) faddp %st(0),%st(3) fxch %st(3) faddp %st(0),%st(2) jmp LSetSides //dist1= p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; //dist2= p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; Lcase5: fmuls (%ecx) // emins[0] flds pl_normal+4(%edx) fxch %st(2) fmuls (%ebx) // emaxs[0] fxch %st(2) fld %st(0) fmuls 4(%ebx) // emaxs[1] flds pl_normal+8(%edx) fxch %st(2) fmuls 4(%ecx) // emins[1] fxch %st(2) fld %st(0) fmuls 8(%ecx) // emins[2] fxch %st(5) faddp %st(0),%st(3) fmuls 8(%ebx) // emaxs[2] fxch %st(1) faddp %st(0),%st(3) fxch %st(3) faddp %st(0),%st(2) jmp LSetSides //dist1= p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; //dist2= p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; Lcase6: fmuls (%ebx) // emaxs[0] flds pl_normal+4(%edx) fxch %st(2) fmuls (%ecx) // emins[0] fxch %st(2) fld %st(0) fmuls 4(%ecx) // emins[1] flds pl_normal+8(%edx) fxch %st(2) fmuls 4(%ebx) // emaxs[1] fxch %st(2) fld %st(0) fmuls 8(%ecx) // emins[2] fxch %st(5) faddp %st(0),%st(3) fmuls 8(%ebx) // emaxs[2] fxch %st(1) faddp %st(0),%st(3) fxch %st(3) faddp %st(0),%st(2) jmp LSetSides //dist1= p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; //dist2= p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; Lcase7: fmuls (%ecx) // emins[0] flds pl_normal+4(%edx) fxch %st(2) fmuls (%ebx) // emaxs[0] fxch %st(2) fld %st(0) fmuls 4(%ecx) // emins[1] flds pl_normal+8(%edx) fxch %st(2) fmuls 4(%ebx) // emaxs[1] fxch %st(2) fld %st(0) fmuls 8(%ecx) // emins[2] fxch %st(5) faddp %st(0),%st(3) fmuls 8(%ebx) // emaxs[2] fxch %st(1) faddp %st(0),%st(3) fxch %st(3) faddp %st(0),%st(2) LSetSides: // sides = 0; // if (dist1 >= p->dist) // sides = 1; // if (dist2 < p->dist) // sides |= 2; faddp %st(0),%st(2) // dist1 | dist2 fcomps pl_dist(%edx) xorl %ecx,%ecx fnstsw %ax fcomps pl_dist(%edx) andb $1,%ah xorb $1,%ah addb %ah,%cl fnstsw %ax andb $1,%ah addb %ah,%ah addb %ah,%cl // return sides; popl %ebx movl %ecx,%eax // return status ret Lerror: call C(BOPS_Error) #endif /* id386 */ mvdsv-0.35/src/mathlib.c000066400000000000000000000167041427146041000151620ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // mathlib.c -- math primitives #ifdef SERVERONLY #include "qwsvdef.h" #else #include "common.h" #endif vec3_t vec3_origin = {0,0,0}; float _mathlib_temp_float1, _mathlib_temp_float2, _mathlib_temp_float3; int _mathlib_temp_int1, _mathlib_temp_int2, _mathlib_temp_int3; /*-----------------------------------------------------------------*/ float anglemod(float a) { #if 0 if (a >= 0) a -= 360*(int)(a/360); else a += 360*( 1 + (int)(-a/360) ); #endif a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); return a; } #define VectorNormalizeFast(_v) \ do { \ _mathlib_temp_float1 = DotProduct((_v), (_v)); \ if (_mathlib_temp_float1) { \ _mathlib_temp_float2 = 0.5f * _mathlib_temp_float1; \ _mathlib_temp_int1 = *((int *) &_mathlib_temp_float1); \ _mathlib_temp_int1 = 0x5f375a86 - (_mathlib_temp_int1 >> 1); \ _mathlib_temp_float1 = *((float *) &_mathlib_temp_int1); \ _mathlib_temp_float1 = _mathlib_temp_float1 * (1.5f - _mathlib_temp_float2 * _mathlib_temp_float1 * _mathlib_temp_float1); \ VectorScale((_v), _mathlib_temp_float1, (_v)) \ } \ } while (0); void PerpendicularVector(vec3_t dst, const vec3_t src) { if (!src[0]) { VectorSet(dst, 1, 0, 0); } else if (!src[1]) { VectorSet(dst, 0, 1, 0); } else if (!src[2]) { VectorSet(dst, 0, 0, 1); } else { VectorSet(dst, -src[1], src[0], 0); VectorNormalizeFast(dst); } } void VectorVectors(vec3_t forward, vec3_t right, vec3_t up) { PerpendicularVector(right, forward); CrossProduct(right, forward, up); } void RotatePointAroundVector(vec3_t dst, const vec3_t dir, const vec3_t point, float degrees) { float t0, t1, angle, c, s; vec3_t vr, vu, vf; angle = DEG2RAD(degrees); c = cos(angle); s = sin(angle); VectorCopy(dir, vf); VectorVectors(vf, vr, vu); t0 = vr[0] * c + vu[0] * -s; t1 = vr[0] * s + vu[0] * c; dst[0] = (t0 * vr[0] + t1 * vu[0] + vf[0] * vf[0]) * point[0] + (t0 * vr[1] + t1 * vu[1] + vf[0] * vf[1]) * point[1] + (t0 * vr[2] + t1 * vu[2] + vf[0] * vf[2]) * point[2]; t0 = vr[1] * c + vu[1] * -s; t1 = vr[1] * s + vu[1] * c; dst[1] = (t0 * vr[0] + t1 * vu[0] + vf[1] * vf[0]) * point[0] + (t0 * vr[1] + t1 * vu[1] + vf[1] * vf[1]) * point[1] + (t0 * vr[2] + t1 * vu[2] + vf[1] * vf[2]) * point[2]; t0 = vr[2] * c + vu[2] * -s; t1 = vr[2] * s + vu[2] * c; dst[2] = (t0 * vr[0] + t1 * vu[0] + vf[2] * vf[0]) * point[0] + (t0 * vr[1] + t1 * vu[1] + vf[2] * vf[1]) * point[1] + (t0 * vr[2] + t1 * vu[2] + vf[2] * vf[2]) * point[2]; } /* ================== BOPS_Error Split out like this for ASM to call. ================== */ #ifdef __cplusplus extern "C" { #endif void BOPS_Error (void) { Sys_Error ("BoxOnPlaneSide: Bad signbits"); } #ifdef __cplusplus } /* extern "C" */ #endif #ifndef id386 /* ================== BoxOnPlaneSide Returns 1, 2, or 1 + 2 ================== */ #ifdef __cplusplus extern "C" { #endif int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, mplane_t *p) { float dist1, dist2; int sides; #if 0 // this is done by the BOX_ON_PLANE_SIDE macro before calling this // function // fast axial cases if (p->type < 3) { if (p->dist <= emins[p->type]) return 1; if (p->dist >= emaxs[p->type]) return 2; return 3; } #endif // general case switch (p->signbits) { case 0: dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; break; case 1: dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; break; case 2: dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; break; case 3: dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; break; case 4: dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; break; case 5: dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; break; case 6: dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; break; case 7: dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; break; default: dist1 = dist2 = 0; // shut up compiler BOPS_Error (); break; } #if 0 int i; vec3_t corners[2]; for (i=0 ; i<3 ; i++) { if (plane->normal[i] < 0) { corners[0][i] = emins[i]; corners[1][i] = emaxs[i]; } else { corners[1][i] = emins[i]; corners[0][i] = emaxs[i]; } } dist = DotProduct (plane->normal, corners[0]) - plane->dist; dist2 = DotProduct (plane->normal, corners[1]) - plane->dist; sides = 0; if (dist1 >= 0) sides = 1; if (dist2 < 0) sides |= 2; #endif sides = 0; if (dist1 >= p->dist) sides = 1; if (dist2 < p->dist) sides |= 2; #ifdef PARANOID if (sides == 0) Sys_Error ("BoxOnPlaneSide: sides==0"); #endif return sides; } #ifdef __cplusplus } /* extern "C" */ #endif #endif /* id386 */ void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) { float angle; float sr, sp, sy, cr, cp, cy; angle = angles[YAW] * (M_PI*2 / 360); sy = sin(angle); cy = cos(angle); angle = angles[PITCH] * (M_PI*2 / 360); sp = sin(angle); cp = cos(angle); if (forward) { forward[0] = cp*cy; forward[1] = cp*sy; forward[2] = -sp; } if (right || up) { angle = angles[ROLL] * (M_PI*2 / 360); sr = sin(angle); cr = cos(angle); if (right) { right[0] = (-1*sr*sp*cy+-1*cr*-sy); right[1] = (-1*sr*sp*sy+-1*cr*cy); right[2] = -1*sr*cp; } if (up) { up[0] = (cr*sp*cy+-sr*-sy); up[1] = (cr*sp*sy+-sr*cy); up[2] = cr*cp; } } } void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc) { vecc[0] = veca[0] + scale*vecb[0]; vecc[1] = veca[1] + scale*vecb[1]; vecc[2] = veca[2] + scale*vecb[2]; } void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) { cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; } vec_t VectorLength (vec3_t v) { return sqrt (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); } float VectorNormalize (vec3_t v) { float length, ilength; length = sqrt (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); if (length) { ilength = 1/length; v[0] *= ilength; v[1] *= ilength; v[2] *= ilength; } return length; } mvdsv-0.35/src/mathlib.h000066400000000000000000000072511427146041000151640ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // mathlib.h #ifndef __MATHLIB_H__ #define __MATHLIB_H__ #define PITCH 0 // up / down #define YAW 1 // left / right #define ROLL 2 // fall over typedef float vec_t; typedef vec_t vec3_t[3]; typedef vec_t vec5_t[5]; typedef int fixed4_t; typedef int fixed8_t; typedef int fixed16_t; #ifndef M_PI #define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h #endif struct mplane_s; extern vec3_t vec3_origin; extern float _mathlib_temp_float1, _mathlib_temp_float2, _mathlib_temp_float3; extern int _mathlib_temp_int1, _mathlib_temp_int2, _mathlib_temp_int3; #define DEG2RAD(a) (((a) * M_PI) / 180.0F) #define NANMASK (255<<23) #define IS_NAN(x) (((*(int *)&x)&NANMASK)==NANMASK) #ifndef Q_rint #define Q_rint(x) ((x) > 0 ? (int)((x) + 0.5) : (int)((x) - 0.5)) #endif #define FloatInterpolate(a, bness, b, c) (c) = (a)*(1-(bness)) + (b)*(bness) #define VectorInterpolate(a, bness, b, c) FloatInterpolate((a)[0], bness, (b)[0], (c)[0]),FloatInterpolate((a)[1], bness, (b)[1], (c)[1]),FloatInterpolate((a)[2], bness, (b)[2], (c)[2]) #define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) #define VectorSubtract(a,b,c) (c[0]=a[0]-b[0],c[1]=a[1]-b[1],c[2]=a[2]-b[2]) #define VectorAdd(a,b,c) (c[0]=a[0]+b[0],c[1]=a[1]+b[1],c[2]=a[2]+b[2]) #define VectorCopy(a,b) (b[0]=a[0],b[1]=a[1],b[2]=a[2]) #define VectorClear(a) (a[0]=a[1]=a[2]=0) #define VectorNegate(a,b) (b[0]=-a[0],b[1]=-a[1],b[2]=-a[2]) #define VectorSet(v, x, y, z) (v[0]=(x),v[1]=(y),v[2]=(z)) void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc); vec_t VectorLength (vec3_t v); void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); float VectorNormalize (vec3_t v); // returns vector length #define VectorScale(in, _scale, out) \ do { \ float scale = (_scale); \ (out)[0] = (in)[0] * (scale); (out)[1] = (in)[1] * (scale); (out)[2] = (in)[2] * (scale); \ } while (0); void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); #ifdef __cplusplus extern "C" { #endif int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct mplane_s *plane); #ifdef __cplusplus } /* extern "C" */ #endif float anglemod(float a); #define PlaneDiff(point, plane) ( \ (((plane)->type < 3) ? (point)[(plane)->type] - (plane)->dist: DotProduct((point), (plane)->normal) - (plane)->dist) \ ) #define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ (((p)->type < 3)? \ ( \ ((p)->dist <= (emins)[(p)->type])? \ 1 \ : \ ( \ ((p)->dist >= (emaxs)[(p)->type])?\ 2 \ : \ 3 \ ) \ ) \ : \ BoxOnPlaneSide( (emins), (emaxs), (p))) #define PlaneDiff(point, plane) ( \ (((plane)->type < 3) ? (point)[(plane)->type] - (plane)->dist: DotProduct((point), (plane)->normal) - (plane)->dist) \ ) void RotatePointAroundVector(vec3_t dst, const vec3_t dir, const vec3_t point, float degrees); void VectorVectors(vec3_t forward, vec3_t right, vec3_t up); #endif /* !__MATHLIB_H__ */ mvdsv-0.35/src/md4.c000066400000000000000000000255421427146041000142260ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ /* GLOBAL.H - RSAREF types and constants */ #include /* POINTER defines a generic pointer type */ typedef unsigned char *POINTER; /* UINT2 defines a two byte word */ typedef unsigned short int UINT2; /* UINT4 defines a four byte word */ #if defined(__alpha__) || defined(_LP64) || defined(__x86_64) typedef unsigned int UINT4; #else typedef unsigned long int UINT4; #endif /* MD4.H - header file for MD4C.C */ /* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. License to copy and use this software is granted provided that it is identified as the 'RSA Data Security, Inc. MD4 Message-Digest Algorithm' in all material mentioning or referencing this software or this function. License is also granted to make and use derivative works provided that such works are identified as 'derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm' in all material mentioning or referencing the derived work. RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided 'as is' without express or implied warranty of any kind. These notices must be retained in any copies of any part of this documentation and/or software. */ /* MD4 context. */ typedef struct { UINT4 state[4]; /* state (ABCD) */ UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ unsigned char buffer[64]; /* input buffer */ } MD4_CTX; void MD4Init (MD4_CTX *); void MD4Update (MD4_CTX *, unsigned char *, unsigned int); void MD4Final (unsigned char [16], MD4_CTX *); /* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm */ /* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. License to copy and use this software is granted provided that it is identified as the RSA Data Security, Inc. MD4 Message-Digest Algorithm in all material mentioning or referencing this software or this function. License is also granted to make and use derivative works provided that such works are identified as derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm in all material mentioning or referencing the derived work. RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided as is without express or implied warranty of any kind. These notices must be retained in any copies of any part of this documentation and/or software. */ /* Constants for MD4Transform routine. */ #define S11 3 #define S12 7 #define S13 11 #define S14 19 #define S21 3 #define S22 5 #define S23 9 #define S24 13 #define S31 3 #define S32 9 #define S33 11 #define S34 15 static void MD4Transform (UINT4 [4], unsigned char [64]); static void Encode (unsigned char *, UINT4 *, unsigned int); static void Decode (UINT4 *, unsigned char *, unsigned int); static unsigned char PADDING[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* F, G and H are basic MD4 functions. */ #define F(x, y, z) (((x) & (y)) | ((~x) & (z))) #define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) #define H(x, y, z) ((x) ^ (y) ^ (z)) /* ROTATE_LEFT rotates x left n bits. */ #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) /* FF, GG and HH are transformations for rounds 1, 2 and 3 */ /* Rotation is separate from addition to prevent recomputation */ #define FF(a, b, c, d, x, s) {(a) += F ((b), (c), (d)) + (x); (a) = ROTATE_LEFT ((a), (s));} #define GG(a, b, c, d, x, s) {(a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; (a) = ROTATE_LEFT ((a), (s));} #define HH(a, b, c, d, x, s) {(a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; (a) = ROTATE_LEFT ((a), (s));} /* MD4 initialization. Begins an MD4 operation, writing a new context. */ void MD4Init (MD4_CTX *context) { context->count[0] = context->count[1] = 0; /* Load magic initialization constants.*/ context->state[0] = 0x67452301; context->state[1] = 0xefcdab89; context->state[2] = 0x98badcfe; context->state[3] = 0x10325476; } /* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */ void MD4Update (MD4_CTX *context, unsigned char *input, unsigned int inputLen) { unsigned int i, index, partLen; /* Compute number of bytes mod 64 */ index = (unsigned int)((context->count[0] >> 3) & 0x3F); /* Update number of bits */ if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3)) context->count[1]++; context->count[1] += ((UINT4)inputLen >> 29); partLen = 64 - index; /* Transform as many times as possible.*/ if (inputLen >= partLen) { memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); MD4Transform (context->state, context->buffer); for (i = partLen; i + 63 < inputLen; i += 64) MD4Transform (context->state, &input[i]); index = 0; } else i = 0; /* Buffer remaining input */ memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i); } /* MD4 finalization. Ends an MD4 message-digest operation, writing the the message digest and zeroizing the context. */ void MD4Final (unsigned char digest[16], MD4_CTX *context) { unsigned char bits[8]; unsigned int index, padLen; /* Save number of bits */ Encode (bits, context->count, 8); /* Pad out to 56 mod 64.*/ index = (unsigned int)((context->count[0] >> 3) & 0x3f); padLen = (index < 56) ? (56 - index) : (120 - index); MD4Update (context, PADDING, padLen); /* Append length (before padding) */ MD4Update (context, bits, 8); /* Store state in digest */ Encode (digest, context->state, 16); /* Zeroize sensitive information.*/ memset ((POINTER)context, 0, sizeof (*context)); } /* MD4 basic transformation. Transforms state based on block. */ static void MD4Transform (UINT4 state[4], unsigned char block[64]) { UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; Decode (x, block, 64); /* Round 1 */ FF (a, b, c, d, x[ 0], S11); /* 1 */ FF (d, a, b, c, x[ 1], S12); /* 2 */ FF (c, d, a, b, x[ 2], S13); /* 3 */ FF (b, c, d, a, x[ 3], S14); /* 4 */ FF (a, b, c, d, x[ 4], S11); /* 5 */ FF (d, a, b, c, x[ 5], S12); /* 6 */ FF (c, d, a, b, x[ 6], S13); /* 7 */ FF (b, c, d, a, x[ 7], S14); /* 8 */ FF (a, b, c, d, x[ 8], S11); /* 9 */ FF (d, a, b, c, x[ 9], S12); /* 10 */ FF (c, d, a, b, x[10], S13); /* 11 */ FF (b, c, d, a, x[11], S14); /* 12 */ FF (a, b, c, d, x[12], S11); /* 13 */ FF (d, a, b, c, x[13], S12); /* 14 */ FF (c, d, a, b, x[14], S13); /* 15 */ FF (b, c, d, a, x[15], S14); /* 16 */ /* Round 2 */ GG (a, b, c, d, x[ 0], S21); /* 17 */ GG (d, a, b, c, x[ 4], S22); /* 18 */ GG (c, d, a, b, x[ 8], S23); /* 19 */ GG (b, c, d, a, x[12], S24); /* 20 */ GG (a, b, c, d, x[ 1], S21); /* 21 */ GG (d, a, b, c, x[ 5], S22); /* 22 */ GG (c, d, a, b, x[ 9], S23); /* 23 */ GG (b, c, d, a, x[13], S24); /* 24 */ GG (a, b, c, d, x[ 2], S21); /* 25 */ GG (d, a, b, c, x[ 6], S22); /* 26 */ GG (c, d, a, b, x[10], S23); /* 27 */ GG (b, c, d, a, x[14], S24); /* 28 */ GG (a, b, c, d, x[ 3], S21); /* 29 */ GG (d, a, b, c, x[ 7], S22); /* 30 */ GG (c, d, a, b, x[11], S23); /* 31 */ GG (b, c, d, a, x[15], S24); /* 32 */ /* Round 3 */ HH (a, b, c, d, x[ 0], S31); /* 33 */ HH (d, a, b, c, x[ 8], S32); /* 34 */ HH (c, d, a, b, x[ 4], S33); /* 35 */ HH (b, c, d, a, x[12], S34); /* 36 */ HH (a, b, c, d, x[ 2], S31); /* 37 */ HH (d, a, b, c, x[10], S32); /* 38 */ HH (c, d, a, b, x[ 6], S33); /* 39 */ HH (b, c, d, a, x[14], S34); /* 40 */ HH (a, b, c, d, x[ 1], S31); /* 41 */ HH (d, a, b, c, x[ 9], S32); /* 42 */ HH (c, d, a, b, x[ 5], S33); /* 43 */ HH (b, c, d, a, x[13], S34); /* 44 */ HH (a, b, c, d, x[ 3], S31); /* 45 */ HH (d, a, b, c, x[11], S32); /* 46 */ HH (c, d, a, b, x[ 7], S33); /* 47 */ HH (b, c, d, a, x[15], S34); /* 48 */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; /* Zeroize sensitive information.*/ memset ((POINTER)x, 0, sizeof (x)); } /* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */ static void Encode (unsigned char *output, UINT4 *input, unsigned int len) { unsigned int i, j; for (i = 0, j = 0; j < len; i++, j += 4) { output[j] = (unsigned char)(input[i] & 0xff); output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); } } /* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */ static void Decode (UINT4 *output, unsigned char *input, unsigned int len) { unsigned int i, j; for (i = 0, j = 0; j < len; i++, j += 4) output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); } //=================================================================== unsigned Com_BlockChecksum (void *buffer, int length) { int digest[4]; unsigned val; MD4_CTX ctx; MD4Init (&ctx); MD4Update (&ctx, (unsigned char *)buffer, length); MD4Final ( (unsigned char *)digest, &ctx); val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; return val; } void Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf) { MD4_CTX ctx; MD4Init (&ctx); MD4Update (&ctx, (unsigned char *)buffer, len); MD4Final ( outbuf, &ctx); } mvdsv-0.35/src/net.c000066400000000000000000001007561427146041000143310ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // net.c #ifdef SERVERONLY #include "qwsvdef.h" #else #include "quakedef.h" #include "server.h" #endif #ifdef _WIN32 WSADATA winsockdata; #endif #ifndef SERVERONLY netadr_t net_local_cl_ipadr; #endif #ifndef CLIENTONLY netadr_t net_local_sv_ipadr; netadr_t net_local_sv_tcpipadr; cvar_t sv_local_addr = {"sv_local_addr", "", CVAR_ROM}; #endif netadr_t net_from; sizebuf_t net_message; static byte net_message_buffer[MSG_BUF_SIZE]; // forward definition. qbool NET_GetPacketEx (netsrc_t netsrc, qbool delay); void NET_SendPacketEx (netsrc_t netsrc, int length, void *data, netadr_t to, qbool delay); //============================================================================= // // LOOPBACK defs. // #define MAX_LOOPBACK 4 // must be a power of two typedef struct { byte data[MSG_BUF_SIZE]; int datalen; } loopmsg_t; typedef struct { loopmsg_t msgs[MAX_LOOPBACK]; unsigned int get, send; } loopback_t; loopback_t loopbacks[2]; //============================================================================= // // Delayed packets, CLIENT ONLY. // #ifndef SERVERONLY static cl_delayed_packet_t cl_delayed_packets_input[CL_MAX_DELAYED_PACKETS]; // from server to our client. static cl_delayed_packet_t cl_delayed_packets_output[CL_MAX_DELAYED_PACKETS]; // from our client to server. // // Attempt to read packet from network and if succeed we put that packet in array/queue (so they are delayed). // Packet will be unqued/extracted and used/parsed later, that depends of cl_delay_packet variable. // void CL_QueInputPacket(void) { int i; // read packets. while (NET_GetPacketEx(NS_CLIENT, false)) { // queue one packet. for (i = 0; i < CL_MAX_DELAYED_PACKETS; i++) { if (cl_delayed_packets_input[i].time) continue; // busy slot // we found unused slot, copy packet, get it later, later depends of cl_delay_packet memmove(cl_delayed_packets_input[i].data, net_message.data, net_message.cursize); cl_delayed_packets_input[i].length = net_message.cursize; cl_delayed_packets_input[i].addr = net_from; cl_delayed_packets_input[i].time = Sys_DoubleTime() + 0.001 * bound(0, 0.5 * cl_delay_packet.value, CL_MAX_PACKET_DELAY); break; // queued OK. } if (i >= CL_MAX_DELAYED_PACKETS) { Con_DPrintf("CL_QueInputPacket: cl_delayed_packets_input overflowed\n"); break; } } return; } // // Check if delayed output packets must be sent right now. // void CL_UnqueOutputPacket(qbool sendall) { int i; double time = Sys_DoubleTime(); for (i = 0; i < CL_MAX_DELAYED_PACKETS; i++) { if (!cl_delayed_packets_output[i].time) continue; // unused slot if (cl_delayed_packets_output[i].time > time && !sendall) continue; // we are not yet ready for send // ok, send it NET_SendPacketEx(NS_CLIENT, cl_delayed_packets_output[i].length, cl_delayed_packets_output[i].data, cl_delayed_packets_output[i].addr, false); cl_delayed_packets_output[i].time = 0; // mark as unused slot // perhaps there other packets should be sent // return; } } // // Get packet from input queue. // qbool NET_GetDelayedPacket (netsrc_t netsrc, netadr_t *from, sizebuf_t *message) { int i; double time = Sys_DoubleTime(); for (i = 0; i < CL_MAX_DELAYED_PACKETS; i++) { if (!cl_delayed_packets_input[i].time) continue; // unused slot if (cl_delayed_packets_input[i].time > time) continue; // we are not yet ready to get this // ok, we got something SZ_Clear(message); SZ_Write(message, cl_delayed_packets_input[i].data, cl_delayed_packets_input[i].length); *from = cl_delayed_packets_input[i].addr; cl_delayed_packets_input[i].time = 0; // mark as free slot return true; } return false; } // // Put packet in output queue. // void NET_SendDelayedPacket (netsrc_t netsrc, int length, void *data, netadr_t to) { int i; for (i = 0; i < CL_MAX_DELAYED_PACKETS; i++) { if (cl_delayed_packets_output[i].time) continue; // busy slot // we found unused slot, copy packet, send it later, later depends of cl_delay_packet memmove(cl_delayed_packets_output[i].data, data, length); cl_delayed_packets_output[i].length = length; cl_delayed_packets_output[i].addr = to; cl_delayed_packets_output[i].time = Sys_DoubleTime() + 0.001 * bound(0, 0.5 * cl_delay_packet.value, CL_MAX_PACKET_DELAY); return; } Con_DPrintf("NET_SendPacketEx: cl_delayed_packets_output overflowed\n"); return; } #endif //============================================================================= // // Geters. // int NET_UDPSVPort (void) { return ntohs(net_local_sv_ipadr.port); } int NET_GetSocket(netsrc_t netsrc, qbool tcp) { if (netsrc == NS_SERVER) { #ifdef CLIENTONLY Sys_Error("NET_GetPacket: Bad netsrc"); return INVALID_SOCKET; #else return tcp ? svs.sockettcp : svs.socketip; #endif } else { #ifdef SERVERONLY Sys_Error("NET_GetPacket: Bad netsrc"); return INVALID_SOCKET; #else return tcp ? cls.sockettcp : cls.socketip; #endif } } //============================================================================= // // Converters. // void NetadrToSockadr (const netadr_t *a, struct sockaddr_storage *s) { memset (s, 0, sizeof(struct sockaddr_in)); ((struct sockaddr_in*)s)->sin_family = AF_INET; ((struct sockaddr_in*)s)->sin_addr.s_addr = *(int *)&a->ip; ((struct sockaddr_in*)s)->sin_port = a->port; } void SockadrToNetadr (const struct sockaddr_storage *s, netadr_t *a) { a->type = NA_IP; *(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr; a->port = ((struct sockaddr_in *)s)->sin_port; return; } //============================================================================= // // Comparators. // qbool NET_CompareBaseAdr (const netadr_t a, const netadr_t b) { #ifndef SERVERONLY if (a.type == NA_LOOPBACK && b.type == NA_LOOPBACK) return true; #endif // FIXME: Should we check a.type == b.type here ??? if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3]) return true; return false; } qbool NET_CompareAdr (const netadr_t a, const netadr_t b) { #ifndef SERVERONLY if (a.type == NA_LOOPBACK && b.type == NA_LOOPBACK) return true; #endif // FIXME: Should we check a.type == b.type here ??? if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] && a.port == b.port) return true; return false; } //============================================================================= // // Printors. // char *NET_AdrToString (const netadr_t a) { static char s[MAX_STRINGS][32]; // 22 should be OK too static int idx = 0; idx %= MAX_STRINGS; #ifndef SERVERONLY if (a.type == NA_LOOPBACK) { snprintf (s[idx], sizeof(s[0]), "loopback"); } else #endif { snprintf (s[idx], sizeof(s[0]), "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs (a.port)); } return s[idx++]; } char *NET_BaseAdrToString (const netadr_t a) { static char s[MAX_STRINGS][32]; // 22 should be OK too static int idx = 0; idx %= MAX_STRINGS; #ifndef SERVERONLY if (a.type == NA_LOOPBACK) { snprintf (s[idx], sizeof(s[0]), "loopback"); } else #endif { snprintf (s[idx], sizeof(s[0]), "%i.%i.%i.%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3]); } return s[idx++]; } // !!! Not used outside of the net.c !!! static qbool NET_StringToSockaddr (const char *s, struct sockaddr_storage *sadr) { struct hostent *h; char *colon; char copy[128]; if (!(*s)) return false; memset (sadr, 0, sizeof(*sadr)); ((struct sockaddr_in *)sadr)->sin_family = AF_INET; ((struct sockaddr_in *)sadr)->sin_port = 0; // can't resolve IP by hostname if hostname was truncated if (strlcpy (copy, s, sizeof(copy)) >= sizeof(copy)) return false; // strip off a trailing :port if present for (colon = copy ; *colon ; colon++) { if (*colon == ':') { *colon = 0; ((struct sockaddr_in *)sadr)->sin_port = htons((short)atoi(colon+1)); } } //this is the wrong way to test. a server name may start with a number. if (copy[0] >= '0' && copy[0] <= '9') { //this is the wrong way to test. a server name may start with a number. *(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr(copy); } else { if (!(h = gethostbyname(copy))) return false; if (h->h_addrtype != AF_INET) return false; *(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0]; } return true; } qbool NET_StringToAdr (const char *s, netadr_t *a) { struct sockaddr_storage sadr; #ifndef SERVERONLY if (!strcmp(s, "local")) { memset(a, 0, sizeof(*a)); a->type = NA_LOOPBACK; return true; } #endif if (!NET_StringToSockaddr (s, &sadr)) return false; SockadrToNetadr (&sadr, a); return true; } /* ============================================================================= LOOPBACK BUFFERS FOR LOCAL PLAYER ============================================================================= */ qbool NET_GetLoopPacket (netsrc_t netsrc, netadr_t *from, sizebuf_t *message) { int i; loopback_t *loop; loop = &loopbacks[netsrc]; if (loop->send - loop->get > MAX_LOOPBACK) loop->get = loop->send - MAX_LOOPBACK; if (loop->get >= loop->send) return false; i = loop->get & (MAX_LOOPBACK-1); loop->get++; if (message->maxsize < loop->msgs[i].datalen) Sys_Error("NET_SendLoopPacket: Loopback buffer was too big"); memcpy (message->data, loop->msgs[i].data, loop->msgs[i].datalen); message->cursize = loop->msgs[i].datalen; memset (from, 0, sizeof(*from)); from->type = NA_LOOPBACK; return true; } void NET_SendLoopPacket (netsrc_t netsrc, int length, void *data, netadr_t to) { int i; loopback_t *loop; loop = &loopbacks[netsrc ^ 1]; i = loop->send & (MAX_LOOPBACK - 1); loop->send++; if (length > (int) sizeof(loop->msgs[i].data)) Sys_Error ("NET_SendLoopPacket: length > %d", (int) sizeof(loop->msgs[i].data)); memcpy (loop->msgs[i].data, data, length); loop->msgs[i].datalen = length; } void NET_ClearLoopback (void) { loopbacks[0].send = loopbacks[0].get = 0; loopbacks[1].send = loopbacks[1].get = 0; } //============================================================================= // // SV TCP connection. // // allocate, may link it in, if requested svtcpstream_t *sv_tcp_connection_new(int sock, netadr_t from, char *buf, int buf_len, qbool link) { svtcpstream_t *st = NULL; st = Q_malloc(sizeof(svtcpstream_t)); st->waitingforprotocolconfirmation = true; st->socketnum = sock; st->remoteaddr = from; if (buf_len > 0 && buf_len < sizeof(st->inbuffer)) { memmove(st->inbuffer, buf, buf_len); st->inlen = buf_len; } else st->drop = true; // yeah, funny // link it in if requested if (link) { st->next = svs.tcpstreams; svs.tcpstreams = st; } return st; } // free data, may unlink it out if requested static void sv_tcp_connection_free(svtcpstream_t *drop, qbool unlink) { if (!drop) return; // someone kidding us // unlink if requested if (unlink) { if (svs.tcpstreams == drop) { svs.tcpstreams = svs.tcpstreams->next; } else { svtcpstream_t *st = NULL; for (st = svs.tcpstreams; st; st = st->next) { if (st->next == drop) { st->next = st->next->next; break; } } } } // well, think socket may be zero, but most of the time zero is stdin fd, so better not close it if (drop->socketnum && drop->socketnum != INVALID_SOCKET) closesocket(drop->socketnum); Q_free(drop); } int sv_tcp_connection_count(void) { svtcpstream_t *st = NULL; int cnt = 0; for (st = svs.tcpstreams; st; st = st->next) cnt++; return cnt; } //============================================================================= qbool NET_GetUDPPacket (netsrc_t netsrc, netadr_t *from_adr, sizebuf_t *message) { int ret, err; struct sockaddr_storage from = {0}; socklen_t fromlen; int socket = NET_GetSocket(netsrc, false); if (socket == INVALID_SOCKET) return false; fromlen = sizeof(from); ret = recvfrom (socket, (char *)message->data, message->maxsize, 0, (struct sockaddr *)&from, &fromlen); SockadrToNetadr (&from, from_adr); if (ret == -1) { err = qerrno; if (err == EWOULDBLOCK) return false; // common error, does not spam in logs. if (err == EMSGSIZE) { Con_DPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (*from_adr)); return false; } if (err == ECONNABORTED || err == ECONNRESET) { Con_DPrintf ("Connection lost or aborted\n"); return false; } Con_Printf ("NET_GetPacket: recvfrom: (%i): %s\n", err, strerror(err)); return false; } if (ret >= message->maxsize) { Con_Printf ("Oversize packet from %s\n", NET_AdrToString (*from_adr)); return false; } message->cursize = ret; return ret; } #ifndef SERVERONLY qbool NET_GetTCPPacket_CL (netsrc_t netsrc, netadr_t *from, sizebuf_t *message) { int ret, err; if (netsrc != NS_CLIENT || cls.sockettcp == INVALID_SOCKET) return false; ret = recv(cls.sockettcp, (char *) cls.tcpinbuffer + cls.tcpinlen, sizeof(cls.tcpinbuffer) - cls.tcpinlen, 0); // FIXME: should we check for ret == 0 for disconnect ??? if (ret == -1) { err = qerrno; if (err == EWOULDBLOCK) { ret = 0; // hint for code below that it was not cricial error. } else if (err == ECONNABORTED || err == ECONNRESET) { Con_Printf ("Connection lost or aborted\n"); //server died/connection lost. } else { Con_Printf ("NET_GetPacket: Error (%i): %s\n", err, strerror(err)); } if (ret) { // error detected, close socket then. closesocket(cls.sockettcp); cls.sockettcp = INVALID_SOCKET; return false; } } cls.tcpinlen += ret; if (cls.tcpinlen < 2) return false; message->cursize = BigShort(*(short*)cls.tcpinbuffer); if (message->cursize >= message->maxsize) { closesocket(cls.sockettcp); cls.sockettcp = INVALID_SOCKET; Con_Printf ("Warning: Oversize packet from %s\n", NET_AdrToString (cls.sockettcpdest)); return false; } if (message->cursize + 2 > cls.tcpinlen) { //not enough buffered to read a packet out of it. return false; } memcpy(message->data, cls.tcpinbuffer + 2, message->cursize); memmove(cls.tcpinbuffer, cls.tcpinbuffer + message->cursize + 2, cls.tcpinlen - (message->cursize + 2)); cls.tcpinlen -= message->cursize + 2; *from = cls.sockettcpdest; return true; } #endif #ifndef CLIENTONLY qbool NET_GetTCPPacket_SV (netsrc_t netsrc, netadr_t *from, sizebuf_t *message) { int ret; float timeval = Sys_DoubleTime(); svtcpstream_t *st = NULL, *next = NULL; if (netsrc != NS_SERVER) return false; for (st = svs.tcpstreams; st; st = next) { next = st->next; *from = st->remoteaddr; if (st->socketnum == INVALID_SOCKET || st->drop) { sv_tcp_connection_free(st, true); // free and unlink continue; } //due to the above checks about invalid sockets, the socket is always open for st below. // check for client timeout if (st->timeouttime < timeval) { st->drop = true; continue; } ret = recv(st->socketnum, st->inbuffer+st->inlen, sizeof(st->inbuffer)-st->inlen, 0); if (ret == 0) { // connection closed st->drop = true; continue; } else if (ret == -1) { int err = qerrno; if (err == EWOULDBLOCK) { ret = 0; // it's OK } else { if (err == ECONNABORTED || err == ECONNRESET) { Con_DPrintf ("Connection lost or aborted\n"); //server died/connection lost. } else { Con_DPrintf ("NET_GetPacket: Error (%i): %s\n", err, strerror(err)); } st->drop = true; continue; } } else { // update timeout st->timeouttime = Sys_DoubleTime() + 10; } st->inlen += ret; if (st->waitingforprotocolconfirmation) { // not enough data if (st->inlen < 6) continue; if (strncmp(st->inbuffer, "qizmo\n", 6)) { Con_Printf ("Unknown TCP client\n"); st->drop = true; continue; } // remove leading 6 bytes memmove(st->inbuffer, st->inbuffer+6, st->inlen - (6)); st->inlen -= 6; // confirmed st->waitingforprotocolconfirmation = false; } // need two bytes for packet len if (st->inlen < 2) continue; message->cursize = BigShort(*(short*)st->inbuffer); if (message->cursize < 0) { message->cursize = 0; Con_Printf ("Warning: malformed message from %s\n", NET_AdrToString (*from)); st->drop = true; continue; } if (message->cursize >= message->maxsize) { Con_Printf ("Warning: Oversize packet from %s\n", NET_AdrToString (*from)); st->drop = true; continue; } if (message->cursize + 2 > st->inlen) { //not enough buffered to read a packet out of it. continue; } memcpy(message->data, st->inbuffer + 2, message->cursize); memmove(st->inbuffer, st->inbuffer + message->cursize + 2, st->inlen - (message->cursize + 2)); st->inlen -= message->cursize + 2; return true; // we got packet! } return false; // no packet received. } #endif qbool NET_GetPacketEx (netsrc_t netsrc, qbool delay) { #ifndef SERVERONLY if (delay) return NET_GetDelayedPacket(netsrc, &net_from, &net_message); #endif #ifndef SERVERONLY if (NET_GetLoopPacket(netsrc, &net_from, &net_message)) return true; #endif if (NET_GetUDPPacket(netsrc, &net_from, &net_message)) return true; // TCPCONNECT --> #ifndef SERVERONLY if (netsrc == NS_CLIENT && cls.sockettcp != INVALID_SOCKET && NET_GetTCPPacket_CL(netsrc, &net_from, &net_message)) return true; #endif #ifndef CLIENTONLY if (netsrc == NS_SERVER && svs.tcpstreams && NET_GetTCPPacket_SV(netsrc, &net_from, &net_message)) return true; #endif // <--TCPCONNECT return false; } qbool NET_GetPacket (netsrc_t netsrc) { #ifdef SERVERONLY qbool delay = false; #else qbool delay = (netsrc == NS_CLIENT && cl_delay_packet.integer); #endif return NET_GetPacketEx (netsrc, delay); } //============================================================================= #ifndef SERVERONLY qbool NET_SendTCPPacket_CL (netsrc_t netsrc, int length, void *data, netadr_t to) { unsigned short slen; if (netsrc != NS_CLIENT || cls.sockettcp == INVALID_SOCKET) return false; if (!NET_CompareAdr(to, cls.sockettcpdest)) return false; // this goes to the server so send it via TCP. slen = BigShort((unsigned short)length); // FIXME: CHECK send() result, we use NON BLOCKIN MODE, FFS! send(cls.sockettcp, (char*)&slen, sizeof(slen), 0); send(cls.sockettcp, data, length, 0); return true; } #endif #ifndef CLIENTONLY qbool NET_SendTCPPacket_SV (netsrc_t netsrc, int length, void *data, netadr_t to) { svtcpstream_t *st; if (netsrc != NS_SERVER) return false; for (st = svs.tcpstreams; st; st = st->next) { if (st->socketnum == INVALID_SOCKET) continue; if (NET_CompareAdr(to, st->remoteaddr)) { int sent; unsigned short slen = BigShort((unsigned short)length); if (st->outlen + length + sizeof(slen) >= sizeof(st->outbuffer)) { // not enough space, we overflowed break; // well, quake should resist to some packet lost.. so we just drop that packet. } // put data in buffer memmove(st->outbuffer + st->outlen, (char*)&slen, sizeof(slen)); st->outlen += sizeof(slen); memmove(st->outbuffer + st->outlen, data, length); st->outlen += length; sent = send(st->socketnum, st->outbuffer, st->outlen, 0); if (sent == 0) { // think it's OK } else if (sent > 0) //we put some data through { //move up the buffer st->outlen -= sent; memmove(st->outbuffer, st->outbuffer + sent, st->outlen); } else { //error of some kind. would block or something if (qerrno != EWOULDBLOCK && qerrno != EAGAIN) { st->drop = true; // something cricial, drop than } } break; } } // 'st' will be not zero, if we found 'to' in 'svs.tcpstreams'. // That does not mean we actualy send packet, since there case of overflow, but who cares, // all is matter that we found such 'to' and tried to send packet. return !!st; } #endif qbool NET_SendUDPPacket (netsrc_t netsrc, int length, void *data, netadr_t to) { struct sockaddr_storage addr; int ret; int socket = NET_GetSocket(netsrc, false); if (socket == INVALID_SOCKET) return false; NetadrToSockadr (&to, &addr); ret = sendto (socket, data, length, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); if (ret == -1) { int err = qerrno; if (err == EWOULDBLOCK || err == ECONNREFUSED || err == EADDRNOTAVAIL) ; // nothing else Con_Printf ("NET_SendPacket: sendto: (%i): %s %i\n", err, strerror(err), socket); } return true; } void NET_SendPacketEx (netsrc_t netsrc, int length, void *data, netadr_t to, qbool delay) { #ifndef SERVERONLY if (delay) { NET_SendDelayedPacket (netsrc, length, data, to); return; } #endif #ifndef SERVERONLY if (to.type == NA_LOOPBACK) { NET_SendLoopPacket (netsrc, length, data, to); return; } #endif // TCPCONNECT --> #ifndef SERVERONLY if (netsrc == NS_CLIENT && cls.sockettcp != INVALID_SOCKET && NET_SendTCPPacket_CL(netsrc, length, data, to)) return; #endif #ifndef CLIENTONLY if (netsrc == NS_SERVER && svs.tcpstreams && NET_SendTCPPacket_SV(netsrc, length, data, to)) return; #endif // <--TCPCONNECT NET_SendUDPPacket(netsrc, length, data, to); } void NET_SendPacket (netsrc_t netsrc, int length, void *data, netadr_t to) { #ifdef SERVERONLY qbool delay = false; #else qbool delay = (netsrc == NS_CLIENT && cl_delay_packet.integer); #endif NET_SendPacketEx (netsrc, length, data, to, delay); } //============================================================================= qbool TCP_Set_KEEPALIVE(int sock) { int iOptVal = 1; if (sock == INVALID_SOCKET) { Con_Printf("TCP_Set_KEEPALIVE: invalid socket\n"); return false; } if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void*)&iOptVal, sizeof(iOptVal)) == SOCKET_ERROR) { Con_Printf ("TCP_Set_KEEPALIVE: setsockopt: (%i): %s\n", qerrno, strerror (qerrno)); return false; } #if defined(__linux__) // The time (in seconds) the connection needs to remain idle before TCP starts sending keepalive probes, // if the socket option SO_KEEPALIVE has been set on this socket. iOptVal = 60; if (setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, (void*)&iOptVal, sizeof(iOptVal)) == -1) { Con_Printf ("TCP_Set_KEEPALIVE: setsockopt TCP_KEEPIDLE: (%i): %s\n", qerrno, strerror(qerrno)); return false; } // The time (in seconds) between individual keepalive probes. iOptVal = 30; if (setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, (void*)&iOptVal, sizeof(iOptVal)) == -1) { Con_Printf ("TCP_Set_KEEPALIVE: setsockopt TCP_KEEPINTVL: (%i): %s\n", qerrno, strerror(qerrno)); return false; } // The maximum number of keepalive probes TCP should send before dropping the connection. iOptVal = 6; if (setsockopt(sock, SOL_TCP, TCP_KEEPCNT, (void*)&iOptVal, sizeof(iOptVal)) == -1) { Con_Printf ("TCP_Set_KEEPALIVE: setsockopt TCP_KEEPCNT: (%i): %s\n", qerrno, strerror(qerrno)); return false; } #else // FIXME: windows, bsd etc... #endif return true; } int TCP_OpenStream (netadr_t remoteaddr) { unsigned long _true = true; int newsocket; int temp; struct sockaddr_storage qs; NetadrToSockadr(&remoteaddr, &qs); temp = sizeof(struct sockaddr_in); if ((newsocket = socket (((struct sockaddr_in*)&qs)->sin_family, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) { Con_Printf ("TCP_OpenStream: socket: (%i): %s\n", qerrno, strerror(qerrno)); return INVALID_SOCKET; } if (connect (newsocket, (struct sockaddr *)&qs, temp) == INVALID_SOCKET) { Con_Printf ("TCP_OpenStream: connect: (%i): %s\n", qerrno, strerror(qerrno)); closesocket(newsocket); return INVALID_SOCKET; } #ifndef _WIN32 if ((fcntl (newsocket, F_SETFL, O_NONBLOCK)) == -1) { // O'Rly?! @@@ Con_Printf ("TCP_OpenStream: fcntl: (%i): %s\n", qerrno, strerror(qerrno)); closesocket(newsocket); return INVALID_SOCKET; } #endif if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) { // make asynchronous Con_Printf ("TCP_OpenStream: ioctl: (%i): %s\n", qerrno, strerror(qerrno)); closesocket(newsocket); return INVALID_SOCKET; } return newsocket; } int TCP_OpenListenSocket (unsigned short int port) { int newsocket; struct sockaddr_in address = {0}; unsigned long nonblocking = true; int i; if ((newsocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) { Con_Printf ("TCP_OpenListenSocket: socket: (%i): %s\n", qerrno, strerror(qerrno)); return INVALID_SOCKET; } if (ioctlsocket (newsocket, FIONBIO, &nonblocking) == -1) { // make asynchronous Con_Printf ("TCP_OpenListenSocket: ioctl: (%i): %s\n", qerrno, strerror(qerrno)); closesocket(newsocket); return INVALID_SOCKET; } #ifdef __APPLE__ address.sin_len = sizeof(address); // apple are special... #endif address.sin_family = AF_INET; // check for interface binding option if ((i = COM_CheckParm("-ip")) != 0 && i < COM_Argc()) { address.sin_addr.s_addr = inet_addr(COM_Argv(i+1)); Con_DPrintf ("Binding to IP Interface Address of %s\n", inet_ntoa(address.sin_addr)); } else { address.sin_addr.s_addr = INADDR_ANY; } if (port == PORT_ANY) address.sin_port = 0; else address.sin_port = htons(port); if (bind (newsocket, (void *)&address, sizeof(address)) == -1) { Con_Printf ("TCP_OpenListenSocket: bind: (%i): %s\n", qerrno, strerror(qerrno)); closesocket(newsocket); return INVALID_SOCKET; } if (listen (newsocket, 2) == INVALID_SOCKET) { Con_Printf ("TCP_OpenListenSocket: listen: (%i): %s\n", qerrno, strerror(qerrno)); closesocket(newsocket); return INVALID_SOCKET; } if (!TCP_Set_KEEPALIVE(newsocket)) { Con_Printf ("TCP_OpenListenSocket: TCP_Set_KEEPALIVE: failed\n"); closesocket(newsocket); return INVALID_SOCKET; } return newsocket; } int UDP_OpenSocket (unsigned short int port) { int newsocket; struct sockaddr_in address; unsigned long _true = true; int i; if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) { Con_Printf ("UDP_OpenSocket: socket: (%i): %s\n", qerrno, strerror(qerrno)); return INVALID_SOCKET; } #ifndef _WIN32 if ((fcntl (newsocket, F_SETFL, O_NONBLOCK)) == -1) { // O'Rly?! @@@ Con_Printf ("UDP_OpenSocket: fcntl: (%i): %s\n", qerrno, strerror(qerrno)); closesocket(newsocket); return INVALID_SOCKET; } #endif if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) { // make asynchronous Con_Printf ("UDP_OpenSocket: ioctl: (%i): %s\n", qerrno, strerror(qerrno)); closesocket(newsocket); return INVALID_SOCKET; } address.sin_family = AF_INET; // check for interface binding option if ((i = COM_CheckParm("-ip")) != 0 && i < COM_Argc()) { address.sin_addr.s_addr = inet_addr(COM_Argv(i+1)); Con_DPrintf ("Binding to IP Interface Address of %s\n", inet_ntoa(address.sin_addr)); } else { address.sin_addr.s_addr = INADDR_ANY; } if (port == PORT_ANY) address.sin_port = 0; else address.sin_port = htons(port); if (bind (newsocket, (void *)&address, sizeof(address)) == -1) { Con_Printf ("UDP_OpenSocket: bind: (%i): %s\n", qerrno, strerror(qerrno)); closesocket(newsocket); return INVALID_SOCKET; } return newsocket; } qbool NET_Sleep(int msec, qbool stdinissocket) { struct timeval timeout; fd_set fdset; qbool stdin_ready = false; int maxfd = 0; FD_ZERO (&fdset); if (stdinissocket) { FD_SET (0, &fdset); // stdin is processed too (tends to be socket 0) maxfd = max(0, maxfd); } if (svs.socketip != INVALID_SOCKET) { FD_SET(svs.socketip, &fdset); // network socket maxfd = max(svs.socketip, maxfd); } timeout.tv_sec = msec/1000; timeout.tv_usec = (msec%1000)*1000; switch (select(maxfd + 1, &fdset, NULL, NULL, &timeout)) { case -1: break; case 0: break; default: if (stdinissocket) stdin_ready = FD_ISSET (0, &fdset); break; } return stdin_ready; } void NET_GetLocalAddress (int socket, netadr_t *out) { char buff[512]; struct sockaddr_storage address; size_t namelen; netadr_t adr = {0}; qbool notvalid = false; strlcpy (buff, "localhost", sizeof (buff)); gethostname (buff, sizeof (buff)); buff[sizeof(buff) - 1] = 0; if (!NET_StringToAdr (buff, &adr)) //urm NET_StringToAdr ("127.0.0.1", &adr); namelen = sizeof(address); if (getsockname (socket, (struct sockaddr *)&address, (socklen_t *)&namelen) == -1) { notvalid = true; NET_StringToSockaddr("0.0.0.0", (struct sockaddr_storage *)&address); // Sys_Error ("NET_Init: getsockname:", strerror(qerrno)); } SockadrToNetadr(&address, out); if (!*(int*)out->ip) //socket was set to auto *(int *)out->ip = *(int *)adr.ip; //change it to what the machine says it is, rather than the socket. #ifndef SERVERONLY if (notvalid) Com_Printf_State (PRINT_FAIL, "Couldn't detect local ip\n"); else Com_Printf_State (PRINT_OK, "IP address %s\n", NET_AdrToString (*out)); #endif } void NET_Init (void) { #ifdef _WIN32 WORD wVersionRequested; int r; wVersionRequested = MAKEWORD(1, 1); r = WSAStartup (wVersionRequested, &winsockdata); if (r) Sys_Error ("Winsock initialization failed."); #endif // init the message buffer SZ_Init (&net_message, net_message_buffer, sizeof(net_message_buffer)); Con_DPrintf("UDP Initialized\n"); #ifndef SERVERONLY cls.socketip = INVALID_SOCKET; // TCPCONNECT --> cls.sockettcp = INVALID_SOCKET; // <--TCPCONNECT #endif #ifndef CLIENTONLY Cvar_Register (&sv_local_addr); svs.socketip = INVALID_SOCKET; // TCPCONNECT --> svs.sockettcp = INVALID_SOCKET; // <--TCPCONNECT #endif #ifdef SERVERONLY // As client+server we init it in SV_SpawnServer(). // As serveronly we do it here. NET_InitServer(); #endif } void NET_Shutdown (void) { #ifndef CLIENTONLY NET_CloseServer(); #endif #ifndef SERVERONLY NET_CloseClient(); #endif #ifdef _WIN32 WSACleanup (); #endif } #ifndef SERVERONLY void NET_InitClient(void) { int port = PORT_CLIENT; int p; p = COM_CheckParm ("-clientport"); if (p && p < COM_Argc()) { port = atoi(COM_Argv(p+1)); } if (cls.socketip == INVALID_SOCKET) cls.socketip = UDP_OpenSocket (port); if (cls.socketip == INVALID_SOCKET) cls.socketip = UDP_OpenSocket (PORT_ANY); // any dynamic port if (cls.socketip == INVALID_SOCKET) Sys_Error ("Couldn't allocate client socket"); // determine my name & address NET_GetLocalAddress (cls.socketip, &net_local_cl_ipadr); Com_Printf_State (PRINT_OK, "Client port Initialized\n"); } void NET_CloseClient (void) { if (cls.socketip != INVALID_SOCKET) { closesocket(cls.socketip); cls.socketip = INVALID_SOCKET; } // TCPCONNECT --> // FIXME: is it OK? Probably we should send disconnect? if (cls.sockettcp != INVALID_SOCKET) { closesocket(cls.sockettcp); cls.sockettcp = INVALID_SOCKET; } // <--TCPCONNECT } #endif #ifndef CLIENTONLY // // Open server TCP port. // NOTE: Zero port will actually close already opened port. // void NET_InitServer_TCP(unsigned short int port) { // close socket first. if (svs.sockettcp != INVALID_SOCKET) { Con_Printf("Server TCP port closed\n"); closesocket(svs.sockettcp); svs.sockettcp = INVALID_SOCKET; net_local_sv_tcpipadr.type = NA_INVALID; } if (port) { svs.sockettcp = TCP_OpenListenSocket (port); if (svs.sockettcp != INVALID_SOCKET) { // get local address. NET_GetLocalAddress (svs.sockettcp, &net_local_sv_tcpipadr); Con_Printf("Opening server TCP port %u\n", (unsigned int)port); } else { Con_Printf("Failed to open server TCP port %u\n", (unsigned int)port); } } } void NET_InitServer (void) { int port = PORT_SERVER; int p; p = COM_CheckParm ("-port"); if (p && p < COM_Argc()) { port = atoi(COM_Argv(p+1)); } if (svs.socketip == INVALID_SOCKET) { svs.socketip = UDP_OpenSocket (port); } if (svs.socketip != INVALID_SOCKET) { NET_GetLocalAddress (svs.socketip, &net_local_sv_ipadr); Cvar_SetROM (&sv_local_addr, NET_AdrToString (net_local_sv_ipadr)); } else { // FIXME: is it right??? Cvar_SetROM (&sv_local_addr, ""); } // TCPCONNECT --> #if 0 // qqshka: TCP port shared with QTV now, so it opened outside here. p = COM_CheckParm ("-tcpport"); if (p && p < COM_Argc()) { NET_InitServer_TCP(atoi(COM_Argv(p+1))); } #endif // <-- TCPCONNECT if (svs.socketip == INVALID_SOCKET) { #ifdef SERVERONLY Sys_Error #else Con_Printf #endif ("WARNING: Couldn't allocate server socket\n"); } } void NET_CloseServer (void) { if (svs.socketip != INVALID_SOCKET) { closesocket(svs.socketip); svs.socketip = INVALID_SOCKET; } net_local_sv_ipadr.type = NA_LOOPBACK; // FIXME: why not NA_INVALID? // TCPCONNECT --> NET_InitServer_TCP(0); // <--TCPCONNECT } #endif mvdsv-0.35/src/net.h000066400000000000000000000151631427146041000143330ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // net.h -- quake's interface to the networking layer #ifndef __NET_H__ #define __NET_H__ #include #define MAX_DUPLICATE_PACKETS (3) #ifdef _WIN32 // // OS specific includes. // #include #include // // OS specific types definition. // typedef int socklen_t; typedef SOCKET socket_t; // // OS specific definitions. // #ifdef EWOULDBLOCK #undef EWOULDBLOCK #endif #ifdef EMSGSIZE #undef EMSGSIZE #endif #ifdef ECONNRESET #undef ECONNRESET #endif #ifdef ECONNABORTED #undef ECONNABORTED #endif #ifdef ECONNREFUSED #undef ECONNREFUSED #endif #ifdef EADDRNOTAVAIL #undef EADDRNOTAVAIL #endif #ifdef EAFNOSUPPORT #undef EAFNOSUPPORT #endif #define EWOULDBLOCK WSAEWOULDBLOCK #define EMSGSIZE WSAEMSGSIZE #define ECONNRESET WSAECONNRESET #define ECONNABORTED WSAECONNABORTED #define ECONNREFUSED WSAECONNREFUSED #define EADDRNOTAVAIL WSAEADDRNOTAVAIL #define EAFNOSUPPORT WSAEAFNOSUPPORT #define qerrno WSAGetLastError() #else //_WIN32 // // OS specific includes. // #include #include #include #include #include #include #include #include #include #include #include #ifdef __sun__ #include #endif //__sun__ #ifdef NeXT #include #endif //NeXT // // OS specific types definition. // typedef int socket_t; // // OS specific definitions. // #define qerrno errno #define closesocket close #define ioctlsocket ioctl #endif //_WIN32 // // common definitions. // #ifndef INVALID_SOCKET #define INVALID_SOCKET -1 #endif #ifndef SOCKET_ERROR #define SOCKET_ERROR -1 #endif #define PORT_ANY ((unsigned short int)0xFFFF) typedef enum {NA_INVALID, NA_LOOPBACK, NA_IP} netadrtype_t; typedef enum {NS_CLIENT, NS_SERVER} netsrc_t; typedef struct { netadrtype_t type; byte ip[4]; unsigned short port; } netadr_t; extern netadr_t net_local_sv_ipadr; extern netadr_t net_local_sv_tcpipadr; extern netadr_t net_local_cl_ipadr; extern netadr_t net_from; // address of who sent the packet extern sizebuf_t net_message; // convert netadrt_t to sockaddr_storage. void NetadrToSockadr (const netadr_t *a, struct sockaddr_storage *s); // convert sockaddr_storage to netadrt_t. void SockadrToNetadr (const struct sockaddr_storage *s, netadr_t *a); // compare netart_t. qbool NET_CompareAdr (const netadr_t a, const netadr_t b); // compare netart_t, ignore port. qbool NET_CompareBaseAdr (const netadr_t a, const netadr_t b); // print netadr_t as string, xxx.xxx.xxx.xxx:xxxxx notation. char *NET_AdrToString (const netadr_t a); // print netadr_t as string, port skipped, xxx.xxx.xxx.xxx notation. char *NET_BaseAdrToString (const netadr_t a); // convert/resolve IP/DNS to netadr_t. qbool NET_StringToAdr (const char *s, netadr_t *a); void NET_Init (void); void NET_Shutdown (void); void NET_InitClient (void); void NET_CloseClient (void); void NET_InitServer (void); void NET_CloseServer (void); qbool NET_GetPacket (netsrc_t sock); void NET_SendPacket (netsrc_t sock, int length, void *data, netadr_t to); void NET_GetLocalAddress (int socket, netadr_t *out); void NET_ClearLoopback (void); qbool NET_Sleep(int msec, qbool stdinissocket); // GETER: return port of UDP server socket. int NET_UDPSVPort (void); // GETER: return client/server UDP/TCP socket. int NET_GetSocket(netsrc_t netsrc, qbool tcp); // open server TCP socket. void NET_InitServer_TCP(unsigned short int port); // UTILITY: set KEEPALIVE option on TCP socket (useful for faster timeout detection). qbool TCP_Set_KEEPALIVE(int sock); // UTILITY: open TCP socket for remove address (useful for client connection). int TCP_OpenStream (netadr_t remoteaddr); // UTILITY: open TCP listen socket (useful for server). int TCP_OpenListenSocket (unsigned short int port); // UTILITY: open UDP listen socket (useful for server). int UDP_OpenSocket (unsigned short int port); //============================================================================ // // netchan related. // #define OLD_AVG 0.99 // total = oldtotal*OLD_AVG + new*(1-OLD_AVG) #define MAX_LATENT 32 typedef struct { netsrc_t sock; qbool fatal_error; int dropped; // between last packet and previous float last_received; // for timeouts // the statistics are cleared at each client begin, because // the server connecting process gives a bogus picture of the data float frame_latency; // rolling average float frame_rate; int drop_count; // dropped packets, cleared each level int good_count; // cleared each level netadr_t remote_address; int qport; // bandwidth estimator double cleartime; // if curtime > nc->cleartime, free to go double rate; // seconds / byte int dupe; // duplicate packets to send (0 = no duplicates, as normal) // sequencing variables int incoming_sequence; int incoming_acknowledged; int incoming_reliable_acknowledged; // single bit int incoming_reliable_sequence; // single bit, maintained local int outgoing_sequence; int reliable_sequence; // single bit int last_reliable_sequence; // sequence number of last send // reliable staging and holding areas sizebuf_t message; // writing buffer to send to server byte message_buf[MAX_MSGLEN]; int reliable_length; byte reliable_buf[MAX_MSGLEN]; // unacked reliable message // time and size data to calculate bandwidth int outgoing_size[MAX_LATENT]; double outgoing_time[MAX_LATENT]; } netchan_t; void Netchan_Init (void); void Netchan_Transmit (netchan_t *chan, int length, byte *data); void Netchan_OutOfBand (netsrc_t sock, netadr_t adr, int length, byte *data); void Netchan_OutOfBandPrint (netsrc_t sock, netadr_t adr, char *format, ...); qbool Netchan_Process (netchan_t *chan); void Netchan_Setup (netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int mtu); qbool Netchan_CanPacket (netchan_t *chan); qbool Netchan_CanReliable (netchan_t *chan); #endif /* !__NET_H__ */ mvdsv-0.35/src/net_chan.c000066400000000000000000000264761427146041000153300ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifdef SERVERONLY #include "qwsvdef.h" #else #include #include "quakedef.h" #include "server.h" #endif #define PACKET_HEADER 8 /* packet header ------------- 31 sequence 1 does this message contain a reliable payload 31 acknowledge sequence 1 acknowledge receipt of even/odd message 16 qport The remote connection never knows if it missed a reliable message, the local side detects that it has been dropped by seeing a sequence acknowledge higher than the last reliable sequence, but without the correct evon/odd bit for the reliable set. If the sender notices that a reliable message has been dropped, it will be retransmitted. It will not be retransmitted again until a message after the retransmit has been acknowledged and the reliable still failed to get there. If the sequence number is -1, the packet should be handled without a netcon. The reliable message can be added to at any time by doing MSG_Write* (&netchan->message, ). If the message buffer is overflowed, either by a single message, or by multiple frames worth piling up while the last reliable transmit goes unacknowledged, the netchan signals a fatal error. Reliable messages are always placed first in a packet, then the unreliable message is included if there is sufficient room. To the receiver, there is no distinction between the reliable and unreliable parts of the message, they are just processed out as a single larger message. Illogical packet sequence numbers cause the packet to be dropped, but do not kill the connection. This, combined with the tight window of valid reliable acknowledgement numbers provides protection against malicious address spoofing. The qport field is a workaround for bad address translating routers that sometimes remap the client's source port on a packet during gameplay. If the base part of the net address matches and the qport matches, then the channel matches even if the IP port differs. The IP port should be updated to the new value before sending out any replies. */ cvar_t showpackets = {"showpackets", "0"}; cvar_t showdrop = {"showdrop", "0"}; #ifndef SERVERONLY cvar_t qport = {"qport", "0"}; #endif /* =============== Netchan_Init =============== */ void Netchan_Init (void) { #ifndef SERVERONLY int port = 0xffff; port &= rand(); #endif // SERVERONLY Cvar_SetCurrentGroup(CVAR_GROUP_SCREEN); Cvar_Register (&showpackets); Cvar_Register (&showdrop); Cvar_SetCurrentGroup(CVAR_GROUP_NO_GROUP); #ifndef SERVERONLY Cvar_Register (&qport); Cvar_SetValue (&qport, port); #endif Cvar_ResetCurrentGroup(); } /* =============== Netchan_OutOfBand Sends an out-of-band datagram ================ */ void Netchan_OutOfBand (netsrc_t sock, netadr_t adr, int length, byte *data) { sizebuf_t send; byte send_buf[MAX_MSGLEN + PACKET_HEADER]; // write the packet header SZ_Init (&send, send_buf, sizeof(send_buf)); MSG_WriteLong (&send, -1); // -1 sequence means out of band SZ_Write (&send, data, length); // send the datagram #ifndef SERVERONLY //zoid, no input in demo playback mode if (!cls.demoplayback) #endif NET_SendPacket (sock, send.cursize, send.data, adr); } /* =============== Netchan_OutOfBandPrint Sends a text message in an out-of-band datagram ================ */ void Netchan_OutOfBandPrint (netsrc_t sock, netadr_t adr, char *format, ...) { va_list argptr; char string[8192]; va_start (argptr, format); vsnprintf (string, sizeof(string), format, argptr); va_end (argptr); Netchan_OutOfBand (sock, adr, strlen(string), (byte *)string); } /* ============== Netchan_Setup called to open a channel to a remote system ============== */ void Netchan_Setup (netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int mtu) { if (mtu < 1) { mtu = (int)sizeof(chan->message_buf); // OLD way, backward compatibility. } else { mtu -= PACKET_HEADER; // new way. } memset (chan, 0, sizeof (*chan)); chan->sock = sock; chan->remote_address = adr; chan->qport = qport; chan->last_received = curtime; chan->rate = 1.0/2500; SZ_InitEx (&chan->message, chan->message_buf, bound(min(MIN_MTU, (int)sizeof(chan->message_buf)), mtu, (int)sizeof(chan->message_buf)), true); } /* =============== Netchan_CanPacket Returns true if the bandwidth choke isn't active ================ */ #define MAX_BACKUP 400 qbool Netchan_CanPacket (netchan_t *chan) { #ifndef SERVERONLY if (chan->remote_address.type == NA_LOOPBACK) return true; // unlimited bandwidth for local client #endif return (chan->cleartime < curtime + MAX_BACKUP * chan->rate) ? true : false; } /* =============== Netchan_CanReliable Returns true if the bandwidth choke isn't ================ */ qbool Netchan_CanReliable (netchan_t *chan) { if (chan->reliable_length) return false; // waiting for ack return Netchan_CanPacket (chan); } /* =============== Netchan_Transmit tries to send an unreliable message to a connection, and handles the transmition / retransmition of the reliable messages. A 0 length will still generate a packet and deal with the reliable messages. ================ */ void Netchan_Transmit (netchan_t *chan, int length, byte *data) { sizebuf_t send; byte send_buf[MAX_MSGLEN + PACKET_HEADER]; qbool send_reliable; unsigned w1, w2; int i; static double last_error_time = 0; // check for message overflow if (chan->message.overflowed) { chan->fatal_error = true; //FIXME: THIS DOES NOTHING if (last_error_time - curtime > 5 || developer.value) { Con_Printf ("%s:Outgoing message overflow\n", NET_AdrToString (chan->remote_address)); last_error_time = curtime; } return; } // if the remote side dropped the last reliable message, resend it send_reliable = false; if (chan->incoming_acknowledged > chan->last_reliable_sequence && chan->incoming_reliable_acknowledged != chan->reliable_sequence) send_reliable = true; // if the reliable transmit buffer is empty, copy the current message out if (!chan->reliable_length && chan->message.cursize) { memcpy (chan->reliable_buf, chan->message_buf, chan->message.cursize); chan->reliable_length = chan->message.cursize; chan->message.cursize = 0; chan->reliable_sequence ^= 1; send_reliable = true; } // write the packet header SZ_Init (&send, send_buf, min(chan->message.maxsize + PACKET_HEADER, (int)sizeof(send_buf))); w1 = chan->outgoing_sequence | (send_reliable<<31); w2 = chan->incoming_sequence | (chan->incoming_reliable_sequence<<31); chan->outgoing_sequence++; MSG_WriteLong (&send, w1); MSG_WriteLong (&send, w2); #ifndef SERVERONLY // send the qport if we are a client if (chan->sock == NS_CLIENT) MSG_WriteShort (&send, chan->qport); #endif // copy the reliable message to the packet first if (send_reliable) { SZ_Write (&send, chan->reliable_buf, chan->reliable_length); chan->last_reliable_sequence = chan->outgoing_sequence; } // add the unreliable part if space is available if (send.maxsize - send.cursize >= length) SZ_Write (&send, data, length); // send the datagram i = chan->outgoing_sequence & (MAX_LATENT-1); chan->outgoing_size[i] = send.cursize; chan->outgoing_time[i] = curtime; i = 1; //for s2c packet multiplication #ifndef SERVERONLY //zoid, no input in demo playback mode if (!cls.demoplayback) #endif { for (i = 0; i <= chan->dupe; ++i) { NET_SendPacket(chan->sock, send.cursize, send.data, chan->remote_address); } } if (chan->cleartime < curtime) { chan->cleartime = curtime + send.cursize * i * chan->rate; } else { chan->cleartime += send.cursize * i * chan->rate; } #ifndef CLIENTONLY if (chan->sock == NS_SERVER && sv.paused) chan->cleartime = curtime; #endif if (showpackets.value) { #ifndef SERVERONLY Print_flags[Print_current] |= PR_TR_SKIP; #endif Con_Printf ("--> s=%i(%i) a=%i(%i) %i\n" , chan->outgoing_sequence , send_reliable , chan->incoming_sequence , chan->incoming_reliable_sequence , send.cursize); } } /* ================= Netchan_Process called when the current net_message is from remote_address modifies net_message so that it points to the packet payload ================= */ qbool Netchan_Process (netchan_t *chan) { unsigned sequence, sequence_ack; unsigned reliable_ack, reliable_message; if ( #ifndef SERVERONLY !cls.demoplayback && #endif !NET_CompareAdr (net_from, chan->remote_address)) return false; // get sequence numbers MSG_BeginReading (); sequence = MSG_ReadLong (); sequence_ack = MSG_ReadLong (); // read the qport if we are a server if (chan->sock == NS_SERVER) MSG_ReadShort (); reliable_message = sequence >> 31; reliable_ack = sequence_ack >> 31; sequence &= ~(1 << 31); sequence_ack &= ~(1 << 31); if (showpackets.value) { #ifndef SERVERONLY Print_flags[Print_current] |= PR_TR_SKIP; #endif Con_Printf ("<-- s=%i(%i) a=%i(%i) %i\n" , sequence , reliable_message , sequence_ack , reliable_ack , net_message.cursize); } // // discard stale or duplicated packets // if (sequence <= (unsigned)chan->incoming_sequence) { if (showdrop.value) { #ifndef SERVERONLY Print_flags[Print_current] |= PR_TR_SKIP; #endif Con_Printf ("%s:Out of order packet %i at %i\n" , NET_AdrToString (chan->remote_address) , sequence , chan->incoming_sequence); } return false; } // // dropped packets don't keep the message from being used // chan->dropped = sequence - (chan->incoming_sequence+1); if (chan->dropped > 0) { chan->drop_count += 1; if (showdrop.value) { #ifndef SERVERONLY Print_flags[Print_current] |= PR_TR_SKIP; #endif Con_Printf ("%s:Dropped %i packets at %i\n" , NET_AdrToString (chan->remote_address) , chan->dropped , sequence); } } // // if the current outgoing reliable message has been acknowledged // clear the buffer to make way for the next // if (reliable_ack == (unsigned)chan->reliable_sequence) chan->reliable_length = 0; // it has been received // // if this message contains a reliable message, bump incoming_reliable_sequence // chan->incoming_sequence = sequence; chan->incoming_acknowledged = sequence_ack; chan->incoming_reliable_acknowledged = reliable_ack; if (reliable_message) chan->incoming_reliable_sequence ^= 1; // // the message can now be read from the current message pointer // update statistics counters // chan->frame_latency = chan->frame_latency * OLD_AVG + (chan->outgoing_sequence - sequence_ack) * (1.0 - OLD_AVG); chan->frame_rate = chan->frame_rate * OLD_AVG + (curtime - chan->last_received) * (1.0 - OLD_AVG); chan->good_count += 1; chan->last_received = curtime; return true; } mvdsv-0.35/src/pcre/000077500000000000000000000000001427146041000143175ustar00rootroot00000000000000mvdsv-0.35/src/pcre/chartables.c000066400000000000000000000170361427146041000166020ustar00rootroot00000000000000/* 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. */ /************************************************* * Perl-Compatible Regular Expressions * *************************************************/ /* This file is automatically written by the dftables auxiliary program. If you edit it by hand, you might like to edit the Makefile to prevent its ever being regenerated. This file is #included in the compilation of pcre.c to build the default character tables which are used when no tables are passed to the compile function. */ static unsigned char pcre_default_tables[] = { /* This table is a lower casing table. */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99,100,101,102,103, 104,105,106,107,108,109,110,111, 112,113,114,115,116,117,118,119, 120,121,122, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103, 104,105,106,107,108,109,110,111, 112,113,114,115,116,117,118,119, 120,121,122,123,124,125,126,127, 128,129,130,131,132,133,134,135, 136,137,138,139,140,141,142,143, 144,145,146,147,148,149,150,151, 152,153,154,155,156,157,158,159, 160,161,162,163,164,165,166,167, 168,169,170,171,172,173,174,175, 176,177,178,179,180,181,182,183, 184,185,186,187,188,189,190,191, 192,193,194,195,196,197,198,199, 200,201,202,203,204,205,206,207, 208,209,210,211,212,213,214,215, 216,217,218,219,220,221,222,223, 224,225,226,227,228,229,230,231, 232,233,234,235,236,237,238,239, 240,241,242,243,244,245,246,247, 248,249,250,251,252,253,254,255, /* This table is a case flipping table. */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99,100,101,102,103, 104,105,106,107,108,109,110,111, 112,113,114,115,116,117,118,119, 120,121,122, 91, 92, 93, 94, 95, 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,123,124,125,126,127, 128,129,130,131,132,133,134,135, 136,137,138,139,140,141,142,143, 144,145,146,147,148,149,150,151, 152,153,154,155,156,157,158,159, 160,161,162,163,164,165,166,167, 168,169,170,171,172,173,174,175, 176,177,178,179,180,181,182,183, 184,185,186,187,188,189,190,191, 192,193,194,195,196,197,198,199, 200,201,202,203,204,205,206,207, 208,209,210,211,212,213,214,215, 216,217,218,219,220,221,222,223, 224,225,226,227,228,229,230,231, 232,233,234,235,236,237,238,239, 240,241,242,243,244,245,246,247, 248,249,250,251,252,253,254,255, /* This table contains bit maps for various character classes. Each map is 32 bytes long and the bits run from the least significant end of each byte. The classes that have their own maps are: space, xdigit, digit, upper, lower, word, graph print, punct, and cntrl. Other classes are built from combinations. */ 0x00,0x3e,0x00,0x00,0x01,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, 0x7e,0x00,0x00,0x00,0x7e,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xfe,0xff,0xff,0x07,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, 0xfe,0xff,0xff,0x87,0xfe,0xff,0xff,0x07, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xfe,0xff,0x00,0xfc, 0x01,0x00,0x00,0xf8,0x01,0x00,0x00,0x78, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* This table identifies various classes of character by individual bits: 0x01 white space character 0x02 letter 0x04 decimal digit 0x08 hexadecimal digit 0x10 alphanumeric or '_' 0x80 regular expression metacharacter or binary zero */ 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 */ 0x00,0x01,0x01,0x00,0x01,0x01,0x00,0x00, /* 8- 15 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */ 0x01,0x00,0x00,0x00,0x80,0x00,0x00,0x00, /* - ' */ 0x80,0x80,0x80,0x80,0x00,0x00,0x80,0x00, /* ( - / */ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c, /* 0 - 7 */ 0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x80, /* 8 - ? */ 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* @ - G */ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* H - O */ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* P - W */ 0x12,0x12,0x12,0x80,0x00,0x00,0x80,0x10, /* X - _ */ 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* ` - g */ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* h - o */ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* p - w */ 0x12,0x12,0x12,0x80,0x80,0x00,0x00,0x00, /* x -127 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 128-135 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 136-143 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144-151 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 152-159 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160-167 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 168-175 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 176-183 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 192-199 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 200-207 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 208-215 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 216-223 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 224-231 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 232-239 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 240-247 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/* 248-255 */ /* End of chartables.c */ mvdsv-0.35/src/pcre/config.h000066400000000000000000000121321427146041000157340ustar00rootroot00000000000000/* 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. */ /* On Unix systems config.in is converted by configure into config.h. PCRE is written in Standard C, but there are a few non-standard things it can cope with, allowing it to run on SunOS4 and other "close to standard" systems. On a non-Unix system you should just copy this file into config.h, and set up the macros the way you need them. You should normally change the definitions of HAVE_STRERROR and HAVE_MEMMOVE to 1. Unfortunately, because of the way autoconf works, these cannot be made the defaults. If your system has bcopy() and not memmove(), change the definition of HAVE_BCOPY instead of HAVE_MEMMOVE. If your system has neither bcopy() nor memmove(), leave them both as 0; an emulation function will be used. */ /* If you are compiling for a system that uses EBCDIC instead of ASCII character codes, define this macro as 1. On systems that can use "configure", this can be done via --enable-ebcdic. */ #ifndef EBCDIC #define EBCDIC 0 #endif /* If you are compiling for a system that needs some magic to be inserted before the definition of an exported function, define this macro to contain the relevant magic. It apears at the start of every exported function. */ #define EXPORT /* Define to empty if the "const" keyword does not work. */ #undef const /* Define to "unsigned" if doesn't define size_t. */ #undef size_t /* The following two definitions are mainly for the benefit of SunOS4, which doesn't have the strerror() or memmove() functions that should be present in all Standard C libraries. The macros HAVE_STRERROR and HAVE_MEMMOVE should normally be defined with the value 1 for other systems, but unfortunately we can't make this the default because "configure" files generated by autoconf will only change 0 to 1; they won't change 1 to 0 if the functions are not found. */ #define HAVE_STRERROR 1 #define HAVE_MEMMOVE 1 /* There are some non-Unix systems that don't even have bcopy(). If this macro is false, an emulation is used. If HAVE_MEMMOVE is set to 1, the value of HAVE_BCOPY is not relevant. */ #define HAVE_BCOPY 0 /* The value of NEWLINE determines the newline character. The default is to leave it up to the compiler, but some sites want to force a particular value. On Unix systems, "configure" can be used to override this default. */ #ifndef NEWLINE #define NEWLINE '\n' #endif /* The value of LINK_SIZE determines the number of bytes used to store links as offsets within the compiled regex. The default is 2, which allows for compiled patterns up to 64K long. This covers the vast majority of cases. However, PCRE can also be compiled to use 3 or 4 bytes instead. This allows for longer patterns in extreme cases. On Unix systems, "configure" can be used to override this default. */ #ifndef LINK_SIZE #define LINK_SIZE 2 #endif /* The value of MATCH_LIMIT determines the default number of times the match() function can be called during a single execution of pcre_exec(). (There is a runtime method of setting a different limit.) The limit exists in order to catch runaway regular expressions that take for ever to determine that they do not match. The default is set very large so that it does not accidentally catch legitimate cases. On Unix systems, "configure" can be used to override this default default. */ #ifndef MATCH_LIMIT #define MATCH_LIMIT 10000000 #endif /* When calling PCRE via the POSIX interface, additional working storage is required for holding the pointers to capturing substrings because PCRE requires three integers per substring, whereas the POSIX interface provides only two. If the number of expected substrings is small, the wrapper function uses space on the stack, because this is faster than using malloc() for each call. The threshold above which the stack is no longer use is defined by POSIX_MALLOC_ THRESHOLD. On Unix systems, "configure" can be used to override this default. */ #ifndef POSIX_MALLOC_THRESHOLD #define POSIX_MALLOC_THRESHOLD 10 #endif /* PCRE uses recursive function calls to handle backtracking while matching. This can sometimes be a problem on systems that have stacks of limited size. Define NO_RECURSE to get a version that doesn't use recursion in the match() function; instead it creates its own stack by steam using pcre_recurse_malloc to get memory. For more detail, see comments and other stuff just above the match() function. On Unix systems, "configure" can be used to set this in the Makefile (use --disable-stack-for-recursion). */ /* #define NO_RECURSE */ /* End */ mvdsv-0.35/src/pcre/get.c000066400000000000000000000307311427146041000152460ustar00rootroot00000000000000/* 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. */ /************************************************* * Perl-Compatible Regular Expressions * *************************************************/ /* This is a library of functions to support regular expressions whose syntax and semantics are as close as possible to those of the Perl 5 language. See the file Tech.Notes for some information on the internals. Written by: Philip Hazel Copyright (c) 1997-2003 University of Cambridge ----------------------------------------------------------------------------- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Cambridge nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ----------------------------------------------------------------------------- */ /* This module contains some convenience functions for extracting substrings from the subject string after a regex match has succeeded. The original idea for these functions came from Scott Wimer. */ /* Include the internals header, which itself includes Standard C headers plus the external pcre header. */ #include "internal.h" /************************************************* * Find number for named string * *************************************************/ /* This function is used by the two extraction functions below, as well as being generally available. Arguments: code the compiled regex stringname the name whose number is required Returns: the number of the named parentheses, or a negative number (PCRE_ERROR_NOSUBSTRING) if not found */ int pcre_get_stringnumber(const pcre *code, const char *stringname) { int rc; int entrysize; int top, bot; uschar *nametable; if ((rc = pcre_fullinfo(code, NULL, PCRE_INFO_NAMECOUNT, &top)) != 0) return rc; if (top <= 0) return PCRE_ERROR_NOSUBSTRING; if ((rc = pcre_fullinfo(code, NULL, PCRE_INFO_NAMEENTRYSIZE, &entrysize)) != 0) return rc; if ((rc = pcre_fullinfo(code, NULL, PCRE_INFO_NAMETABLE, &nametable)) != 0) return rc; bot = 0; while (top > bot) { int mid = (top + bot) / 2; uschar *entry = nametable + entrysize*mid; int c = strcmp(stringname, (char *)(entry + 2)); if (c == 0) return (entry[0] << 8) + entry[1]; if (c > 0) bot = mid + 1; else top = mid; } return PCRE_ERROR_NOSUBSTRING; } /************************************************* * Copy captured string to given buffer * *************************************************/ /* This function copies a single captured substring into a given buffer. Note that we use memcpy() rather than strncpy() in case there are binary zeros in the string. Arguments: subject the subject string that was matched ovector pointer to the offsets table stringcount the number of substrings that were captured (i.e. the yield of the pcre_exec call, unless that was zero, in which case it should be 1/3 of the offset table size) stringnumber the number of the required substring buffer where to put the substring size the size of the buffer Returns: if successful: the length of the copied string, not including the zero that is put on the end; can be zero if not successful: PCRE_ERROR_NOMEMORY (-6) buffer too small PCRE_ERROR_NOSUBSTRING (-7) no such captured substring */ int pcre_copy_substring(const char *subject, int *ovector, int stringcount, int stringnumber, char *buffer, int size) { int yield; if (stringnumber < 0 || stringnumber >= stringcount) return PCRE_ERROR_NOSUBSTRING; stringnumber *= 2; yield = ovector[stringnumber+1] - ovector[stringnumber]; if (size < yield + 1) return PCRE_ERROR_NOMEMORY; memcpy(buffer, subject + ovector[stringnumber], yield); buffer[yield] = 0; return yield; } /************************************************* * Copy named captured string to given buffer * *************************************************/ /* This function copies a single captured substring into a given buffer, identifying it by name. Arguments: code the compiled regex subject the subject string that was matched ovector pointer to the offsets table stringcount the number of substrings that were captured (i.e. the yield of the pcre_exec call, unless that was zero, in which case it should be 1/3 of the offset table size) stringname the name of the required substring buffer where to put the substring size the size of the buffer Returns: if successful: the length of the copied string, not including the zero that is put on the end; can be zero if not successful: PCRE_ERROR_NOMEMORY (-6) buffer too small PCRE_ERROR_NOSUBSTRING (-7) no such captured substring */ int pcre_copy_named_substring(const pcre *code, const char *subject, int *ovector, int stringcount, const char *stringname, char *buffer, int size) { int n = pcre_get_stringnumber(code, stringname); if (n <= 0) return n; return pcre_copy_substring(subject, ovector, stringcount, n, buffer, size); } /************************************************* * Copy all captured strings to new store * *************************************************/ /* This function gets one chunk of store and builds a list of pointers and all of the captured substrings in it. A NULL pointer is put on the end of the list. Arguments: subject the subject string that was matched ovector pointer to the offsets table stringcount the number of substrings that were captured (i.e. the yield of the pcre_exec call, unless that was zero, in which case it should be 1/3 of the offset table size) listptr set to point to the list of pointers Returns: if successful: 0 if not successful: PCRE_ERROR_NOMEMORY (-6) failed to get store */ int pcre_get_substring_list(const char *subject, int *ovector, int stringcount, const char ***listptr) { int i; int size = sizeof(char *); int double_count = stringcount * 2; char **stringlist; char *p; for (i = 0; i < double_count; i += 2) size += sizeof(char *) + ovector[i+1] - ovector[i] + 1; stringlist = (char **)(pcre_malloc)(size); if (stringlist == NULL) return PCRE_ERROR_NOMEMORY; *listptr = (const char **)stringlist; p = (char *)(stringlist + stringcount + 1); for (i = 0; i < double_count; i += 2) { int len = ovector[i+1] - ovector[i]; memcpy(p, subject + ovector[i], len); *stringlist++ = p; p += len; *p++ = 0; } *stringlist = NULL; return 0; } /************************************************* * Free store obtained by get_substring_list * *************************************************/ /* This function exists for the benefit of people calling PCRE from non-C programs that can call its functions, but not free() or (pcre_free)() directly. Argument: the result of a previous pcre_get_substring_list() Returns: nothing */ void pcre_free_substring_list(const char **pointer) { (pcre_free)((void *)pointer); } /************************************************* * Copy captured string to new store * *************************************************/ /* This function copies a single captured substring into a piece of new store Arguments: subject the subject string that was matched ovector pointer to the offsets table stringcount the number of substrings that were captured (i.e. the yield of the pcre_exec call, unless that was zero, in which case it should be 1/3 of the offset table size) stringnumber the number of the required substring stringptr where to put a pointer to the substring Returns: if successful: the length of the string, not including the zero that is put on the end; can be zero if not successful: PCRE_ERROR_NOMEMORY (-6) failed to get store PCRE_ERROR_NOSUBSTRING (-7) substring not present */ int pcre_get_substring(const char *subject, int *ovector, int stringcount, int stringnumber, const char **stringptr) { int yield; char *substring; if (stringnumber < 0 || stringnumber >= stringcount) return PCRE_ERROR_NOSUBSTRING; stringnumber *= 2; yield = ovector[stringnumber+1] - ovector[stringnumber]; substring = (char *)(pcre_malloc)(yield + 1); if (substring == NULL) return PCRE_ERROR_NOMEMORY; memcpy(substring, subject + ovector[stringnumber], yield); substring[yield] = 0; *stringptr = substring; return yield; } /************************************************* * Copy named captured string to new store * *************************************************/ /* This function copies a single captured substring, identified by name, into new store. Arguments: code the compiled regex subject the subject string that was matched ovector pointer to the offsets table stringcount the number of substrings that were captured (i.e. the yield of the pcre_exec call, unless that was zero, in which case it should be 1/3 of the offset table size) stringname the name of the required substring stringptr where to put the pointer Returns: if successful: the length of the copied string, not including the zero that is put on the end; can be zero if not successful: PCRE_ERROR_NOMEMORY (-6) couldn't get memory PCRE_ERROR_NOSUBSTRING (-7) no such captured substring */ int pcre_get_named_substring(const pcre *code, const char *subject, int *ovector, int stringcount, const char *stringname, const char **stringptr) { int n = pcre_get_stringnumber(code, stringname); if (n <= 0) return n; return pcre_get_substring(subject, ovector, stringcount, n, stringptr); } /************************************************* * Free store obtained by get_substring * *************************************************/ /* This function exists for the benefit of people calling PCRE from non-C programs that can call its functions, but not free() or (pcre_free)() directly. Argument: the result of a previous pcre_get_substring() Returns: nothing */ void pcre_free_substring(const char *pointer) { (pcre_free)((void *)pointer); } /* End of get.c */ mvdsv-0.35/src/pcre/internal.h000066400000000000000000001015211427146041000163040ustar00rootroot00000000000000/* 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. */ /************************************************* * Perl-Compatible Regular Expressions * *************************************************/ /* This is a library of functions to support regular expressions whose syntax and semantics are as close as possible to those of the Perl 5 language. See the file doc/Tech.Notes for some information on the internals. Written by: Philip Hazel Copyright (c) 1997-2004 University of Cambridge ----------------------------------------------------------------------------- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Cambridge nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ----------------------------------------------------------------------------- */ /* This header contains definitions that are shared between the different modules, but which are not relevant to the outside. */ /* Get the definitions provided by running "configure" */ #include "config.h" /* Standard C headers plus the external interface definition. The only time setjmp and stdarg are used is when NO_RECURSE is set. */ #include #include #include #include #include #include #include #include #ifndef PCRE_SPY #define PCRE_DEFINITION /* Win32 __declspec(export) trigger for .dll */ #endif /* We need to have types that specify unsigned 16-bit and 32-bit integers. We cannot determine these outside the compilation (e.g. by running a program as part of "configure") because PCRE is often cross-compiled for use on other systems. Instead we make use of the maximum sizes that are available at preprocessor time in standard C environments. */ #if USHRT_MAX == 65535 typedef unsigned short pcre_uint16; #elif UINT_MAX == 65535 typedef unsigned int pcre_uint16; #else #error Cannot determine a type for 16-bit unsigned integers #endif #if UINT_MAX == 4294967295 typedef unsigned int pcre_uint32; #elif ULONG_MAX == 4294967295 typedef unsigned long int pcre_uint32; #else #error Cannot determine a type for 32-bit unsigned integers #endif /* All character handling must be done as unsigned characters. Otherwise there are problems with top-bit-set characters and functions such as isspace(). However, we leave the interface to the outside world as char *, because that should make things easier for callers. We define a short type for unsigned char to save lots of typing. I tried "uchar", but it causes problems on Digital Unix, where it is defined in sys/types, so use "uschar" instead. */ typedef unsigned char uschar; /* Include the public PCRE header */ #include "pcre.h" /* When compiling for use with the Virtual Pascal compiler, these functions need to have their names changed. PCRE must be compiled with the -DVPCOMPAT option on the command line. */ #ifdef VPCOMPAT #define strncmp(s1,s2,m) _strncmp(s1,s2,m) #define memcpy(d,s,n) _memcpy(d,s,n) #define memmove(d,s,n) _memmove(d,s,n) #define memset(s,c,n) _memset(s,c,n) #else /* VPCOMPAT */ /* To cope with SunOS4 and other systems that lack memmove() but have bcopy(), define a macro for memmove() if HAVE_MEMMOVE is false, provided that HAVE_BCOPY is set. Otherwise, include an emulating function for those systems that have neither (there some non-Unix environments where this is the case). This assumes that all calls to memmove are moving strings upwards in store, which is the case in PCRE. */ #if ! HAVE_MEMMOVE #undef memmove /* some systems may have a macro */ #if HAVE_BCOPY #define memmove(a, b, c) bcopy(b, a, c) #else /* HAVE_BCOPY */ void * pcre_memmove(unsigned char *dest, const unsigned char *src, size_t n) { int i; dest += n; src += n; for (i = 0; i < n; ++i) *(--dest) = *(--src); } #define memmove(a, b, c) pcre_memmove(a, b, c) #endif /* not HAVE_BCOPY */ #endif /* not HAVE_MEMMOVE */ #endif /* not VPCOMPAT */ /* PCRE keeps offsets in its compiled code as 2-byte quantities (always stored in big-endian order) by default. These are used, for example, to link from the start of a subpattern to its alternatives and its end. The use of 2 bytes per offset limits the size of the compiled regex to around 64K, which is big enough for almost everybody. However, I received a request for an even bigger limit. For this reason, and also to make the code easier to maintain, the storing and loading of offsets from the byte string is now handled by the macros that are defined here. The macros are controlled by the value of LINK_SIZE. This defaults to 2 in the config.h file, but can be overridden by using -D on the command line. This is automated on Unix systems via the "configure" command. */ #if LINK_SIZE == 2 #define PUT(a,n,d) \ (a[n] = (d) >> 8), \ (a[(n)+1] = (d) & 255) #define GET(a,n) \ (((a)[n] << 8) | (a)[(n)+1]) #define MAX_PATTERN_SIZE (1 << 16) #elif LINK_SIZE == 3 #define PUT(a,n,d) \ (a[n] = (d) >> 16), \ (a[(n)+1] = (d) >> 8), \ (a[(n)+2] = (d) & 255) #define GET(a,n) \ (((a)[n] << 16) | ((a)[(n)+1] << 8) | (a)[(n)+2]) #define MAX_PATTERN_SIZE (1 << 24) #elif LINK_SIZE == 4 #define PUT(a,n,d) \ (a[n] = (d) >> 24), \ (a[(n)+1] = (d) >> 16), \ (a[(n)+2] = (d) >> 8), \ (a[(n)+3] = (d) & 255) #define GET(a,n) \ (((a)[n] << 24) | ((a)[(n)+1] << 16) | ((a)[(n)+2] << 8) | (a)[(n)+3]) #define MAX_PATTERN_SIZE (1 << 30) /* Keep it positive */ #else #error LINK_SIZE must be either 2, 3, or 4 #endif /* Convenience macro defined in terms of the others */ #define PUTINC(a,n,d) PUT(a,n,d), a += LINK_SIZE /* PCRE uses some other 2-byte quantities that do not change when the size of offsets changes. There are used for repeat counts and for other things such as capturing parenthesis numbers in back references. */ #define PUT2(a,n,d) \ a[n] = (d) >> 8; \ a[(n)+1] = (d) & 255 #define GET2(a,n) \ (((a)[n] << 8) | (a)[(n)+1]) #define PUT2INC(a,n,d) PUT2(a,n,d), a += 2 /* In case there is no definition of offsetof() provided - though any proper Standard C system should have one. */ #ifndef offsetof #define offsetof(p_type,field) ((size_t)&(((p_type *)0)->field)) #endif /* These are the public options that can change during matching. */ #define PCRE_IMS (PCRE_CASELESS|PCRE_MULTILINE|PCRE_DOTALL) /* Private options flags start at the most significant end of the four bytes, but skip the top bit so we can use ints for convenience without getting tangled with negative values. The public options defined in pcre.h start at the least significant end. Make sure they don't overlap, though now that we have expanded to four bytes, there is plenty of space. */ #define PCRE_FIRSTSET 0x40000000 /* first_byte is set */ #define PCRE_REQCHSET 0x20000000 /* req_byte is set */ #define PCRE_STARTLINE 0x10000000 /* start after \n for multiline */ #define PCRE_ICHANGED 0x08000000 /* i option changes within regex */ #define PCRE_NOPARTIAL 0x04000000 /* can't use partial with this regex */ /* Options for the "extra" block produced by pcre_study(). */ #define PCRE_STUDY_MAPPED 0x01 /* a map of starting chars exists */ /* Masks for identifying the public options which are permitted at compile time, run time or study time, respectively. */ #define PUBLIC_OPTIONS \ (PCRE_CASELESS|PCRE_EXTENDED|PCRE_ANCHORED|PCRE_MULTILINE| \ PCRE_DOTALL|PCRE_DOLLAR_ENDONLY|PCRE_EXTRA|PCRE_UNGREEDY|PCRE_UTF8| \ PCRE_NO_AUTO_CAPTURE|PCRE_NO_UTF8_CHECK|PCRE_AUTO_CALLOUT) #define PUBLIC_EXEC_OPTIONS \ (PCRE_ANCHORED|PCRE_NOTBOL|PCRE_NOTEOL|PCRE_NOTEMPTY|PCRE_NO_UTF8_CHECK| \ PCRE_PARTIAL) #define PUBLIC_STUDY_OPTIONS 0 /* None defined */ /* Magic number to provide a small check against being handed junk. */ #define MAGIC_NUMBER 0x50435245UL /* 'PCRE' */ /* Negative values for the firstchar and reqchar variables */ #define REQ_UNSET (-2) #define REQ_NONE (-1) /* Flags added to firstbyte or reqbyte; a "non-literal" item is either a variable-length repeat, or a anything other than literal characters. */ #define REQ_CASELESS 0x0100 /* indicates caselessness */ #define REQ_VARY 0x0200 /* reqbyte followed non-literal item */ /* Miscellaneous definitions */ typedef int BOOL; #define FALSE 0 #define TRUE 1 /* Escape items that are just an encoding of a particular data value. Note that ESC_n is defined as yet another macro, which is set in config.h to either \n (the default) or \r (which some people want). */ #ifndef ESC_e #define ESC_e 27 #endif #ifndef ESC_f #define ESC_f '\f' #endif #ifndef ESC_n #define ESC_n NEWLINE #endif #ifndef ESC_r #define ESC_r '\r' #endif /* We can't officially use ESC_t because it is a POSIX reserved identifier (presumably because of all the others like size_t). */ #ifndef ESC_tee #define ESC_tee '\t' #endif /* These are escaped items that aren't just an encoding of a particular data value such as \n. They must have non-zero values, as check_escape() returns their negation. Also, they must appear in the same order as in the opcode definitions below, up to ESC_z. There's a dummy for OP_ANY because it corresponds to "." rather than an escape sequence. The final one must be ESC_REF as subsequent values are used for \1, \2, \3, etc. There is are two tests in the code for an escape greater than ESC_b and less than ESC_Z to detect the types that may be repeated. These are the types that consume characters. If any new escapes are put in between that don't consume a character, that code will have to change. */ enum { ESC_A = 1, ESC_G, ESC_B, ESC_b, ESC_D, ESC_d, ESC_S, ESC_s, ESC_W, ESC_w, ESC_dum1, ESC_C, ESC_P, ESC_p, ESC_X, ESC_Z, ESC_z, ESC_E, ESC_Q, ESC_REF }; /* Flag bits and data types for the extended class (OP_XCLASS) for classes that contain UTF-8 characters with values greater than 255. */ #define XCL_NOT 0x01 /* Flag: this is a negative class */ #define XCL_MAP 0x02 /* Flag: a 32-byte map is present */ #define XCL_END 0 /* Marks end of individual items */ #define XCL_SINGLE 1 /* Single item (one multibyte char) follows */ #define XCL_RANGE 2 /* A range (two multibyte chars) follows */ #define XCL_PROP 3 /* Unicode property (one property code) follows */ #define XCL_NOTPROP 4 /* Unicode inverted property (ditto) */ /* Opcode table: OP_BRA must be last, as all values >= it are used for brackets that extract substrings. Starting from 1 (i.e. after OP_END), the values up to OP_EOD must correspond in order to the list of escapes immediately above. Note that whenever this list is updated, the two macro definitions that follow must also be updated to match. */ enum { OP_END, /* 0 End of pattern */ /* Values corresponding to backslashed metacharacters */ OP_SOD, /* 1 Start of data: \A */ OP_SOM, /* 2 Start of match (subject + offset): \G */ OP_NOT_WORD_BOUNDARY, /* 3 \B */ OP_WORD_BOUNDARY, /* 4 \b */ OP_NOT_DIGIT, /* 5 \D */ OP_DIGIT, /* 6 \d */ OP_NOT_WHITESPACE, /* 7 \S */ OP_WHITESPACE, /* 8 \s */ OP_NOT_WORDCHAR, /* 9 \W */ OP_WORDCHAR, /* 10 \w */ OP_ANY, /* 11 Match any character */ OP_ANYBYTE, /* 12 Match any byte (\C); different to OP_ANY for UTF-8 */ OP_NOTPROP, /* 13 \P (not Unicode property) */ OP_PROP, /* 14 \p (Unicode property) */ OP_EXTUNI, /* 15 \X (extended Unicode sequence */ OP_EODN, /* 16 End of data or \n at end of data: \Z. */ OP_EOD, /* 17 End of data: \z */ OP_OPT, /* 18 Set runtime options */ OP_CIRC, /* 19 Start of line - varies with multiline switch */ OP_DOLL, /* 20 End of line - varies with multiline switch */ OP_CHAR, /* 21 Match one character, casefully */ OP_CHARNC, /* 22 Match one character, caselessly */ OP_NOT, /* 23 Match anything but the following char */ OP_STAR, /* 24 The maximizing and minimizing versions of */ OP_MINSTAR, /* 25 all these opcodes must come in pairs, with */ OP_PLUS, /* 26 the minimizing one second. */ OP_MINPLUS, /* 27 This first set applies to single characters */ OP_QUERY, /* 28 */ OP_MINQUERY, /* 29 */ OP_UPTO, /* 30 From 0 to n matches */ OP_MINUPTO, /* 31 */ OP_EXACT, /* 32 Exactly n matches */ OP_NOTSTAR, /* 33 The maximizing and minimizing versions of */ OP_NOTMINSTAR, /* 34 all these opcodes must come in pairs, with */ OP_NOTPLUS, /* 35 the minimizing one second. */ OP_NOTMINPLUS, /* 36 This set applies to "not" single characters */ OP_NOTQUERY, /* 37 */ OP_NOTMINQUERY, /* 38 */ OP_NOTUPTO, /* 39 From 0 to n matches */ OP_NOTMINUPTO, /* 40 */ OP_NOTEXACT, /* 41 Exactly n matches */ OP_TYPESTAR, /* 42 The maximizing and minimizing versions of */ OP_TYPEMINSTAR, /* 43 all these opcodes must come in pairs, with */ OP_TYPEPLUS, /* 44 the minimizing one second. These codes must */ OP_TYPEMINPLUS, /* 45 be in exactly the same order as those above. */ OP_TYPEQUERY, /* 46 This set applies to character types such as \d */ OP_TYPEMINQUERY, /* 47 */ OP_TYPEUPTO, /* 48 From 0 to n matches */ OP_TYPEMINUPTO, /* 49 */ OP_TYPEEXACT, /* 50 Exactly n matches */ OP_CRSTAR, /* 51 The maximizing and minimizing versions of */ OP_CRMINSTAR, /* 52 all these opcodes must come in pairs, with */ OP_CRPLUS, /* 53 the minimizing one second. These codes must */ OP_CRMINPLUS, /* 54 be in exactly the same order as those above. */ OP_CRQUERY, /* 55 These are for character classes and back refs */ OP_CRMINQUERY, /* 56 */ OP_CRRANGE, /* 57 These are different to the three sets above. */ OP_CRMINRANGE, /* 58 */ OP_CLASS, /* 59 Match a character class, chars < 256 only */ OP_NCLASS, /* 60 Same, but the bitmap was created from a negative class - the difference is relevant only when a UTF-8 character > 255 is encountered. */ OP_XCLASS, /* 61 Extended class for handling UTF-8 chars within the class. This does both positive and negative. */ OP_REF, /* 62 Match a back reference */ OP_RECURSE, /* 63 Match a numbered subpattern (possibly recursive) */ OP_CALLOUT, /* 64 Call out to external function if provided */ OP_ALT, /* 65 Start of alternation */ OP_KET, /* 66 End of group that doesn't have an unbounded repeat */ OP_KETRMAX, /* 67 These two must remain together and in this */ OP_KETRMIN, /* 68 order. They are for groups the repeat for ever. */ /* The assertions must come before ONCE and COND */ OP_ASSERT, /* 69 Positive lookahead */ OP_ASSERT_NOT, /* 70 Negative lookahead */ OP_ASSERTBACK, /* 71 Positive lookbehind */ OP_ASSERTBACK_NOT, /* 72 Negative lookbehind */ OP_REVERSE, /* 73 Move pointer back - used in lookbehind assertions */ /* ONCE and COND must come after the assertions, with ONCE first, as there's a test for >= ONCE for a subpattern that isn't an assertion. */ OP_ONCE, /* 74 Once matched, don't back up into the subpattern */ OP_COND, /* 75 Conditional group */ OP_CREF, /* 76 Used to hold an extraction string number (cond ref) */ OP_BRAZERO, /* 77 These two must remain together and in this */ OP_BRAMINZERO, /* 78 order. */ OP_BRANUMBER, /* 79 Used for extracting brackets whose number is greater than can fit into an opcode. */ OP_BRA /* 80 This and greater values are used for brackets that extract substrings up to EXTRACT_BASIC_MAX. After that, use is made of OP_BRANUMBER. */ }; /* WARNING WARNING WARNING: There is an implicit assumption in pcre.c and study.c that all opcodes are less than 128 in value. This makes handling UTF-8 character sequences easier. */ /* The highest extraction number before we have to start using additional bytes. (Originally PCRE didn't have support for extraction counts highter than this number.) The value is limited by the number of opcodes left after OP_BRA, i.e. 255 - OP_BRA. We actually set it a bit lower to leave room for additional opcodes. */ #define EXTRACT_BASIC_MAX 100 /* This macro defines textual names for all the opcodes. There are used only for debugging, in pcre.c when DEBUG is defined, and also in pcretest.c. The macro is referenced only in printint.c. */ #define OP_NAME_LIST \ "End", "\\A", "\\G", "\\B", "\\b", "\\D", "\\d", \ "\\S", "\\s", "\\W", "\\w", "Any", "Anybyte", \ "notprop", "prop", "extuni", \ "\\Z", "\\z", \ "Opt", "^", "$", "char", "charnc", "not", \ "*", "*?", "+", "+?", "?", "??", "{", "{", "{", \ "*", "*?", "+", "+?", "?", "??", "{", "{", "{", \ "*", "*?", "+", "+?", "?", "??", "{", "{", "{", \ "*", "*?", "+", "+?", "?", "??", "{", "{", \ "class", "nclass", "xclass", "Ref", "Recurse", "Callout", \ "Alt", "Ket", "KetRmax", "KetRmin", "Assert", "Assert not", \ "AssertB", "AssertB not", "Reverse", "Once", "Cond", "Cond ref",\ "Brazero", "Braminzero", "Branumber", "Bra" /* This macro defines the length of fixed length operations in the compiled regex. The lengths are used when searching for specific things, and also in the debugging printing of a compiled regex. We use a macro so that it can be incorporated both into pcre.c and pcretest.c without being publicly exposed. As things have been extended, some of these are no longer fixed lenths, but are minima instead. For example, the length of a single-character repeat may vary in UTF-8 mode. The code that uses this table must know about such things. */ #define OP_LENGTHS \ 1, /* End */ \ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* \A, \G, \B, \B, \D, \d, \S, \s, \W, \w */ \ 1, 1, /* Any, Anybyte */ \ 2, 2, 1, /* NOTPROP, PROP, EXTUNI */ \ 1, 1, 2, 1, 1, /* \Z, \z, Opt, ^, $ */ \ 2, /* Char - the minimum length */ \ 2, /* Charnc - the minimum length */ \ 2, /* not */ \ /* Positive single-char repeats ** These are */ \ 2, 2, 2, 2, 2, 2, /* *, *?, +, +?, ?, ?? ** minima in */ \ 4, 4, 4, /* upto, minupto, exact ** UTF-8 mode */ \ /* Negative single-char repeats - only for chars < 256 */ \ 2, 2, 2, 2, 2, 2, /* NOT *, *?, +, +?, ?, ?? */ \ 4, 4, 4, /* NOT upto, minupto, exact */ \ /* Positive type repeats */ \ 2, 2, 2, 2, 2, 2, /* Type *, *?, +, +?, ?, ?? */ \ 4, 4, 4, /* Type upto, minupto, exact */ \ /* Character class & ref repeats */ \ 1, 1, 1, 1, 1, 1, /* *, *?, +, +?, ?, ?? */ \ 5, 5, /* CRRANGE, CRMINRANGE */ \ 33, /* CLASS */ \ 33, /* NCLASS */ \ 0, /* XCLASS - variable length */ \ 3, /* REF */ \ 1+LINK_SIZE, /* RECURSE */ \ 2+2*LINK_SIZE, /* CALLOUT */ \ 1+LINK_SIZE, /* Alt */ \ 1+LINK_SIZE, /* Ket */ \ 1+LINK_SIZE, /* KetRmax */ \ 1+LINK_SIZE, /* KetRmin */ \ 1+LINK_SIZE, /* Assert */ \ 1+LINK_SIZE, /* Assert not */ \ 1+LINK_SIZE, /* Assert behind */ \ 1+LINK_SIZE, /* Assert behind not */ \ 1+LINK_SIZE, /* Reverse */ \ 1+LINK_SIZE, /* Once */ \ 1+LINK_SIZE, /* COND */ \ 3, /* CREF */ \ 1, 1, /* BRAZERO, BRAMINZERO */ \ 3, /* BRANUMBER */ \ 1+LINK_SIZE /* BRA */ \ /* A magic value for OP_CREF to indicate the "in recursion" condition. */ #define CREF_RECURSE 0xffff /* The texts of compile-time error messages are defined as macros here so that they can be accessed by the POSIX wrapper and converted into error codes. Yes, I could have used error codes in the first place, but didn't feel like changing just to accommodate the POSIX wrapper. */ #define ERR1 "\\ at end of pattern" #define ERR2 "\\c at end of pattern" #define ERR3 "unrecognized character follows \\" #define ERR4 "numbers out of order in {} quantifier" #define ERR5 "number too big in {} quantifier" #define ERR6 "missing terminating ] for character class" #define ERR7 "invalid escape sequence in character class" #define ERR8 "range out of order in character class" #define ERR9 "nothing to repeat" #define ERR10 "operand of unlimited repeat could match the empty string" #define ERR11 "internal error: unexpected repeat" #define ERR12 "unrecognized character after (?" #define ERR13 "POSIX named classes are supported only within a class" #define ERR14 "missing )" #define ERR15 "reference to non-existent subpattern" #define ERR16 "erroffset passed as NULL" #define ERR17 "unknown option bit(s) set" #define ERR18 "missing ) after comment" #define ERR19 "parentheses nested too deeply" #define ERR20 "regular expression too large" #define ERR21 "failed to get memory" #define ERR22 "unmatched parentheses" #define ERR23 "internal error: code overflow" #define ERR24 "unrecognized character after (?<" #define ERR25 "lookbehind assertion is not fixed length" #define ERR26 "malformed number after (?(" #define ERR27 "conditional group contains more than two branches" #define ERR28 "assertion expected after (?(" #define ERR29 "(?R or (?digits must be followed by )" #define ERR30 "unknown POSIX class name" #define ERR31 "POSIX collating elements are not supported" #define ERR32 "this version of PCRE is not compiled with PCRE_UTF8 support" #define ERR33 "spare error" #define ERR34 "character value in \\x{...} sequence is too large" #define ERR35 "invalid condition (?(0)" #define ERR36 "\\C not allowed in lookbehind assertion" #define ERR37 "PCRE does not support \\L, \\l, \\N, \\U, or \\u" #define ERR38 "number after (?C is > 255" #define ERR39 "closing ) for (?C expected" #define ERR40 "recursive call could loop indefinitely" #define ERR41 "unrecognized character after (?P" #define ERR42 "syntax error after (?P" #define ERR43 "two named groups have the same name" #define ERR44 "invalid UTF-8 string" #define ERR45 "support for \\P, \\p, and \\X has not been compiled" #define ERR46 "malformed \\P or \\p sequence" #define ERR47 "unknown property name after \\P or \\p" /* The real format of the start of the pcre block; the index of names and the code vector run on as long as necessary after the end. We store an explicit offset to the name table so that if a regex is compiled on one host, saved, and then run on another where the size of pointers is different, all might still be well. For the case of compiled-on-4 and run-on-8, we include an extra pointer that is always NULL. For future-proofing, we also include a few dummy fields - even though you can never get this planning right! NOTE NOTE NOTE: Because people can now save and re-use compiled patterns, any additions to this structure should be made at the end, and something earlier (e.g. a new flag in the options or one of the dummy fields) should indicate that the new fields are present. Currently PCRE always sets the dummy fields to zero. NOTE NOTE NOTE: */ typedef struct real_pcre { pcre_uint32 magic_number; pcre_uint32 size; /* Total that was malloced */ pcre_uint32 options; pcre_uint32 dummy1; /* For future use, maybe */ pcre_uint16 top_bracket; pcre_uint16 top_backref; pcre_uint16 first_byte; pcre_uint16 req_byte; pcre_uint16 name_table_offset; /* Offset to name table that follows */ pcre_uint16 name_entry_size; /* Size of any name items */ pcre_uint16 name_count; /* Number of name items */ pcre_uint16 dummy2; /* For future use, maybe */ const unsigned char *tables; /* Pointer to tables or NULL for std */ const unsigned char *nullpad; /* NULL padding */ } real_pcre; /* The format of the block used to store data from pcre_study(). The same remark (see NOTE above) about extending this structure applies. */ typedef struct pcre_study_data { pcre_uint32 size; /* Total that was malloced */ pcre_uint32 options; uschar start_bits[32]; } pcre_study_data; /* Structure for passing "static" information around between the functions doing the compiling, so that they are thread-safe. */ typedef struct compile_data { const uschar *lcc; /* Points to lower casing table */ const uschar *fcc; /* Points to case-flipping table */ const uschar *cbits; /* Points to character type table */ const uschar *ctypes; /* Points to table of type maps */ const uschar *start_code; /* The start of the compiled code */ const uschar *start_pattern; /* The start of the pattern */ uschar *name_table; /* The name/number table */ int names_found; /* Number of entries so far */ int name_entry_size; /* Size of each entry */ int top_backref; /* Maximum back reference */ unsigned int backref_map; /* Bitmap of low back refs */ int req_varyopt; /* "After variable item" flag for reqbyte */ BOOL nopartial; /* Set TRUE if partial won't work */ } compile_data; /* Structure for maintaining a chain of pointers to the currently incomplete branches, for testing for left recursion. */ typedef struct branch_chain { struct branch_chain *outer; uschar *current; } branch_chain; /* Structure for items in a linked list that represents an explicit recursive call within the pattern. */ typedef struct recursion_info { struct recursion_info *prevrec; /* Previous recursion record (or NULL) */ int group_num; /* Number of group that was called */ const uschar *after_call; /* "Return value": points after the call in the expr */ const uschar *save_start; /* Old value of md->start_match */ int *offset_save; /* Pointer to start of saved offsets */ int saved_max; /* Number of saved offsets */ } recursion_info; /* When compiling in a mode that doesn't use recursive calls to match(), a structure is used to remember local variables on the heap. It is defined in pcre.c, close to the match() function, so that it is easy to keep it in step with any changes of local variable. However, the pointer to the current frame must be saved in some "static" place over a longjmp(). We declare the structure here so that we can put a pointer in the match_data structure. NOTE: This isn't used for a "normal" compilation of pcre. */ struct heapframe; /* Structure for passing "static" information around between the functions doing the matching, so that they are thread-safe. */ typedef struct match_data { unsigned long int match_call_count; /* As it says */ unsigned long int match_limit;/* As it says */ int *offset_vector; /* Offset vector */ int offset_end; /* One past the end */ int offset_max; /* The maximum usable for return data */ const uschar *lcc; /* Points to lower casing table */ const uschar *ctypes; /* Points to table of type maps */ BOOL offset_overflow; /* Set if too many extractions */ BOOL notbol; /* NOTBOL flag */ BOOL noteol; /* NOTEOL flag */ BOOL utf8; /* UTF8 flag */ BOOL endonly; /* Dollar not before final \n */ BOOL notempty; /* Empty string match not wanted */ BOOL partial; /* PARTIAL flag */ BOOL hitend; /* Hit the end of the subject at some point */ const uschar *start_code; /* For use when recursing */ const uschar *start_subject; /* Start of the subject string */ const uschar *end_subject; /* End of the subject string */ const uschar *start_match; /* Start of this match attempt */ const uschar *end_match_ptr; /* Subject position at end match */ int end_offset_top; /* Highwater mark at end of match */ int capture_last; /* Most recent capture number */ int start_offset; /* The start offset value */ recursion_info *recursive; /* Linked list of recursion data */ void *callout_data; /* To pass back to callouts */ struct heapframe *thisframe; /* Used only when compiling for no recursion */ } match_data; /* Bit definitions for entries in the pcre_ctypes table. */ #define ctype_space 0x01 #define ctype_letter 0x02 #define ctype_digit 0x04 #define ctype_xdigit 0x08 #define ctype_word 0x10 /* alphameric or '_' */ #define ctype_meta 0x80 /* regexp meta char or zero (end pattern) */ /* Offsets for the bitmap tables in pcre_cbits. Each table contains a set of bits for a class map. Some classes are built by combining these tables. */ #define cbit_space 0 /* [:space:] or \s */ #define cbit_xdigit 32 /* [:xdigit:] */ #define cbit_digit 64 /* [:digit:] or \d */ #define cbit_upper 96 /* [:upper:] */ #define cbit_lower 128 /* [:lower:] */ #define cbit_word 160 /* [:word:] or \w */ #define cbit_graph 192 /* [:graph:] */ #define cbit_print 224 /* [:print:] */ #define cbit_punct 256 /* [:punct:] */ #define cbit_cntrl 288 /* [:cntrl:] */ #define cbit_length 320 /* Length of the cbits table */ /* Offsets of the various tables from the base tables pointer, and total length. */ #define lcc_offset 0 #define fcc_offset 256 #define cbits_offset 512 #define ctypes_offset (cbits_offset + cbit_length) #define tables_length (ctypes_offset + 256) /* End of internal.h */ mvdsv-0.35/src/pcre/pcre.c000066400000000000000000010514661427146041000154310ustar00rootroot00000000000000/* 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. */ /************************************************* * Perl-Compatible Regular Expressions * *************************************************/ /* This is a library of functions to support regular expressions whose syntax and semantics are as close as possible to those of the Perl 5 language. See the file Tech.Notes for some information on the internals. Written by: Philip Hazel Copyright (c) 1997-2004 University of Cambridge ----------------------------------------------------------------------------- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Cambridge nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ----------------------------------------------------------------------------- */ /* Define DEBUG to get debugging output on stdout. */ /* #define DEBUG */ /* Use a macro for debugging printing, 'cause that eliminates the use of #ifdef inline, and there are *still* stupid compilers about that don't like indented pre-processor statements. I suppose it's only been 10 years... */ #ifdef DEBUG #define DPRINTF(p) printf p #else #define DPRINTF(p) /*nothing*/ #endif /* Include the internals header, which itself includes "config.h", the Standard C headers, and the external pcre header. */ #include "internal.h" /* If Unicode Property support is wanted, include a private copy of the function that does it, and the table that translates names to numbers. */ #ifdef SUPPORT_UCP #include "ucp.c" #include "ucptypetable.c" #endif /* Maximum number of items on the nested bracket stacks at compile time. This applies to the nesting of all kinds of parentheses. It does not limit un-nested, non-capturing parentheses. This number can be made bigger if necessary - it is used to dimension one int and one unsigned char vector at compile time. */ #define BRASTACK_SIZE 200 /* Maximum number of ints of offset to save on the stack for recursive calls. If the offset vector is bigger, malloc is used. This should be a multiple of 3, because the offset vector is always a multiple of 3 long. */ #define REC_STACK_SAVE_MAX 30 /* The maximum remaining length of subject we are prepared to search for a req_byte match. */ #define REQ_BYTE_MAX 1000 /* Table of sizes for the fixed-length opcodes. It's defined in a macro so that the definition is next to the definition of the opcodes in internal.h. */ static const uschar OP_lengths[] = { OP_LENGTHS }; /* Min and max values for the common repeats; for the maxima, 0 => infinity */ static const char rep_min[] = { 0, 0, 1, 1, 0, 0 }; static const char rep_max[] = { 0, 0, 0, 0, 1, 1 }; /* Table for handling escaped characters in the range '0'-'z'. Positive returns are simple data values; negative values are for special things like \d and so on. Zero means further processing is needed (for things like \x), or the escape is invalid. */ #if !EBCDIC /* This is the "normal" table for ASCII systems */ static const short int escapes[] = { 0, 0, 0, 0, 0, 0, 0, 0, /* 0 - 7 */ 0, 0, ':', ';', '<', '=', '>', '?', /* 8 - ? */ '@', -ESC_A, -ESC_B, -ESC_C, -ESC_D, -ESC_E, 0, -ESC_G, /* @ - G */ 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ -ESC_P, -ESC_Q, 0, -ESC_S, 0, 0, 0, -ESC_W, /* P - W */ -ESC_X, 0, -ESC_Z, '[', '\\', ']', '^', '_', /* X - _ */ '`', 7, -ESC_b, 0, -ESC_d, ESC_e, ESC_f, 0, /* ` - g */ 0, 0, 0, 0, 0, 0, ESC_n, 0, /* h - o */ -ESC_p, 0, ESC_r, -ESC_s, ESC_tee, 0, 0, -ESC_w, /* p - w */ 0, 0, -ESC_z /* x - z */ }; #else /* This is the "abnormal" table for EBCDIC systems */ static const short int escapes[] = { /* 48 */ 0, 0, 0, '.', '<', '(', '+', '|', /* 50 */ '&', 0, 0, 0, 0, 0, 0, 0, /* 58 */ 0, 0, '!', '$', '*', ')', ';', '~', /* 60 */ '-', '/', 0, 0, 0, 0, 0, 0, /* 68 */ 0, 0, '|', ',', '%', '_', '>', '?', /* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 78 */ 0, '`', ':', '#', '@', '\'', '=', '"', /* 80 */ 0, 7, -ESC_b, 0, -ESC_d, ESC_e, ESC_f, 0, /* 88 */ 0, 0, 0, '{', 0, 0, 0, 0, /* 90 */ 0, 0, 0, 'l', 0, ESC_n, 0, -ESC_p, /* 98 */ 0, ESC_r, 0, '}', 0, 0, 0, 0, /* A0 */ 0, '~', -ESC_s, ESC_tee, 0, 0, -ESC_w, 0, /* A8 */ 0,-ESC_z, 0, 0, 0, '[', 0, 0, /* B0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* B8 */ 0, 0, 0, 0, 0, ']', '=', '-', /* C0 */ '{',-ESC_A, -ESC_B, -ESC_C, -ESC_D,-ESC_E, 0, -ESC_G, /* C8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* D0 */ '}', 0, 0, 0, 0, 0, 0, -ESC_P, /* D8 */-ESC_Q, 0, 0, 0, 0, 0, 0, 0, /* E0 */ '\\', 0, -ESC_S, 0, 0, 0, -ESC_W, -ESC_X, /* E8 */ 0,-ESC_Z, 0, 0, 0, 0, 0, 0, /* F0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* F8 */ 0, 0, 0, 0, 0, 0, 0, 0 }; #endif /* Tables of names of POSIX character classes and their lengths. The list is terminated by a zero length entry. The first three must be alpha, upper, lower, as this is assumed for handling case independence. */ static const char *const posix_names[] = { "alpha", "lower", "upper", "alnum", "ascii", "blank", "cntrl", "digit", "graph", "print", "punct", "space", "word", "xdigit" }; static const uschar posix_name_lengths[] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 6, 0 }; /* Table of class bit maps for each POSIX class; up to three may be combined to form the class. The table for [:blank:] is dynamically modified to remove the vertical space characters. */ static const int posix_class_maps[] = { cbit_lower, cbit_upper, -1, /* alpha */ cbit_lower, -1, -1, /* lower */ cbit_upper, -1, -1, /* upper */ cbit_digit, cbit_lower, cbit_upper, /* alnum */ cbit_print, cbit_cntrl, -1, /* ascii */ cbit_space, -1, -1, /* blank - a GNU extension */ cbit_cntrl, -1, -1, /* cntrl */ cbit_digit, -1, -1, /* digit */ cbit_graph, -1, -1, /* graph */ cbit_print, -1, -1, /* print */ cbit_punct, -1, -1, /* punct */ cbit_space, -1, -1, /* space */ cbit_word, -1, -1, /* word - a Perl extension */ cbit_xdigit,-1, -1 /* xdigit */ }; /* Table to identify digits and hex digits. This is used when compiling patterns. Note that the tables in chartables are dependent on the locale, and may mark arbitrary characters as digits - but the PCRE compiling code expects to handle only 0-9, a-z, and A-Z as digits when compiling. That is why we have a private table here. It costs 256 bytes, but it is a lot faster than doing character value tests (at least in some simple cases I timed), and in some applications one wants PCRE to compile efficiently as well as match efficiently. For convenience, we use the same bit definitions as in chartables: 0x04 decimal digit 0x08 hexadecimal digit Then we can use ctype_digit and ctype_xdigit in the code. */ #if !EBCDIC /* This is the "normal" case, for ASCII systems */ static const unsigned char digitab[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 8- 15 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - ' */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ( - / */ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c, /* 0 - 7 */ 0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00, /* 8 - ? */ 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* @ - G */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* H - O */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* P - W */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* X - _ */ 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* ` - g */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* h - o */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* p - w */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* x -127 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 128-135 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 136-143 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144-151 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 152-159 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160-167 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 168-175 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 176-183 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 192-199 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 200-207 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 208-215 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 216-223 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 224-231 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 232-239 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 240-247 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/* 248-255 */ #else /* This is the "abnormal" case, for EBCDIC systems */ static const unsigned char digitab[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 0 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 8- 15 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 10 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 32- 39 20 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 40- 47 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 48- 55 30 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 56- 63 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - 71 40 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 72- | */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* & - 87 50 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 88- */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - -103 60 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 104- ? */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 112-119 70 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 120- " */ 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* 128- g 80 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* h -143 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144- p 90 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* q -159 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160- x A0 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* y -175 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ^ -183 B0 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */ 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* { - G C0 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* H -207 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* } - P D0 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Q -223 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* \ - X E0 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Y -239 */ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c, /* 0 - 7 F0 */ 0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00};/* 8 -255 */ static const unsigned char ebcdic_chartab[] = { /* chartable partial dup */ 0x80,0x00,0x00,0x00,0x00,0x01,0x00,0x00, /* 0- 7 */ 0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x00, /* 8- 15 */ 0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00, /* 16- 23 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */ 0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00, /* 32- 39 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 40- 47 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 48- 55 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 56- 63 */ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - 71 */ 0x00,0x00,0x00,0x80,0x00,0x80,0x80,0x80, /* 72- | */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* & - 87 */ 0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00, /* 88- */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - -103 */ 0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x80, /* 104- ? */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 112-119 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 120- " */ 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* 128- g */ 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* h -143 */ 0x00,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* 144- p */ 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* q -159 */ 0x00,0x00,0x12,0x12,0x12,0x12,0x12,0x12, /* 160- x */ 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* y -175 */ 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ^ -183 */ 0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00, /* 184-191 */ 0x80,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* { - G */ 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* H -207 */ 0x00,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* } - P */ 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* Q -223 */ 0x00,0x00,0x12,0x12,0x12,0x12,0x12,0x12, /* \ - X */ 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* Y -239 */ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c, /* 0 - 7 */ 0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x00};/* 8 -255 */ #endif /* Definition to allow mutual recursion */ static BOOL compile_regex(int, int, int *, uschar **, const uschar **, const char **, BOOL, int, int *, int *, branch_chain *, compile_data *); /* Structure for building a chain of data that actually lives on the stack, for holding the values of the subject pointer at the start of each subpattern, so as to detect when an empty string has been matched by a subpattern - to break infinite loops. When NO_RECURSE is set, these blocks are on the heap, not on the stack. */ typedef struct eptrblock { struct eptrblock *epb_prev; const uschar *epb_saved_eptr; } eptrblock; /* Flag bits for the match() function */ #define match_condassert 0x01 /* Called to check a condition assertion */ #define match_isgroup 0x02 /* Set if start of bracketed group */ /* Non-error returns from the match() function. Error returns are externally defined PCRE_ERROR_xxx codes, which are all negative. */ #define MATCH_MATCH 1 #define MATCH_NOMATCH 0 /************************************************* * Global variables * *************************************************/ /* PCRE is thread-clean and doesn't use any global variables in the normal sense. However, it calls memory allocation and free functions via the four indirections below, and it can optionally do callouts. These values can be changed by the caller, but are shared between all threads. However, when compiling for Virtual Pascal, things are done differently (see pcre.in). */ #ifndef VPCOMPAT #ifdef __cplusplus extern "C" void *(*pcre_malloc)(size_t) = malloc; extern "C" void (*pcre_free)(void *) = free; extern "C" void *(*pcre_stack_malloc)(size_t) = malloc; extern "C" void (*pcre_stack_free)(void *) = free; extern "C" int (*pcre_callout)(pcre_callout_block *) = NULL; #else void *(*pcre_malloc)(size_t) = malloc; void (*pcre_free)(void *) = free; void *(*pcre_stack_malloc)(size_t) = malloc; void (*pcre_stack_free)(void *) = free; int (*pcre_callout)(pcre_callout_block *) = NULL; #endif #endif /************************************************* * Macros and tables for character handling * *************************************************/ /* When UTF-8 encoding is being used, a character is no longer just a single byte. The macros for character handling generate simple sequences when used in byte-mode, and more complicated ones for UTF-8 characters. */ #ifndef SUPPORT_UTF8 #define GETCHAR(c, eptr) c = *eptr; #define GETCHARINC(c, eptr) c = *eptr++; #define GETCHARINCTEST(c, eptr) c = *eptr++; #define GETCHARLEN(c, eptr, len) c = *eptr; #define BACKCHAR(eptr) #else /* SUPPORT_UTF8 */ /* Get the next UTF-8 character, not advancing the pointer. This is called when we know we are in UTF-8 mode. */ #define GETCHAR(c, eptr) \ c = *eptr; \ if ((c & 0xc0) == 0xc0) \ { \ int gcii; \ int gcaa = utf8_table4[c & 0x3f]; /* Number of additional bytes */ \ int gcss = 6*gcaa; \ c = (c & utf8_table3[gcaa]) << gcss; \ for (gcii = 1; gcii <= gcaa; gcii++) \ { \ gcss -= 6; \ c |= (eptr[gcii] & 0x3f) << gcss; \ } \ } /* Get the next UTF-8 character, advancing the pointer. This is called when we know we are in UTF-8 mode. */ #define GETCHARINC(c, eptr) \ c = *eptr++; \ if ((c & 0xc0) == 0xc0) \ { \ int gcaa = utf8_table4[c & 0x3f]; /* Number of additional bytes */ \ int gcss = 6*gcaa; \ c = (c & utf8_table3[gcaa]) << gcss; \ while (gcaa-- > 0) \ { \ gcss -= 6; \ c |= (*eptr++ & 0x3f) << gcss; \ } \ } /* Get the next character, testing for UTF-8 mode, and advancing the pointer */ #define GETCHARINCTEST(c, eptr) \ c = *eptr++; \ if (md->utf8 && (c & 0xc0) == 0xc0) \ { \ int gcaa = utf8_table4[c & 0x3f]; /* Number of additional bytes */ \ int gcss = 6*gcaa; \ c = (c & utf8_table3[gcaa]) << gcss; \ while (gcaa-- > 0) \ { \ gcss -= 6; \ c |= (*eptr++ & 0x3f) << gcss; \ } \ } /* Get the next UTF-8 character, not advancing the pointer, incrementing length if there are extra bytes. This is called when we know we are in UTF-8 mode. */ #define GETCHARLEN(c, eptr, len) \ c = *eptr; \ if ((c & 0xc0) == 0xc0) \ { \ int gcii; \ int gcaa = utf8_table4[c & 0x3f]; /* Number of additional bytes */ \ int gcss = 6*gcaa; \ c = (c & utf8_table3[gcaa]) << gcss; \ for (gcii = 1; gcii <= gcaa; gcii++) \ { \ gcss -= 6; \ c |= (eptr[gcii] & 0x3f) << gcss; \ } \ len += gcaa; \ } /* If the pointer is not at the start of a character, move it back until it is. Called only in UTF-8 mode. */ #define BACKCHAR(eptr) while((*eptr & 0xc0) == 0x80) eptr--; #endif /************************************************* * Default character tables * *************************************************/ /* A default set of character tables is included in the PCRE binary. Its source is built by the maketables auxiliary program, which uses the default C ctypes functions, and put in the file chartables.c. These tables are used by PCRE whenever the caller of pcre_compile() does not provide an alternate set of tables. */ #include "chartables.c" #ifdef SUPPORT_UTF8 /************************************************* * Tables for UTF-8 support * *************************************************/ /* These are the breakpoints for different numbers of bytes in a UTF-8 character. */ static const int utf8_table1[] = { 0x7f, 0x7ff, 0xffff, 0x1fffff, 0x3ffffff, 0x7fffffff}; /* These are the indicator bits and the mask for the data bits to set in the first byte of a character, indexed by the number of additional bytes. */ static const int utf8_table2[] = { 0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc}; static const int utf8_table3[] = { 0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01}; /* Table of the number of extra characters, indexed by the first character masked with 0x3f. The highest number for a valid UTF-8 character is in fact 0x3d. */ static const uschar utf8_table4[] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 }; /************************************************* * Convert character value to UTF-8 * *************************************************/ /* This function takes an integer value in the range 0 - 0x7fffffff and encodes it as a UTF-8 character in 0 to 6 bytes. Arguments: cvalue the character value buffer pointer to buffer for result - at least 6 bytes long Returns: number of characters placed in the buffer */ static int ord2utf8(int cvalue, uschar *buffer) { register int i, j; for (i = 0; i < sizeof(utf8_table1)/sizeof(int); i++) if (cvalue <= utf8_table1[i]) break; buffer += i; for (j = i; j > 0; j--) { *buffer-- = 0x80 | (cvalue & 0x3f); cvalue >>= 6; } *buffer = utf8_table2[i] | cvalue; return i + 1; } #endif /************************************************* * Print compiled regex * *************************************************/ /* The code for doing this is held in a separate file that is also included in pcretest.c. It defines a function called print_internals(). */ #ifdef DEBUG #if 0 // commented by VVD { #include "printint.c" #endif // } commented by VVD #endif /************************************************* * Return version string * *************************************************/ #define STRING(a) # a #define XSTRING(s) STRING(s) EXPORT const char * pcre_version(void) { return XSTRING(PCRE_MAJOR) "." XSTRING(PCRE_MINOR) " " XSTRING(PCRE_DATE); } /************************************************* * Flip bytes in an integer * *************************************************/ /* This function is called when the magic number in a regex doesn't match in order to flip its bytes to see if we are dealing with a pattern that was compiled on a host of different endianness. If so, this function is used to flip other byte values. Arguments: value the number to flip n the number of bytes to flip (assumed to be 2 or 4) Returns: the flipped value */ static long int byteflip(long int value, int n) { if (n == 2) return ((value & 0x00ff) << 8) | ((value & 0xff00) >> 8); return ((value & 0x000000ff) << 24) | ((value & 0x0000ff00) << 8) | ((value & 0x00ff0000) >> 8) | ((value & 0xff000000) >> 24); } /************************************************* * Test for a byte-flipped compiled regex * *************************************************/ /* This function is called from pce_exec() and also from pcre_fullinfo(). Its job is to test whether the regex is byte-flipped - that is, it was compiled on a system of opposite endianness. The function is called only when the native MAGIC_NUMBER test fails. If the regex is indeed flipped, we flip all the relevant values into a different data block, and return it. Arguments: re points to the regex study points to study data, or NULL internal_re points to a new regex block internal_study points to a new study block Returns: the new block if is is indeed a byte-flipped regex NULL if it is not */ static real_pcre * try_flipped(const real_pcre *re, real_pcre *internal_re, const pcre_study_data *study, pcre_study_data *internal_study) { if (byteflip(re->magic_number, sizeof(re->magic_number)) != MAGIC_NUMBER) return NULL; *internal_re = *re; /* To copy other fields */ internal_re->size = byteflip(re->size, sizeof(re->size)); internal_re->options = byteflip(re->options, sizeof(re->options)); #ifdef _WIN32 #pragma warning( disable : 4244 ) // mgraph gets this #endif internal_re->top_bracket = byteflip(re->top_bracket, sizeof(re->top_bracket)); internal_re->top_backref = byteflip(re->top_backref, sizeof(re->top_backref)); internal_re->first_byte = byteflip(re->first_byte, sizeof(re->first_byte)); internal_re->req_byte = byteflip(re->req_byte, sizeof(re->req_byte)); internal_re->name_table_offset = byteflip(re->name_table_offset, sizeof(re->name_table_offset)); internal_re->name_entry_size = byteflip(re->name_entry_size, sizeof(re->name_entry_size)); internal_re->name_count = byteflip(re->name_count, sizeof(re->name_count)); #ifdef _WIN32 #pragma warning( default : 4244 ) // mgraph gets this #endif if (study != NULL) { *internal_study = *study; /* To copy other fields */ internal_study->size = byteflip(study->size, sizeof(study->size)); internal_study->options = byteflip(study->options, sizeof(study->options)); } return internal_re; } /************************************************* * (Obsolete) Return info about compiled pattern * *************************************************/ /* This is the original "info" function. It picks potentially useful data out of the private structure, but its interface was too rigid. It remains for backwards compatibility. The public options are passed back in an int - though the re->options field has been expanded to a long int, all the public options at the low end of it, and so even on 16-bit systems this will still be OK. Therefore, I haven't changed the API for pcre_info(). Arguments: argument_re points to compiled code optptr where to pass back the options first_byte where to pass back the first character, or -1 if multiline and all branches start ^, or -2 otherwise Returns: number of capturing subpatterns or negative values on error */ EXPORT int pcre_info(const pcre *argument_re, int *optptr, int *first_byte) { real_pcre internal_re; const real_pcre *re = (const real_pcre *)argument_re; if (re == NULL) return PCRE_ERROR_NULL; if (re->magic_number != MAGIC_NUMBER) { re = try_flipped(re, &internal_re, NULL, NULL); if (re == NULL) return PCRE_ERROR_BADMAGIC; } if (optptr != NULL) *optptr = (int)(re->options & PUBLIC_OPTIONS); if (first_byte != NULL) *first_byte = ((re->options & PCRE_FIRSTSET) != 0)? re->first_byte : ((re->options & PCRE_STARTLINE) != 0)? -1 : -2; return re->top_bracket; } /************************************************* * Return info about compiled pattern * *************************************************/ /* This is a newer "info" function which has an extensible interface so that additional items can be added compatibly. Arguments: argument_re points to compiled code extra_data points extra data, or NULL what what information is required where where to put the information Returns: 0 if data returned, negative on error */ EXPORT int pcre_fullinfo(const pcre *argument_re, const pcre_extra *extra_data, int what, void *where) { real_pcre internal_re; pcre_study_data internal_study; const real_pcre *re = (const real_pcre *)argument_re; const pcre_study_data *study = NULL; if (re == NULL || where == NULL) return PCRE_ERROR_NULL; if (extra_data != NULL && (extra_data->flags & PCRE_EXTRA_STUDY_DATA) != 0) study = (const pcre_study_data *)extra_data->study_data; if (re->magic_number != MAGIC_NUMBER) { re = try_flipped(re, &internal_re, study, &internal_study); if (re == NULL) return PCRE_ERROR_BADMAGIC; if (study != NULL) study = &internal_study; } switch (what) { case PCRE_INFO_OPTIONS: *((unsigned long int *)where) = re->options & PUBLIC_OPTIONS; break; case PCRE_INFO_SIZE: *((size_t *)where) = re->size; break; case PCRE_INFO_STUDYSIZE: *((size_t *)where) = (study == NULL)? 0 : study->size; break; case PCRE_INFO_CAPTURECOUNT: *((int *)where) = re->top_bracket; break; case PCRE_INFO_BACKREFMAX: *((int *)where) = re->top_backref; break; case PCRE_INFO_FIRSTBYTE: *((int *)where) = ((re->options & PCRE_FIRSTSET) != 0)? re->first_byte : ((re->options & PCRE_STARTLINE) != 0)? -1 : -2; break; /* Make sure we pass back the pointer to the bit vector in the external block, not the internal copy (with flipped integer fields). */ case PCRE_INFO_FIRSTTABLE: *((const uschar **)where) = (study != NULL && (study->options & PCRE_STUDY_MAPPED) != 0)? ((const pcre_study_data *)extra_data->study_data)->start_bits : NULL; break; case PCRE_INFO_LASTLITERAL: *((int *)where) = ((re->options & PCRE_REQCHSET) != 0)? re->req_byte : -1; break; case PCRE_INFO_NAMEENTRYSIZE: *((int *)where) = re->name_entry_size; break; case PCRE_INFO_NAMECOUNT: *((int *)where) = re->name_count; break; case PCRE_INFO_NAMETABLE: *((const uschar **)where) = (const uschar *)re + re->name_table_offset; break; case PCRE_INFO_DEFAULT_TABLES: *((const uschar **)where) = (const uschar *)pcre_default_tables; break; default: return PCRE_ERROR_BADOPTION; } return 0; } /************************************************* * Return info about what features are configured * *************************************************/ /* This is function which has an extensible interface so that additional items can be added compatibly. Arguments: what what information is required where where to put the information Returns: 0 if data returned, negative on error */ EXPORT int pcre_config(int what, void *where) { switch (what) { case PCRE_CONFIG_UTF8: #ifdef SUPPORT_UTF8 *((int *)where) = 1; #else *((int *)where) = 0; #endif break; case PCRE_CONFIG_UNICODE_PROPERTIES: #ifdef SUPPORT_UCP *((int *)where) = 1; #else *((int *)where) = 0; #endif break; case PCRE_CONFIG_NEWLINE: *((int *)where) = NEWLINE; break; case PCRE_CONFIG_LINK_SIZE: *((int *)where) = LINK_SIZE; break; case PCRE_CONFIG_POSIX_MALLOC_THRESHOLD: *((int *)where) = POSIX_MALLOC_THRESHOLD; break; case PCRE_CONFIG_MATCH_LIMIT: *((unsigned int *)where) = MATCH_LIMIT; break; case PCRE_CONFIG_STACKRECURSE: #ifdef NO_RECURSE *((int *)where) = 0; #else *((int *)where) = 1; #endif break; default: return PCRE_ERROR_BADOPTION; } return 0; } #ifdef DEBUG /************************************************* * Debugging function to print chars * *************************************************/ /* Print a sequence of chars in printable format, stopping at the end of the subject if the requested. Arguments: p points to characters length number to print is_subject TRUE if printing from within md->start_subject md pointer to matching data block, if is_subject is TRUE Returns: nothing */ static void pchars(const uschar *p, int length, BOOL is_subject, match_data *md) { int c; if (is_subject && length > md->end_subject - p) length = md->end_subject - p; while (length-- > 0) if (isprint(c = *(p++))) printf("%c", c); else printf("\\x%02x", c); } #endif /************************************************* * Handle escapes * *************************************************/ /* This function is called when a \ has been encountered. It either returns a positive value for a simple escape such as \n, or a negative value which encodes one of the more complicated things such as \d. When UTF-8 is enabled, a positive value greater than 255 may be returned. On entry, ptr is pointing at the \. On exit, it is on the final character of the escape sequence. Arguments: ptrptr points to the pattern position pointer errorptr points to the pointer to the error message bracount number of previous extracting brackets options the options bits isclass TRUE if inside a character class Returns: zero or positive => a data character negative => a special escape sequence on error, errorptr is set */ static int check_escape(const uschar **ptrptr, const char **errorptr, int bracount, int options, BOOL isclass) { const uschar *ptr = *ptrptr; int c, i; /* If backslash is at the end of the pattern, it's an error. */ c = *(++ptr); if (c == 0) *errorptr = ERR1; /* Non-alphamerics are literals. For digits or letters, do an initial lookup in a table. A non-zero result is something that can be returned immediately. Otherwise further processing may be required. */ #if !EBCDIC /* ASCII coding */ else if (c < '0' || c > 'z') {} /* Not alphameric */ else if ((i = escapes[c - '0']) != 0) c = i; #else /* EBCDIC coding */ else if (c < 'a' || (ebcdic_chartab[c] & 0x0E) == 0) {} /* Not alphameric */ else if ((i = escapes[c - 0x48]) != 0) c = i; #endif /* Escapes that need further processing, or are illegal. */ else { const uschar *oldptr; switch (c) { /* A number of Perl escapes are not handled by PCRE. We give an explicit error. */ case 'l': case 'L': case 'N': case 'u': case 'U': *errorptr = ERR37; break; /* The handling of escape sequences consisting of a string of digits starting with one that is not zero is not straightforward. By experiment, the way Perl works seems to be as follows: Outside a character class, the digits are read as a decimal number. If the number is less than 10, or if there are that many previous extracting left brackets, then it is a back reference. Otherwise, up to three octal digits are read to form an escaped byte. Thus \123 is likely to be octal 123 (cf \0123, which is octal 012 followed by the literal 3). If the octal value is greater than 377, the least significant 8 bits are taken. Inside a character class, \ followed by a digit is always an octal number. */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (!isclass) { oldptr = ptr; c -= '0'; while ((digitab[ptr[1]] & ctype_digit) != 0) c = c * 10 + *(++ptr) - '0'; if (c < 10 || c <= bracount) { c = -(ESC_REF + c); break; } ptr = oldptr; /* Put the pointer back and fall through */ } /* Handle an octal number following \. If the first digit is 8 or 9, Perl generates a binary zero byte and treats the digit as a following literal. Thus we have to pull back the pointer by one. */ if ((c = *ptr) >= '8') { ptr--; c = 0; break; } /* \0 always starts an octal number, but we may drop through to here with a larger first octal digit. */ case '0': c -= '0'; while(i++ < 2 && ptr[1] >= '0' && ptr[1] <= '7') c = c * 8 + *(++ptr) - '0'; c &= 255; /* Take least significant 8 bits */ break; /* \x is complicated when UTF-8 is enabled. \x{ddd} is a character number which can be greater than 0xff, but only if the ddd are hex digits. */ case 'x': #ifdef SUPPORT_UTF8 if (ptr[1] == '{' && (options & PCRE_UTF8) != 0) { const uschar *pt = ptr + 2; register int count = 0; c = 0; while ((digitab[*pt] & ctype_xdigit) != 0) { int cc = *pt++; count++; #if !EBCDIC /* ASCII coding */ if (cc >= 'a') cc -= 32; /* Convert to upper case */ c = c * 16 + cc - ((cc < 'A')? '0' : ('A' - 10)); #else /* EBCDIC coding */ if (cc >= 'a' && cc <= 'z') cc += 64; /* Convert to upper case */ c = c * 16 + cc - ((cc >= '0')? '0' : ('A' - 10)); #endif } if (*pt == '}') { if (c < 0 || count > 8) *errorptr = ERR34; ptr = pt; break; } /* If the sequence of hex digits does not end with '}', then we don't recognize this construct; fall through to the normal \x handling. */ } #endif /* Read just a single hex char */ c = 0; while (i++ < 2 && (digitab[ptr[1]] & ctype_xdigit) != 0) { int cc; /* Some compilers don't like ++ */ cc = *(++ptr); /* in initializers */ #if !EBCDIC /* ASCII coding */ if (cc >= 'a') cc -= 32; /* Convert to upper case */ c = c * 16 + cc - ((cc < 'A')? '0' : ('A' - 10)); #else /* EBCDIC coding */ if (cc <= 'z') cc += 64; /* Convert to upper case */ c = c * 16 + cc - ((cc >= '0')? '0' : ('A' - 10)); #endif } break; /* Other special escapes not starting with a digit are straightforward */ case 'c': c = *(++ptr); if (c == 0) { *errorptr = ERR2; return 0; } /* A letter is upper-cased; then the 0x40 bit is flipped. This coding is ASCII-specific, but then the whole concept of \cx is ASCII-specific. (However, an EBCDIC equivalent has now been added.) */ #if !EBCDIC /* ASCII coding */ if (c >= 'a' && c <= 'z') c -= 32; c ^= 0x40; #else /* EBCDIC coding */ if (c >= 'a' && c <= 'z') c += 64; c ^= 0xC0; #endif break; /* PCRE_EXTRA enables extensions to Perl in the matter of escapes. Any other alphameric following \ is an error if PCRE_EXTRA was set; otherwise, for Perl compatibility, it is a literal. This code looks a bit odd, but there used to be some cases other than the default, and there may be again in future, so I haven't "optimized" it. */ default: if ((options & PCRE_EXTRA) != 0) switch(c) { default: *errorptr = ERR3; break; } break; } } *ptrptr = ptr; return c; } #ifdef SUPPORT_UCP /************************************************* * Handle \P and \p * *************************************************/ /* This function is called after \P or \p has been encountered, provided that PCRE is compiled with support for Unicode properties. On entry, ptrptr is pointing at the P or p. On exit, it is pointing at the final character of the escape sequence. Argument: ptrptr points to the pattern position pointer negptr points to a boolean that is set TRUE for negation else FALSE errorptr points to the pointer to the error message Returns: value from ucp_type_table, or -1 for an invalid type */ static int get_ucp(const uschar **ptrptr, BOOL *negptr, const char **errorptr) { int c, i, bot, top; const uschar *ptr = *ptrptr; char name[4]; c = *(++ptr); if (c == 0) goto ERROR_RETURN; *negptr = FALSE; /* \P or \p can be followed by a one- or two-character name in {}, optionally preceded by ^ for negation. */ if (c == '{') { if (ptr[1] == '^') { *negptr = TRUE; ptr++; } for (i = 0; i <= 2; i++) { c = *(++ptr); if (c == 0) goto ERROR_RETURN; if (c == '}') break; name[i] = c; } if (c !='}') /* Try to distinguish error cases */ { while (*(++ptr) != 0 && *ptr != '}'); if (*ptr == '}') goto UNKNOWN_RETURN; else goto ERROR_RETURN; } name[i] = 0; } /* Otherwise there is just one following character */ else { name[0] = c; name[1] = 0; } *ptrptr = ptr; /* Search for a recognized property name using binary chop */ bot = 0; top = sizeof(utt)/sizeof(ucp_type_table); while (bot < top) { i = (bot + top)/2; c = strcmp(name, utt[i].name); if (c == 0) return utt[i].value; if (c > 0) bot = i + 1; else top = i; } UNKNOWN_RETURN: *errorptr = ERR47; *ptrptr = ptr; return -1; ERROR_RETURN: *errorptr = ERR46; *ptrptr = ptr; return -1; } #endif /************************************************* * Check for counted repeat * *************************************************/ /* This function is called when a '{' is encountered in a place where it might start a quantifier. It looks ahead to see if it really is a quantifier or not. It is only a quantifier if it is one of the forms {ddd} {ddd,} or {ddd,ddd} where the ddds are digits. Arguments: p pointer to the first char after '{' Returns: TRUE or FALSE */ static BOOL is_counted_repeat(const uschar *p) { if ((digitab[*p++] & ctype_digit) == 0) return FALSE; while ((digitab[*p] & ctype_digit) != 0) p++; if (*p == '}') return TRUE; if (*p++ != ',') return FALSE; if (*p == '}') return TRUE; if ((digitab[*p++] & ctype_digit) == 0) return FALSE; while ((digitab[*p] & ctype_digit) != 0) p++; return (*p == '}'); } /************************************************* * Read repeat counts * *************************************************/ /* Read an item of the form {n,m} and return the values. This is called only after is_counted_repeat() has confirmed that a repeat-count quantifier exists, so the syntax is guaranteed to be correct, but we need to check the values. Arguments: p pointer to first char after '{' minp pointer to int for min maxp pointer to int for max returned as -1 if no max errorptr points to pointer to error message Returns: pointer to '}' on success; current ptr on error, with errorptr set */ static const uschar * read_repeat_counts(const uschar *p, int *minp, int *maxp, const char **errorptr) { int min = 0; int max = -1; while ((digitab[*p] & ctype_digit) != 0) min = min * 10 + *p++ - '0'; if (*p == '}') max = min; else { if (*(++p) != '}') { max = 0; while((digitab[*p] & ctype_digit) != 0) max = max * 10 + *p++ - '0'; if (max < min) { *errorptr = ERR4; return p; } } } /* Do paranoid checks, then fill in the required variables, and pass back the pointer to the terminating '}'. */ if (min > 65535 || max > 65535) *errorptr = ERR5; else { *minp = min; *maxp = max; } return p; } /************************************************* * Find first significant op code * *************************************************/ /* This is called by several functions that scan a compiled expression looking for a fixed first character, or an anchoring op code etc. It skips over things that do not influence this. For some calls, a change of option is important. For some calls, it makes sense to skip negative forward and all backward assertions, and also the \b assertion; for others it does not. Arguments: code pointer to the start of the group options pointer to external options optbit the option bit whose changing is significant, or zero if none are skipassert TRUE if certain assertions are to be skipped Returns: pointer to the first significant opcode */ static const uschar* first_significant_code(const uschar *code, int *options, int optbit, BOOL skipassert) { for (;;) { switch ((int)*code) { case OP_OPT: if (optbit > 0 && ((int)code[1] & optbit) != (*options & optbit)) *options = (int)code[1]; code += 2; break; case OP_ASSERT_NOT: case OP_ASSERTBACK: case OP_ASSERTBACK_NOT: if (!skipassert) return code; do code += GET(code, 1); while (*code == OP_ALT); code += OP_lengths[*code]; break; case OP_WORD_BOUNDARY: case OP_NOT_WORD_BOUNDARY: if (!skipassert) return code; /* Fall through */ case OP_CALLOUT: case OP_CREF: case OP_BRANUMBER: code += OP_lengths[*code]; break; default: return code; } } /* Control never reaches here */ } /************************************************* * Find the fixed length of a pattern * *************************************************/ /* Scan a pattern and compute the fixed length of subject that will match it, if the length is fixed. This is needed for dealing with backward assertions. In UTF8 mode, the result is in characters rather than bytes. Arguments: code points to the start of the pattern (the bracket) options the compiling options Returns: the fixed length, or -1 if there is no fixed length, or -2 if \C was encountered */ static int find_fixedlength(uschar *code, int options) { int length = -1; register int branchlength = 0; register uschar *cc = code + 1 + LINK_SIZE; /* Scan along the opcodes for this branch. If we get to the end of the branch, check the length against that of the other branches. */ for (;;) { int d; register int op = *cc; if (op >= OP_BRA) op = OP_BRA; switch (op) { case OP_BRA: case OP_ONCE: case OP_COND: d = find_fixedlength(cc, options); if (d < 0) return d; branchlength += d; do cc += GET(cc, 1); while (*cc == OP_ALT); cc += 1 + LINK_SIZE; break; /* Reached end of a branch; if it's a ket it is the end of a nested call. If it's ALT it is an alternation in a nested call. If it is END it's the end of the outer call. All can be handled by the same code. */ case OP_ALT: case OP_KET: case OP_KETRMAX: case OP_KETRMIN: case OP_END: if (length < 0) length = branchlength; else if (length != branchlength) return -1; if (*cc != OP_ALT) return length; cc += 1 + LINK_SIZE; branchlength = 0; break; /* Skip over assertive subpatterns */ case OP_ASSERT: case OP_ASSERT_NOT: case OP_ASSERTBACK: case OP_ASSERTBACK_NOT: do cc += GET(cc, 1); while (*cc == OP_ALT); /* Fall through */ /* Skip over things that don't match chars */ case OP_REVERSE: case OP_BRANUMBER: case OP_CREF: case OP_OPT: case OP_CALLOUT: case OP_SOD: case OP_SOM: case OP_EOD: case OP_EODN: case OP_CIRC: case OP_DOLL: case OP_NOT_WORD_BOUNDARY: case OP_WORD_BOUNDARY: cc += OP_lengths[*cc]; break; /* Handle literal characters */ case OP_CHAR: case OP_CHARNC: branchlength++; cc += 2; #ifdef SUPPORT_UTF8 if ((options & PCRE_UTF8) != 0) { while ((*cc & 0xc0) == 0x80) cc++; } #endif break; /* Handle exact repetitions. The count is already in characters, but we need to skip over a multibyte character in UTF8 mode. */ case OP_EXACT: branchlength += GET2(cc,1); cc += 4; #ifdef SUPPORT_UTF8 if ((options & PCRE_UTF8) != 0) { while((*cc & 0x80) == 0x80) cc++; } #endif break; case OP_TYPEEXACT: branchlength += GET2(cc,1); cc += 4; break; /* Handle single-char matchers */ case OP_PROP: case OP_NOTPROP: cc++; /* Fall through */ case OP_NOT_DIGIT: case OP_DIGIT: case OP_NOT_WHITESPACE: case OP_WHITESPACE: case OP_NOT_WORDCHAR: case OP_WORDCHAR: case OP_ANY: branchlength++; cc++; break; /* The single-byte matcher isn't allowed */ case OP_ANYBYTE: return -2; /* Check a class for variable quantification */ #ifdef SUPPORT_UTF8 case OP_XCLASS: cc += GET(cc, 1) - 33; /* Fall through */ #endif case OP_CLASS: case OP_NCLASS: cc += 33; switch (*cc) { case OP_CRSTAR: case OP_CRMINSTAR: case OP_CRQUERY: case OP_CRMINQUERY: return -1; case OP_CRRANGE: case OP_CRMINRANGE: if (GET2(cc,1) != GET2(cc,3)) return -1; branchlength += GET2(cc,1); cc += 5; break; default: branchlength++; } break; /* Anything else is variable length */ default: return -1; } } /* Control never gets here */ } /************************************************* * Scan compiled regex for numbered bracket * *************************************************/ /* This little function scans through a compiled pattern until it finds a capturing bracket with the given number. Arguments: code points to start of expression utf8 TRUE in UTF-8 mode number the required bracket number Returns: pointer to the opcode for the bracket, or NULL if not found */ static const uschar * find_bracket(const uschar *code, BOOL utf8, int number) { #ifndef SUPPORT_UTF8 utf8 = utf8; /* Stop pedantic compilers complaining */ #endif for (;;) { register int c = *code; if (c == OP_END) return NULL; else if (c > OP_BRA) { int n = c - OP_BRA; if (n > EXTRACT_BASIC_MAX) n = GET2(code, 2+LINK_SIZE); if (n == number) return (uschar *)code; code += OP_lengths[OP_BRA]; } else { code += OP_lengths[c]; #ifdef SUPPORT_UTF8 /* In UTF-8 mode, opcodes that are followed by a character may be followed by a multi-byte character. The length in the table is a minimum, so we have to scan along to skip the extra bytes. All opcodes are less than 128, so we can use relatively efficient code. */ if (utf8) switch(c) { case OP_CHAR: case OP_CHARNC: case OP_EXACT: case OP_UPTO: case OP_MINUPTO: case OP_STAR: case OP_MINSTAR: case OP_PLUS: case OP_MINPLUS: case OP_QUERY: case OP_MINQUERY: while ((*code & 0xc0) == 0x80) code++; break; /* XCLASS is used for classes that cannot be represented just by a bit map. This includes negated single high-valued characters. The length in the table is zero; the actual length is stored in the compiled code. */ case OP_XCLASS: code += GET(code, 1) + 1; break; } #endif } } } /************************************************* * Scan compiled regex for recursion reference * *************************************************/ /* This little function scans through a compiled pattern until it finds an instance of OP_RECURSE. Arguments: code points to start of expression utf8 TRUE in UTF-8 mode Returns: pointer to the opcode for OP_RECURSE, or NULL if not found */ static const uschar * find_recurse(const uschar *code, BOOL utf8) { #ifndef SUPPORT_UTF8 utf8 = utf8; /* Stop pedantic compilers complaining */ #endif for (;;) { register int c = *code; if (c == OP_END) return NULL; else if (c == OP_RECURSE) return code; else if (c > OP_BRA) { code += OP_lengths[OP_BRA]; } else { code += OP_lengths[c]; #ifdef SUPPORT_UTF8 /* In UTF-8 mode, opcodes that are followed by a character may be followed by a multi-byte character. The length in the table is a minimum, so we have to scan along to skip the extra bytes. All opcodes are less than 128, so we can use relatively efficient code. */ if (utf8) switch(c) { case OP_CHAR: case OP_CHARNC: case OP_EXACT: case OP_UPTO: case OP_MINUPTO: case OP_STAR: case OP_MINSTAR: case OP_PLUS: case OP_MINPLUS: case OP_QUERY: case OP_MINQUERY: while ((*code & 0xc0) == 0x80) code++; break; /* XCLASS is used for classes that cannot be represented just by a bit map. This includes negated single high-valued characters. The length in the table is zero; the actual length is stored in the compiled code. */ case OP_XCLASS: code += GET(code, 1) + 1; break; } #endif } } } /************************************************* * Scan compiled branch for non-emptiness * *************************************************/ /* This function scans through a branch of a compiled pattern to see whether it can match the empty string or not. It is called only from could_be_empty() below. Note that first_significant_code() skips over assertions. If we hit an unclosed bracket, we return "empty" - this means we've struck an inner bracket whose current branch will already have been scanned. Arguments: code points to start of search endcode points to where to stop utf8 TRUE if in UTF8 mode Returns: TRUE if what is matched could be empty */ static BOOL could_be_empty_branch(const uschar *code, const uschar *endcode, BOOL utf8) { register int c; for (code = first_significant_code(code + 1 + LINK_SIZE, NULL, 0, TRUE); code < endcode; code = first_significant_code(code + OP_lengths[c], NULL, 0, TRUE)) { const uschar *ccode; c = *code; if (c >= OP_BRA) { BOOL empty_branch; if (GET(code, 1) == 0) return TRUE; /* Hit unclosed bracket */ /* Scan a closed bracket */ empty_branch = FALSE; do { if (!empty_branch && could_be_empty_branch(code, endcode, utf8)) empty_branch = TRUE; code += GET(code, 1); } while (*code == OP_ALT); if (!empty_branch) return FALSE; /* All branches are non-empty */ code += 1 + LINK_SIZE; c = *code; } else switch (c) { /* Check for quantifiers after a class */ #ifdef SUPPORT_UTF8 case OP_XCLASS: ccode = code + GET(code, 1); goto CHECK_CLASS_REPEAT; #endif case OP_CLASS: case OP_NCLASS: ccode = code + 33; #ifdef SUPPORT_UTF8 CHECK_CLASS_REPEAT: #endif switch (*ccode) { case OP_CRSTAR: /* These could be empty; continue */ case OP_CRMINSTAR: case OP_CRQUERY: case OP_CRMINQUERY: break; default: /* Non-repeat => class must match */ case OP_CRPLUS: /* These repeats aren't empty */ case OP_CRMINPLUS: return FALSE; case OP_CRRANGE: case OP_CRMINRANGE: if (GET2(ccode, 1) > 0) return FALSE; /* Minimum > 0 */ break; } break; /* Opcodes that must match a character */ case OP_PROP: case OP_NOTPROP: case OP_EXTUNI: case OP_NOT_DIGIT: case OP_DIGIT: case OP_NOT_WHITESPACE: case OP_WHITESPACE: case OP_NOT_WORDCHAR: case OP_WORDCHAR: case OP_ANY: case OP_ANYBYTE: case OP_CHAR: case OP_CHARNC: case OP_NOT: case OP_PLUS: case OP_MINPLUS: case OP_EXACT: case OP_NOTPLUS: case OP_NOTMINPLUS: case OP_NOTEXACT: case OP_TYPEPLUS: case OP_TYPEMINPLUS: case OP_TYPEEXACT: return FALSE; /* End of branch */ case OP_KET: case OP_KETRMAX: case OP_KETRMIN: case OP_ALT: return TRUE; /* In UTF-8 mode, STAR, MINSTAR, QUERY, MINQUERY, UPTO, and MINUPTO may be followed by a multibyte character */ #ifdef SUPPORT_UTF8 case OP_STAR: case OP_MINSTAR: case OP_QUERY: case OP_MINQUERY: case OP_UPTO: case OP_MINUPTO: if (utf8) while ((code[2] & 0xc0) == 0x80) code++; break; #endif } } return TRUE; } /************************************************* * Scan compiled regex for non-emptiness * *************************************************/ /* This function is called to check for left recursive calls. We want to check the current branch of the current pattern to see if it could match the empty string. If it could, we must look outwards for branches at other levels, stopping when we pass beyond the bracket which is the subject of the recursion. Arguments: code points to start of the recursion endcode points to where to stop (current RECURSE item) bcptr points to the chain of current (unclosed) branch starts utf8 TRUE if in UTF-8 mode Returns: TRUE if what is matched could be empty */ static BOOL could_be_empty(const uschar *code, const uschar *endcode, branch_chain *bcptr, BOOL utf8) { while (bcptr != NULL && bcptr->current >= code) { if (!could_be_empty_branch(bcptr->current, endcode, utf8)) return FALSE; bcptr = bcptr->outer; } return TRUE; } /************************************************* * Check for POSIX class syntax * *************************************************/ /* This function is called when the sequence "[:" or "[." or "[=" is encountered in a character class. It checks whether this is followed by an optional ^ and then a sequence of letters, terminated by a matching ":]" or ".]" or "=]". Argument: ptr pointer to the initial [ endptr where to return the end pointer cd pointer to compile data Returns: TRUE or FALSE */ static BOOL check_posix_syntax(const uschar *ptr, const uschar **endptr, compile_data *cd) { int terminator; /* Don't combine these lines; the Solaris cc */ terminator = *(++ptr); /* compiler warns about "non-constant" initializer. */ if (*(++ptr) == '^') ptr++; while ((cd->ctypes[*ptr] & ctype_letter) != 0) ptr++; if (*ptr == terminator && ptr[1] == ']') { *endptr = ptr; return TRUE; } return FALSE; } /************************************************* * Check POSIX class name * *************************************************/ /* This function is called to check the name given in a POSIX-style class entry such as [:alnum:]. Arguments: ptr points to the first letter len the length of the name Returns: a value representing the name, or -1 if unknown */ static int check_posix_name(const uschar *ptr, int len) { register int yield = 0; while (posix_name_lengths[yield] != 0) { if (len == posix_name_lengths[yield] && strncmp((const char *)ptr, posix_names[yield], len) == 0) return yield; yield++; } return -1; } /************************************************* * Adjust OP_RECURSE items in repeated group * *************************************************/ /* OP_RECURSE items contain an offset from the start of the regex to the group that is referenced. This means that groups can be replicated for fixed repetition simply by copying (because the recursion is allowed to refer to earlier groups that are outside the current group). However, when a group is optional (i.e. the minimum quantifier is zero), OP_BRAZERO is inserted before it, after it has been compiled. This means that any OP_RECURSE items within it that refer to the group itself or any contained groups have to have their offsets adjusted. That is the job of this function. Before it is called, the partially compiled regex must be temporarily terminated with OP_END. Arguments: group points to the start of the group adjust the amount by which the group is to be moved utf8 TRUE in UTF-8 mode cd contains pointers to tables etc. Returns: nothing */ static void adjust_recurse(uschar *group, int adjust, BOOL utf8, compile_data *cd) { uschar *ptr = group; while ((ptr = (uschar *)find_recurse(ptr, utf8)) != NULL) { int offset = GET(ptr, 1); if (cd->start_code + offset >= group) PUT(ptr, 1, offset + adjust); ptr += 1 + LINK_SIZE; } } /************************************************* * Insert an automatic callout point * *************************************************/ /* This function is called when the PCRE_AUTO_CALLOUT option is set, to insert callout points before each pattern item. Arguments: code current code pointer ptr current pattern pointer cd pointers to tables etc Returns: new code pointer */ static uschar * auto_callout(uschar *code, const uschar *ptr, compile_data *cd) { *code++ = OP_CALLOUT; *code++ = 255; PUT(code, 0, ptr - cd->start_pattern); /* Pattern offset */ PUT(code, LINK_SIZE, 0); /* Default length */ return code + 2*LINK_SIZE; } /************************************************* * Complete a callout item * *************************************************/ /* A callout item contains the length of the next item in the pattern, which we can't fill in till after we have reached the relevant point. This is used for both automatic and manual callouts. Arguments: previous_callout points to previous callout item ptr current pattern pointer cd pointers to tables etc Returns: nothing */ static void complete_callout(uschar *previous_callout, const uschar *ptr, compile_data *cd) { int length = ptr - cd->start_pattern - GET(previous_callout, 2); PUT(previous_callout, 2 + LINK_SIZE, length); } #ifdef SUPPORT_UCP /************************************************* * Get othercase range * *************************************************/ /* This function is passed the start and end of a class range, in UTF-8 mode with UCP support. It searches up the characters, looking for internal ranges of characters in the "other" case. Each call returns the next one, updating the start address. Arguments: cptr points to starting character value; updated d end value ocptr where to put start of othercase range odptr where to put end of othercase range Yield: TRUE when range returned; FALSE when no more */ static BOOL get_othercase_range(int *cptr, int d, int *ocptr, int *odptr) { int c, chartype, othercase, next; for (c = *cptr; c <= d; c++) { if (ucp_findchar(c, &chartype, &othercase) == ucp_L && othercase != 0) break; } if (c > d) return FALSE; *ocptr = othercase; next = othercase + 1; for (++c; c <= d; c++) { if (ucp_findchar(c, &chartype, &othercase) != ucp_L || othercase != next) break; next++; } *odptr = next - 1; *cptr = c; return TRUE; } #endif /* SUPPORT_UCP */ /************************************************* * Compile one branch * *************************************************/ /* Scan the pattern, compiling it into the code vector. If the options are changed during the branch, the pointer is used to change the external options bits. Arguments: optionsptr pointer to the option bits brackets points to number of extracting brackets used codeptr points to the pointer to the current code point ptrptr points to the current pattern pointer errorptr points to pointer to error message firstbyteptr set to initial literal character, or < 0 (REQ_UNSET, REQ_NONE) reqbyteptr set to the last literal character required, else < 0 bcptr points to current branch chain cd contains pointers to tables etc. Returns: TRUE on success FALSE, with *errorptr set on error */ static BOOL compile_branch(int *optionsptr, int *brackets, uschar **codeptr, const uschar **ptrptr, const char **errorptr, int *firstbyteptr, int *reqbyteptr, branch_chain *bcptr, compile_data *cd) { int repeat_type, op_type; int repeat_min = 0, repeat_max = 0; /* To please picky compilers */ int bravalue = 0; int greedy_default, greedy_non_default; int firstbyte, reqbyte; int zeroreqbyte, zerofirstbyte; int req_caseopt, reqvary, tempreqvary; int condcount = 0; int options = *optionsptr; int after_manual_callout = 0; register int c = 0; register uschar *code = *codeptr; uschar *tempcode; BOOL inescq = FALSE; BOOL groupsetfirstbyte = FALSE; const uschar *ptr = *ptrptr; const uschar *tempptr; uschar *previous = NULL; uschar *previous_callout = NULL; uschar classbits[32]; #ifdef SUPPORT_UTF8 BOOL class_utf8; BOOL utf8 = (options & PCRE_UTF8) != 0; uschar *class_utf8data; uschar utf8_char[6]; #else BOOL utf8 = FALSE; #endif /* Set up the default and non-default settings for greediness */ greedy_default = ((options & PCRE_UNGREEDY) != 0); greedy_non_default = greedy_default ^ 1; /* Initialize no first byte, no required byte. REQ_UNSET means "no char matching encountered yet". It gets changed to REQ_NONE if we hit something that matches a non-fixed char first char; reqbyte just remains unset if we never find one. When we hit a repeat whose minimum is zero, we may have to adjust these values to take the zero repeat into account. This is implemented by setting them to zerofirstbyte and zeroreqbyte when such a repeat is encountered. The individual item types that can be repeated set these backoff variables appropriately. */ firstbyte = reqbyte = zerofirstbyte = zeroreqbyte = REQ_UNSET; /* The variable req_caseopt contains either the REQ_CASELESS value or zero, according to the current setting of the caseless flag. REQ_CASELESS is a bit value > 255. It is added into the firstbyte or reqbyte variables to record the case status of the value. This is used only for ASCII characters. */ req_caseopt = ((options & PCRE_CASELESS) != 0)? REQ_CASELESS : 0; /* Switch on next character until the end of the branch */ for (;; ptr++) { BOOL negate_class; BOOL possessive_quantifier; BOOL is_quantifier = FALSE; int class_charcount; int class_lastchar; int newoptions; int recno; int skipbytes; int subreqbyte; int subfirstbyte; int mclength; uschar mcbuffer[8]; /* Next byte in the pattern */ c = *ptr; /* If in \Q...\E, check for the end; if not, we have a literal */ if (inescq && c != 0) { if (c == '\\' && ptr[1] == 'E') { inescq = FALSE; ptr++; continue; } else { if (previous_callout != NULL) { complete_callout(previous_callout, ptr, cd); previous_callout = NULL; } if ((options & PCRE_AUTO_CALLOUT) != 0) { previous_callout = code; code = auto_callout(code, ptr, cd); } goto NORMAL_CHAR; } } /* Fill in length of a previous callout, except when the next thing is a quantifier. */ is_quantifier = c == '*' || c == '+' || c == '?' || (c == '{' && is_counted_repeat(ptr+1)); if (!is_quantifier && previous_callout != NULL && after_manual_callout-- <= 0) { complete_callout(previous_callout, ptr, cd); previous_callout = NULL; } /* In extended mode, skip white space and comments */ if ((options & PCRE_EXTENDED) != 0) { if ((cd->ctypes[c] & ctype_space) != 0) continue; if (c == '#') { /* The space before the ; is to avoid a warning on a silly compiler on the Macintosh. */ while ((c = *(++ptr)) != 0 && c != NEWLINE) ; if (c != 0) continue; /* Else fall through to handle end of string */ } } /* No auto callout for quantifiers. */ if ((options & PCRE_AUTO_CALLOUT) != 0 && !is_quantifier) { previous_callout = code; code = auto_callout(code, ptr, cd); } switch(c) { /* The branch terminates at end of string, |, or ). */ case 0: case '|': case ')': *firstbyteptr = firstbyte; *reqbyteptr = reqbyte; *codeptr = code; *ptrptr = ptr; return TRUE; /* Handle single-character metacharacters. In multiline mode, ^ disables the setting of any following char as a first character. */ case '^': if ((options & PCRE_MULTILINE) != 0) { if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE; } previous = NULL; *code++ = OP_CIRC; break; case '$': previous = NULL; *code++ = OP_DOLL; break; /* There can never be a first char if '.' is first, whatever happens about repeats. The value of reqbyte doesn't change either. */ case '.': if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE; zerofirstbyte = firstbyte; zeroreqbyte = reqbyte; previous = code; *code++ = OP_ANY; break; /* Character classes. If the included characters are all < 255 in value, we build a 32-byte bitmap of the permitted characters, except in the special case where there is only one such character. For negated classes, we build the map as usual, then invert it at the end. However, we use a different opcode so that data characters > 255 can be handled correctly. If the class contains characters outside the 0-255 range, a different opcode is compiled. It may optionally have a bit map for characters < 256, but those above are are explicitly listed afterwards. A flag byte tells whether the bitmap is present, and whether this is a negated class or not. */ case '[': previous = code; /* PCRE supports POSIX class stuff inside a class. Perl gives an error if they are encountered at the top level, so we'll do that too. */ if ((ptr[1] == ':' || ptr[1] == '.' || ptr[1] == '=') && check_posix_syntax(ptr, &tempptr, cd)) { *errorptr = (ptr[1] == ':')? ERR13 : ERR31; goto FAILED; } /* If the first character is '^', set the negation flag and skip it. */ if ((c = *(++ptr)) == '^') { negate_class = TRUE; c = *(++ptr); } else { negate_class = FALSE; } /* Keep a count of chars with values < 256 so that we can optimize the case of just a single character (as long as it's < 256). For higher valued UTF-8 characters, we don't yet do any optimization. */ class_charcount = 0; class_lastchar = -1; #ifdef SUPPORT_UTF8 class_utf8 = FALSE; /* No chars >= 256 */ class_utf8data = code + LINK_SIZE + 34; /* For UTF-8 items */ #endif /* Initialize the 32-char bit map to all zeros. We have to build the map in a temporary bit of store, in case the class contains only 1 character (< 256), because in that case the compiled code doesn't use the bit map. */ memset(classbits, 0, 32 * sizeof(uschar)); /* Process characters until ] is reached. By writing this as a "do" it means that an initial ] is taken as a data character. The first pass through the regex checked the overall syntax, so we don't need to be very strict here. At the start of the loop, c contains the first byte of the character. */ do { #ifdef SUPPORT_UTF8 if (utf8 && c > 127) { /* Braces are required because the */ GETCHARLEN(c, ptr, ptr); /* macro generates multiple statements */ } #endif /* Inside \Q...\E everything is literal except \E */ if (inescq) { if (c == '\\' && ptr[1] == 'E') { inescq = FALSE; ptr++; continue; } else goto LONE_SINGLE_CHARACTER; } /* Handle POSIX class names. Perl allows a negation extension of the form [:^name:]. A square bracket that doesn't match the syntax is treated as a literal. We also recognize the POSIX constructions [.ch.] and [=ch=] ("collating elements") and fault them, as Perl 5.6 and 5.8 do. */ if (c == '[' && (ptr[1] == ':' || ptr[1] == '.' || ptr[1] == '=') && check_posix_syntax(ptr, &tempptr, cd)) { BOOL local_negate = FALSE; int posix_class, i; register const uschar *cbits = cd->cbits; if (ptr[1] != ':') { *errorptr = ERR31; goto FAILED; } ptr += 2; if (*ptr == '^') { local_negate = TRUE; ptr++; } posix_class = check_posix_name(ptr, tempptr - ptr); if (posix_class < 0) { *errorptr = ERR30; goto FAILED; } /* If matching is caseless, upper and lower are converted to alpha. This relies on the fact that the class table starts with alpha, lower, upper as the first 3 entries. */ if ((options & PCRE_CASELESS) != 0 && posix_class <= 2) posix_class = 0; /* Or into the map we are building up to 3 of the static class tables, or their negations. The [:blank:] class sets up the same chars as the [:space:] class (all white space). We remove the vertical white space chars afterwards. */ posix_class *= 3; for (i = 0; i < 3; i++) { BOOL blankclass = strncmp((char *)ptr, "blank", 5) == 0; int taboffset = posix_class_maps[posix_class + i]; if (taboffset < 0) break; if (local_negate) { if (i == 0) for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+taboffset]; else for (c = 0; c < 32; c++) classbits[c] &= ~cbits[c+taboffset]; if (blankclass) classbits[1] |= 0x3c; } else { for (c = 0; c < 32; c++) classbits[c] |= cbits[c+taboffset]; if (blankclass) classbits[1] &= ~0x3c; } } ptr = tempptr + 1; class_charcount = 10; /* Set > 1; assumes more than 1 per class */ continue; /* End of POSIX syntax handling */ } /* Backslash may introduce a single character, or it may introduce one of the specials, which just set a flag. Escaped items are checked for validity in the pre-compiling pass. The sequence \b is a special case. Inside a class (and only there) it is treated as backspace. Elsewhere it marks a word boundary. Other escapes have preset maps ready to or into the one we are building. We assume they have more than one character in them, so set class_charcount bigger than one. */ if (c == '\\') { c = check_escape(&ptr, errorptr, *brackets, options, TRUE); if (-c == ESC_b) c = '\b'; /* \b is backslash in a class */ else if (-c == ESC_X) c = 'X'; /* \X is literal X in a class */ else if (-c == ESC_Q) /* Handle start of quoted string */ { if (ptr[1] == '\\' && ptr[2] == 'E') { ptr += 2; /* avoid empty string */ } else inescq = TRUE; continue; } if (c < 0) { register const uschar *cbits = cd->cbits; class_charcount += 2; /* Greater than 1 is what matters */ switch (-c) { case ESC_d: for (c = 0; c < 32; c++) classbits[c] |= cbits[c+cbit_digit]; continue; case ESC_D: for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+cbit_digit]; continue; case ESC_w: for (c = 0; c < 32; c++) classbits[c] |= cbits[c+cbit_word]; continue; case ESC_W: for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+cbit_word]; continue; case ESC_s: for (c = 0; c < 32; c++) classbits[c] |= cbits[c+cbit_space]; classbits[1] &= ~0x08; /* Perl 5.004 onwards omits VT from \s */ continue; case ESC_S: for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+cbit_space]; classbits[1] |= 0x08; /* Perl 5.004 onwards omits VT from \s */ continue; #ifdef SUPPORT_UCP case ESC_p: case ESC_P: { BOOL negated; int property = get_ucp(&ptr, &negated, errorptr); if (property < 0) goto FAILED; class_utf8 = TRUE; *class_utf8data++ = ((-c == ESC_p) != negated)? XCL_PROP : XCL_NOTPROP; *class_utf8data++ = property; class_charcount -= 2; /* Not a < 256 character */ } continue; #endif /* Unrecognized escapes are faulted if PCRE is running in its strict mode. By default, for compatibility with Perl, they are treated as literals. */ default: if ((options & PCRE_EXTRA) != 0) { *errorptr = ERR7; goto FAILED; } c = *ptr; /* The final character */ class_charcount -= 2; /* Undo the default count from above */ } } /* Fall through if we have a single character (c >= 0). This may be > 256 in UTF-8 mode. */ } /* End of backslash handling */ /* A single character may be followed by '-' to form a range. However, Perl does not permit ']' to be the end of the range. A '-' character here is treated as a literal. */ if (ptr[1] == '-' && ptr[2] != ']') { int d; ptr += 2; #ifdef SUPPORT_UTF8 if (utf8) { /* Braces are required because the */ GETCHARLEN(d, ptr, ptr); /* macro generates multiple statements */ } else #endif d = *ptr; /* Not UTF-8 mode */ /* The second part of a range can be a single-character escape, but not any of the other escapes. Perl 5.6 treats a hyphen as a literal in such circumstances. */ if (d == '\\') { const uschar *oldptr = ptr; d = check_escape(&ptr, errorptr, *brackets, options, TRUE); /* \b is backslash; \X is literal X; any other special means the '-' was literal */ if (d < 0) { if (d == -ESC_b) d = '\b'; else if (d == -ESC_X) d = 'X'; else { ptr = oldptr - 2; goto LONE_SINGLE_CHARACTER; /* A few lines below */ } } } /* The check that the two values are in the correct order happens in the pre-pass. Optimize one-character ranges */ if (d == c) goto LONE_SINGLE_CHARACTER; /* A few lines below */ /* In UTF-8 mode, if the upper limit is > 255, or > 127 for caseless matching, we have to use an XCLASS with extra data items. Caseless matching for characters > 127 is available only if UCP support is available. */ #ifdef SUPPORT_UTF8 if (utf8 && (d > 255 || ((options & PCRE_CASELESS) != 0 && d > 127))) { class_utf8 = TRUE; /* With UCP support, we can find the other case equivalents of the relevant characters. There may be several ranges. Optimize how they fit with the basic range. */ #ifdef SUPPORT_UCP if ((options & PCRE_CASELESS) != 0) { int occ, ocd; int cc = c; int origd = d; while (get_othercase_range(&cc, origd, &occ, &ocd)) { if (occ >= c && ocd <= d) continue; /* Skip embedded ranges */ if (occ < c && ocd >= c - 1) /* Extend the basic range */ { /* if there is overlap, */ c = occ; /* noting that if occ < c */ continue; /* we can't have ocd > d */ } /* because a subrange is */ if (ocd > d && occ <= d + 1) /* always shorter than */ { /* the basic range. */ d = ocd; continue; } if (occ == ocd) { *class_utf8data++ = XCL_SINGLE; } else { *class_utf8data++ = XCL_RANGE; class_utf8data += ord2utf8(occ, class_utf8data); } class_utf8data += ord2utf8(ocd, class_utf8data); } } #endif /* SUPPORT_UCP */ /* Now record the original range, possibly modified for UCP caseless overlapping ranges. */ *class_utf8data++ = XCL_RANGE; class_utf8data += ord2utf8(c, class_utf8data); class_utf8data += ord2utf8(d, class_utf8data); /* With UCP support, we are done. Without UCP support, there is no caseless matching for UTF-8 characters > 127; we can use the bit map for the smaller ones. */ #ifdef SUPPORT_UCP continue; /* With next character in the class */ #else if ((options & PCRE_CASELESS) == 0 || c > 127) continue; /* Adjust upper limit and fall through to set up the map */ d = 127; #endif /* SUPPORT_UCP */ } #endif /* SUPPORT_UTF8 */ /* We use the bit map for all cases when not in UTF-8 mode; else ranges that lie entirely within 0-127 when there is UCP support; else for partial ranges without UCP support. */ for (; c <= d; c++) { classbits[c/8] |= (1 << (c&7)); if ((options & PCRE_CASELESS) != 0) { int uc = cd->fcc[c]; /* flip case */ classbits[uc/8] |= (1 << (uc&7)); } class_charcount++; /* in case a one-char range */ class_lastchar = c; } continue; /* Go get the next char in the class */ } /* Handle a lone single character - we can get here for a normal non-escape char, or after \ that introduces a single character or for an apparent range that isn't. */ LONE_SINGLE_CHARACTER: /* Handle a character that cannot go in the bit map */ #ifdef SUPPORT_UTF8 if (utf8 && (c > 255 || ((options & PCRE_CASELESS) != 0 && c > 127))) { class_utf8 = TRUE; *class_utf8data++ = XCL_SINGLE; class_utf8data += ord2utf8(c, class_utf8data); #ifdef SUPPORT_UCP if ((options & PCRE_CASELESS) != 0) { int chartype; int othercase; if (ucp_findchar(c, &chartype, &othercase) >= 0 && othercase > 0) { *class_utf8data++ = XCL_SINGLE; class_utf8data += ord2utf8(othercase, class_utf8data); } } #endif /* SUPPORT_UCP */ } else #endif /* SUPPORT_UTF8 */ /* Handle a single-byte character */ { classbits[c/8] |= (1 << (c&7)); if ((options & PCRE_CASELESS) != 0) { c = cd->fcc[c]; /* flip case */ classbits[c/8] |= (1 << (c&7)); } class_charcount++; class_lastchar = c; } } /* Loop until ']' reached; the check for end of string happens inside the loop. This "while" is the end of the "do" above. */ while ((c = *(++ptr)) != ']' || inescq); /* If class_charcount is 1, we saw precisely one character whose value is less than 256. In non-UTF-8 mode we can always optimize. In UTF-8 mode, we can optimize the negative case only if there were no characters >= 128 because OP_NOT and the related opcodes like OP_NOTSTAR operate on single-bytes only. This is an historical hangover. Maybe one day we can tidy these opcodes to handle multi-byte characters. The optimization throws away the bit map. We turn the item into a 1-character OP_CHAR[NC] if it's positive, or OP_NOT if it's negative. Note that OP_NOT does not support multibyte characters. In the positive case, it can cause firstbyte to be set. Otherwise, there can be no first char if this item is first, whatever repeat count may follow. In the case of reqbyte, save the previous value for reinstating. */ #ifdef SUPPORT_UTF8 if (class_charcount == 1 && (!utf8 || (!class_utf8 && (!negate_class || class_lastchar < 128)))) #else if (class_charcount == 1) #endif { zeroreqbyte = reqbyte; /* The OP_NOT opcode works on one-byte characters only. */ if (negate_class) { if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE; zerofirstbyte = firstbyte; *code++ = OP_NOT; *code++ = class_lastchar; break; } /* For a single, positive character, get the value into mcbuffer, and then we can handle this with the normal one-character code. */ #ifdef SUPPORT_UTF8 if (utf8 && class_lastchar > 127) mclength = ord2utf8(class_lastchar, mcbuffer); else #endif { mcbuffer[0] = class_lastchar; mclength = 1; } goto ONE_CHAR; } /* End of 1-char optimization */ /* The general case - not the one-char optimization. If this is the first thing in the branch, there can be no first char setting, whatever the repeat count. Any reqbyte setting must remain unchanged after any kind of repeat. */ if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE; zerofirstbyte = firstbyte; zeroreqbyte = reqbyte; /* If there are characters with values > 255, we have to compile an extended class, with its own opcode. If there are no characters < 256, we can omit the bitmap. */ #ifdef SUPPORT_UTF8 if (class_utf8) { *class_utf8data++ = XCL_END; /* Marks the end of extra data */ *code++ = OP_XCLASS; code += LINK_SIZE; *code = negate_class? XCL_NOT : 0; /* If the map is required, install it, and move on to the end of the extra data */ if (class_charcount > 0) { *code++ |= XCL_MAP; memcpy(code, classbits, 32); code = class_utf8data; } /* If the map is not required, slide down the extra data. */ else { int len = class_utf8data - (code + 33); memmove(code + 1, code + 33, len); code += len + 1; } /* Now fill in the complete length of the item */ PUT(previous, 1, code - previous); break; /* End of class handling */ } #endif /* If there are no characters > 255, negate the 32-byte map if necessary, and copy it into the code vector. If this is the first thing in the branch, there can be no first char setting, whatever the repeat count. Any reqbyte setting must remain unchanged after any kind of repeat. */ if (negate_class) { *code++ = OP_NCLASS; for (c = 0; c < 32; c++) code[c] = ~classbits[c]; } else { *code++ = OP_CLASS; memcpy(code, classbits, 32); } code += 32; break; /* Various kinds of repeat; '{' is not necessarily a quantifier, but this has been tested above. */ case '{': if (!is_quantifier) goto NORMAL_CHAR; ptr = read_repeat_counts(ptr+1, &repeat_min, &repeat_max, errorptr); if (*errorptr != NULL) goto FAILED; goto REPEAT; case '*': repeat_min = 0; repeat_max = -1; goto REPEAT; case '+': repeat_min = 1; repeat_max = -1; goto REPEAT; case '?': repeat_min = 0; repeat_max = 1; REPEAT: if (previous == NULL) { *errorptr = ERR9; goto FAILED; } if (repeat_min == 0) { firstbyte = zerofirstbyte; /* Adjust for zero repeat */ reqbyte = zeroreqbyte; /* Ditto */ } /* Remember whether this is a variable length repeat */ reqvary = (repeat_min == repeat_max)? 0 : REQ_VARY; op_type = 0; /* Default single-char op codes */ possessive_quantifier = FALSE; /* Default not possessive quantifier */ /* Save start of previous item, in case we have to move it up to make space for an inserted OP_ONCE for the additional '+' extension. */ tempcode = previous; /* If the next character is '+', we have a possessive quantifier. This implies greediness, whatever the setting of the PCRE_UNGREEDY option. If the next character is '?' this is a minimizing repeat, by default, but if PCRE_UNGREEDY is set, it works the other way round. We change the repeat type to the non-default. */ if (ptr[1] == '+') { repeat_type = 0; /* Force greedy */ possessive_quantifier = TRUE; ptr++; } else if (ptr[1] == '?') { repeat_type = greedy_non_default; ptr++; } else repeat_type = greedy_default; /* If previous was a recursion, we need to wrap it inside brackets so that it can be replicated if necessary. */ if (*previous == OP_RECURSE) { memmove(previous + 1 + LINK_SIZE, previous, 1 + LINK_SIZE); code += 1 + LINK_SIZE; *previous = OP_BRA; PUT(previous, 1, code - previous); *code = OP_KET; PUT(code, 1, code - previous); code += 1 + LINK_SIZE; } /* If previous was a character match, abolish the item and generate a repeat item instead. If a char item has a minumum of more than one, ensure that it is set in reqbyte - it might not be if a sequence such as x{3} is the first thing in a branch because the x will have gone into firstbyte instead. */ if (*previous == OP_CHAR || *previous == OP_CHARNC) { /* Deal with UTF-8 characters that take up more than one byte. It's easier to write this out separately than try to macrify it. Use c to hold the length of the character in bytes, plus 0x80 to flag that it's a length rather than a small character. */ #ifdef SUPPORT_UTF8 if (utf8 && (code[-1] & 0x80) != 0) { uschar *lastchar = code - 1; while((*lastchar & 0xc0) == 0x80) lastchar--; c = code - lastchar; /* Length of UTF-8 character */ memcpy(utf8_char, lastchar, c); /* Save the char */ c |= 0x80; /* Flag c as a length */ } else #endif /* Handle the case of a single byte - either with no UTF8 support, or with UTF-8 disabled, or for a UTF-8 character < 128. */ { c = code[-1]; if (repeat_min > 1) reqbyte = c | req_caseopt | cd->req_varyopt; } goto OUTPUT_SINGLE_REPEAT; /* Code shared with single character types */ } /* If previous was a single negated character ([^a] or similar), we use one of the special opcodes, replacing it. The code is shared with single- character repeats by setting opt_type to add a suitable offset into repeat_type. OP_NOT is currently used only for single-byte chars. */ else if (*previous == OP_NOT) { op_type = OP_NOTSTAR - OP_STAR; /* Use "not" opcodes */ c = previous[1]; goto OUTPUT_SINGLE_REPEAT; } /* If previous was a character type match (\d or similar), abolish it and create a suitable repeat item. The code is shared with single-character repeats by setting op_type to add a suitable offset into repeat_type. Note the the Unicode property types will be present only when SUPPORT_UCP is defined, but we don't wrap the little bits of code here because it just makes it horribly messy. */ else if (*previous < OP_EODN) { uschar *oldcode; int prop_type; op_type = OP_TYPESTAR - OP_STAR; /* Use type opcodes */ c = *previous; OUTPUT_SINGLE_REPEAT: prop_type = (*previous == OP_PROP || *previous == OP_NOTPROP)? previous[1] : -1; oldcode = code; code = previous; /* Usually overwrite previous item */ /* If the maximum is zero then the minimum must also be zero; Perl allows this case, so we do too - by simply omitting the item altogether. */ if (repeat_max == 0) goto END_REPEAT; /* All real repeats make it impossible to handle partial matching (maybe one day we will be able to remove this restriction). */ if (repeat_max != 1) cd->nopartial = TRUE; /* Combine the op_type with the repeat_type */ repeat_type += op_type; /* A minimum of zero is handled either as the special case * or ?, or as an UPTO, with the maximum given. */ if (repeat_min == 0) { if (repeat_max == -1) *code++ = OP_STAR + repeat_type; else if (repeat_max == 1) *code++ = OP_QUERY + repeat_type; else { *code++ = OP_UPTO + repeat_type; PUT2INC(code, 0, repeat_max); } } /* A repeat minimum of 1 is optimized into some special cases. If the maximum is unlimited, we use OP_PLUS. Otherwise, the original item it left in place and, if the maximum is greater than 1, we use OP_UPTO with one less than the maximum. */ else if (repeat_min == 1) { if (repeat_max == -1) *code++ = OP_PLUS + repeat_type; else { code = oldcode; /* leave previous item in place */ if (repeat_max == 1) goto END_REPEAT; *code++ = OP_UPTO + repeat_type; PUT2INC(code, 0, repeat_max - 1); } } /* The case {n,n} is just an EXACT, while the general case {n,m} is handled as an EXACT followed by an UPTO. */ else { *code++ = OP_EXACT + op_type; /* NB EXACT doesn't have repeat_type */ PUT2INC(code, 0, repeat_min); /* If the maximum is unlimited, insert an OP_STAR. Before doing so, we have to insert the character for the previous code. For a repeated Unicode property match, there is an extra byte that defines the required property. In UTF-8 mode, long characters have their length in c, with the 0x80 bit as a flag. */ if (repeat_max < 0) { #ifdef SUPPORT_UTF8 if (utf8 && c >= 128) { memcpy(code, utf8_char, c & 7); code += c & 7; } else #endif { *code++ = c; if (prop_type >= 0) *code++ = prop_type; } *code++ = OP_STAR + repeat_type; } /* Else insert an UPTO if the max is greater than the min, again preceded by the character, for the previously inserted code. */ else if (repeat_max != repeat_min) { #ifdef SUPPORT_UTF8 if (utf8 && c >= 128) { memcpy(code, utf8_char, c & 7); code += c & 7; } else #endif *code++ = c; if (prop_type >= 0) *code++ = prop_type; repeat_max -= repeat_min; *code++ = OP_UPTO + repeat_type; PUT2INC(code, 0, repeat_max); } } /* The character or character type itself comes last in all cases. */ #ifdef SUPPORT_UTF8 if (utf8 && c >= 128) { memcpy(code, utf8_char, c & 7); code += c & 7; } else #endif *code++ = c; /* For a repeated Unicode property match, there is an extra byte that defines the required property. */ #ifdef SUPPORT_UCP if (prop_type >= 0) *code++ = prop_type; #endif } /* If previous was a character class or a back reference, we put the repeat stuff after it, but just skip the item if the repeat was {0,0}. */ else if (*previous == OP_CLASS || *previous == OP_NCLASS || #ifdef SUPPORT_UTF8 *previous == OP_XCLASS || #endif *previous == OP_REF) { if (repeat_max == 0) { code = previous; goto END_REPEAT; } /* All real repeats make it impossible to handle partial matching (maybe one day we will be able to remove this restriction). */ if (repeat_max != 1) cd->nopartial = TRUE; if (repeat_min == 0 && repeat_max == -1) *code++ = OP_CRSTAR + repeat_type; else if (repeat_min == 1 && repeat_max == -1) *code++ = OP_CRPLUS + repeat_type; else if (repeat_min == 0 && repeat_max == 1) *code++ = OP_CRQUERY + repeat_type; else { *code++ = OP_CRRANGE + repeat_type; PUT2INC(code, 0, repeat_min); if (repeat_max == -1) repeat_max = 0; /* 2-byte encoding for max */ PUT2INC(code, 0, repeat_max); } } /* If previous was a bracket group, we may have to replicate it in certain cases. */ else if (*previous >= OP_BRA || *previous == OP_ONCE || *previous == OP_COND) { register int i; int ketoffset = 0; int len = code - previous; uschar *bralink = NULL; /* If the maximum repeat count is unlimited, find the end of the bracket by scanning through from the start, and compute the offset back to it from the current code pointer. There may be an OP_OPT setting following the final KET, so we can't find the end just by going back from the code pointer. */ if (repeat_max == -1) { register uschar *ket = previous; do ket += GET(ket, 1); while (*ket != OP_KET); ketoffset = code - ket; } /* The case of a zero minimum is special because of the need to stick OP_BRAZERO in front of it, and because the group appears once in the data, whereas in other cases it appears the minimum number of times. For this reason, it is simplest to treat this case separately, as otherwise the code gets far too messy. There are several special subcases when the minimum is zero. */ if (repeat_min == 0) { /* If the maximum is also zero, we just omit the group from the output altogether. */ if (repeat_max == 0) { code = previous; goto END_REPEAT; } /* If the maximum is 1 or unlimited, we just have to stick in the BRAZERO and do no more at this point. However, we do need to adjust any OP_RECURSE calls inside the group that refer to the group itself or any internal group, because the offset is from the start of the whole regex. Temporarily terminate the pattern while doing this. */ if (repeat_max <= 1) { *code = OP_END; adjust_recurse(previous, 1, utf8, cd); memmove(previous+1, previous, len); code++; *previous++ = OP_BRAZERO + repeat_type; } /* If the maximum is greater than 1 and limited, we have to replicate in a nested fashion, sticking OP_BRAZERO before each set of brackets. The first one has to be handled carefully because it's the original copy, which has to be moved up. The remainder can be handled by code that is common with the non-zero minimum case below. We have to adjust the value or repeat_max, since one less copy is required. Once again, we may have to adjust any OP_RECURSE calls inside the group. */ else { int offset; *code = OP_END; adjust_recurse(previous, 2 + LINK_SIZE, utf8, cd); memmove(previous + 2 + LINK_SIZE, previous, len); code += 2 + LINK_SIZE; *previous++ = OP_BRAZERO + repeat_type; *previous++ = OP_BRA; /* We chain together the bracket offset fields that have to be filled in later when the ends of the brackets are reached. */ offset = (bralink == NULL)? 0 : previous - bralink; bralink = previous; PUTINC(previous, 0, offset); } repeat_max--; } /* If the minimum is greater than zero, replicate the group as many times as necessary, and adjust the maximum to the number of subsequent copies that we need. If we set a first char from the group, and didn't set a required char, copy the latter from the former. */ else { if (repeat_min > 1) { if (groupsetfirstbyte && reqbyte < 0) reqbyte = firstbyte; for (i = 1; i < repeat_min; i++) { memcpy(code, previous, len); code += len; } } if (repeat_max > 0) repeat_max -= repeat_min; } /* This code is common to both the zero and non-zero minimum cases. If the maximum is limited, it replicates the group in a nested fashion, remembering the bracket starts on a stack. In the case of a zero minimum, the first one was set up above. In all cases the repeat_max now specifies the number of additional copies needed. */ if (repeat_max >= 0) { for (i = repeat_max - 1; i >= 0; i--) { *code++ = OP_BRAZERO + repeat_type; /* All but the final copy start a new nesting, maintaining the chain of brackets outstanding. */ if (i != 0) { int offset; *code++ = OP_BRA; offset = (bralink == NULL)? 0 : code - bralink; bralink = code; PUTINC(code, 0, offset); } memcpy(code, previous, len); code += len; } /* Now chain through the pending brackets, and fill in their length fields (which are holding the chain links pro tem). */ while (bralink != NULL) { int oldlinkoffset; int offset = code - bralink + 1; uschar *bra = code - offset; oldlinkoffset = GET(bra, 1); bralink = (oldlinkoffset == 0)? NULL : bralink - oldlinkoffset; *code++ = OP_KET; PUTINC(code, 0, offset); PUT(bra, 1, offset); } } /* If the maximum is unlimited, set a repeater in the final copy. We can't just offset backwards from the current code point, because we don't know if there's been an options resetting after the ket. The correct offset was computed above. */ else code[-ketoffset] = OP_KETRMAX + repeat_type; } /* Else there's some kind of shambles */ else { *errorptr = ERR11; goto FAILED; } /* If the character following a repeat is '+', we wrap the entire repeated item inside OP_ONCE brackets. This is just syntactic sugar, taken from Sun's Java package. The repeated item starts at tempcode, not at previous, which might be the first part of a string whose (former) last char we repeated. However, we don't support '+' after a greediness '?'. */ if (possessive_quantifier) { int len = code - tempcode; memmove(tempcode + 1+LINK_SIZE, tempcode, len); code += 1 + LINK_SIZE; len += 1 + LINK_SIZE; tempcode[0] = OP_ONCE; *code++ = OP_KET; PUTINC(code, 0, len); PUT(tempcode, 1, len); } /* In all case we no longer have a previous item. We also set the "follows varying string" flag for subsequently encountered reqbytes if it isn't already set and we have just passed a varying length item. */ END_REPEAT: previous = NULL; cd->req_varyopt |= reqvary; break; /* Start of nested bracket sub-expression, or comment or lookahead or lookbehind or option setting or condition. First deal with special things that can come after a bracket; all are introduced by ?, and the appearance of any of them means that this is not a referencing group. They were checked for validity in the first pass over the string, so we don't have to check for syntax errors here. */ case '(': newoptions = options; skipbytes = 0; if (*(++ptr) == '?') { int set, unset; int *optset; switch (*(++ptr)) { case '#': /* Comment; skip to ket */ ptr++; while (*ptr != ')') ptr++; continue; case ':': /* Non-extracting bracket */ bravalue = OP_BRA; ptr++; break; case '(': bravalue = OP_COND; /* Conditional group */ /* Condition to test for recursion */ if (ptr[1] == 'R') { code[1+LINK_SIZE] = OP_CREF; PUT2(code, 2+LINK_SIZE, CREF_RECURSE); skipbytes = 3; ptr += 3; } /* Condition to test for a numbered subpattern match. We know that if a digit follows ( then there will just be digits until ) because the syntax was checked in the first pass. */ else if ((digitab[ptr[1]] && ctype_digit) != 0) { int condref; /* Don't amalgamate; some compilers */ condref = *(++ptr) - '0'; /* grumble at autoincrement in declaration */ while (*(++ptr) != ')') condref = condref*10 + *ptr - '0'; if (condref == 0) { *errorptr = ERR35; goto FAILED; } ptr++; code[1+LINK_SIZE] = OP_CREF; PUT2(code, 2+LINK_SIZE, condref); skipbytes = 3; } /* For conditions that are assertions, we just fall through, having set bravalue above. */ break; case '=': /* Positive lookahead */ bravalue = OP_ASSERT; ptr++; break; case '!': /* Negative lookahead */ bravalue = OP_ASSERT_NOT; ptr++; break; case '<': /* Lookbehinds */ switch (*(++ptr)) { case '=': /* Positive lookbehind */ bravalue = OP_ASSERTBACK; ptr++; break; case '!': /* Negative lookbehind */ bravalue = OP_ASSERTBACK_NOT; ptr++; break; } break; case '>': /* One-time brackets */ bravalue = OP_ONCE; ptr++; break; case 'C': /* Callout - may be followed by digits; */ previous_callout = code; /* Save for later completion */ after_manual_callout = 1; /* Skip one item before completing */ *code++ = OP_CALLOUT; /* Already checked that the terminating */ { /* closing parenthesis is present. */ int n = 0; while ((digitab[*(++ptr)] & ctype_digit) != 0) n = n * 10 + *ptr - '0'; if (n > 255) { *errorptr = ERR38; goto FAILED; } *code++ = n; PUT(code, 0, ptr - cd->start_pattern + 1); /* Pattern offset */ PUT(code, LINK_SIZE, 0); /* Default length */ code += 2 * LINK_SIZE; } previous = NULL; continue; case 'P': /* Named subpattern handling */ if (*(++ptr) == '<') /* Definition */ { int i, namelen; uschar *slot = cd->name_table; const uschar *name; /* Don't amalgamate; some compilers */ name = ++ptr; /* grumble at autoincrement in declaration */ while (*ptr++ != '>'); namelen = ptr - name - 1; for (i = 0; i < cd->names_found; i++) { int crc = memcmp(name, slot+2, namelen); if (crc == 0) { if (slot[2+namelen] == 0) { *errorptr = ERR43; goto FAILED; } crc = -1; /* Current name is substring */ } if (crc < 0) { memmove(slot + cd->name_entry_size, slot, (cd->names_found - i) * cd->name_entry_size); break; } slot += cd->name_entry_size; } PUT2(slot, 0, *brackets + 1); memcpy(slot + 2, name, namelen); slot[2+namelen] = 0; cd->names_found++; goto NUMBERED_GROUP; } if (*ptr == '=' || *ptr == '>') /* Reference or recursion */ { int i, namelen; int type = *ptr++; const uschar *name = ptr; uschar *slot = cd->name_table; while (*ptr != ')') ptr++; namelen = ptr - name; for (i = 0; i < cd->names_found; i++) { if (strncmp((char *)name, (char *)slot+2, namelen) == 0) break; slot += cd->name_entry_size; } if (i >= cd->names_found) { *errorptr = ERR15; goto FAILED; } recno = GET2(slot, 0); if (type == '>') goto HANDLE_RECURSION; /* A few lines below */ /* Back reference */ previous = code; *code++ = OP_REF; PUT2INC(code, 0, recno); cd->backref_map |= (recno < 32)? (1 << recno) : 1; if (recno > cd->top_backref) cd->top_backref = recno; continue; } /* Should never happen */ break; case 'R': /* Pattern recursion */ ptr++; /* Same as (?0) */ /* Fall through */ /* Recursion or "subroutine" call */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { const uschar *called; recno = 0; while((digitab[*ptr] & ctype_digit) != 0) recno = recno * 10 + *ptr++ - '0'; /* Come here from code above that handles a named recursion */ HANDLE_RECURSION: previous = code; /* Find the bracket that is being referenced. Temporarily end the regex in case it doesn't exist. */ *code = OP_END; called = (recno == 0)? cd->start_code : find_bracket(cd->start_code, utf8, recno); if (called == NULL) { *errorptr = ERR15; goto FAILED; } /* If the subpattern is still open, this is a recursive call. We check to see if this is a left recursion that could loop for ever, and diagnose that case. */ if (GET(called, 1) == 0 && could_be_empty(called, code, bcptr, utf8)) { *errorptr = ERR40; goto FAILED; } /* Insert the recursion/subroutine item */ *code = OP_RECURSE; PUT(code, 1, called - cd->start_code); code += 1 + LINK_SIZE; } continue; /* Character after (? not specially recognized */ default: /* Option setting */ set = unset = 0; optset = &set; while (*ptr != ')' && *ptr != ':') { switch (*ptr++) { case '-': optset = &unset; break; case 'i': *optset |= PCRE_CASELESS; break; case 'm': *optset |= PCRE_MULTILINE; break; case 's': *optset |= PCRE_DOTALL; break; case 'x': *optset |= PCRE_EXTENDED; break; case 'U': *optset |= PCRE_UNGREEDY; break; case 'X': *optset |= PCRE_EXTRA; break; } } /* Set up the changed option bits, but don't change anything yet. */ newoptions = (options | set) & (~unset); /* If the options ended with ')' this is not the start of a nested group with option changes, so the options change at this level. Compile code to change the ims options if this setting actually changes any of them. We also pass the new setting back so that it can be put at the start of any following branches, and when this group ends (if we are in a group), a resetting item can be compiled. Note that if this item is right at the start of the pattern, the options will have been abstracted and made global, so there will be no change to compile. */ if (*ptr == ')') { if ((options & PCRE_IMS) != (newoptions & PCRE_IMS)) { *code++ = OP_OPT; *code++ = newoptions & PCRE_IMS; } /* Change options at this level, and pass them back for use in subsequent branches. Reset the greedy defaults and the case value for firstbyte and reqbyte. */ *optionsptr = options = newoptions; greedy_default = ((newoptions & PCRE_UNGREEDY) != 0); greedy_non_default = greedy_default ^ 1; req_caseopt = ((options & PCRE_CASELESS) != 0)? REQ_CASELESS : 0; previous = NULL; /* This item can't be repeated */ continue; /* It is complete */ } /* If the options ended with ':' we are heading into a nested group with possible change of options. Such groups are non-capturing and are not assertions of any kind. All we need to do is skip over the ':'; the newoptions value is handled below. */ bravalue = OP_BRA; ptr++; } } /* If PCRE_NO_AUTO_CAPTURE is set, all unadorned brackets become non-capturing and behave like (?:...) brackets */ else if ((options & PCRE_NO_AUTO_CAPTURE) != 0) { bravalue = OP_BRA; } /* Else we have a referencing group; adjust the opcode. If the bracket number is greater than EXTRACT_BASIC_MAX, we set the opcode one higher, and arrange for the true number to follow later, in an OP_BRANUMBER item. */ else { NUMBERED_GROUP: if (++(*brackets) > EXTRACT_BASIC_MAX) { bravalue = OP_BRA + EXTRACT_BASIC_MAX + 1; code[1+LINK_SIZE] = OP_BRANUMBER; PUT2(code, 2+LINK_SIZE, *brackets); skipbytes = 3; } else bravalue = OP_BRA + *brackets; } /* Process nested bracketed re. Assertions may not be repeated, but other kinds can be. We copy code into a non-register variable in order to be able to pass its address because some compilers complain otherwise. Pass in a new setting for the ims options if they have changed. */ previous = (bravalue >= OP_ONCE)? code : NULL; *code = bravalue; tempcode = code; tempreqvary = cd->req_varyopt; /* Save value before bracket */ if (!compile_regex( newoptions, /* The complete new option state */ options & PCRE_IMS, /* The previous ims option state */ brackets, /* Extracting bracket count */ &tempcode, /* Where to put code (updated) */ &ptr, /* Input pointer (updated) */ errorptr, /* Where to put an error message */ (bravalue == OP_ASSERTBACK || bravalue == OP_ASSERTBACK_NOT), /* TRUE if back assert */ skipbytes, /* Skip over OP_COND/OP_BRANUMBER */ &subfirstbyte, /* For possible first char */ &subreqbyte, /* For possible last char */ bcptr, /* Current branch chain */ cd)) /* Tables block */ goto FAILED; /* At the end of compiling, code is still pointing to the start of the group, while tempcode has been updated to point past the end of the group and any option resetting that may follow it. The pattern pointer (ptr) is on the bracket. */ /* If this is a conditional bracket, check that there are no more than two branches in the group. */ else if (bravalue == OP_COND) { uschar *tc = code; condcount = 0; do { condcount++; tc += GET(tc,1); } while (*tc != OP_KET); if (condcount > 2) { *errorptr = ERR27; goto FAILED; } /* If there is just one branch, we must not make use of its firstbyte or reqbyte, because this is equivalent to an empty second branch. */ if (condcount == 1) subfirstbyte = subreqbyte = REQ_NONE; } /* Handle updating of the required and first characters. Update for normal brackets of all kinds, and conditions with two branches (see code above). If the bracket is followed by a quantifier with zero repeat, we have to back off. Hence the definition of zeroreqbyte and zerofirstbyte outside the main loop so that they can be accessed for the back off. */ zeroreqbyte = reqbyte; zerofirstbyte = firstbyte; groupsetfirstbyte = FALSE; if (bravalue >= OP_BRA || bravalue == OP_ONCE || bravalue == OP_COND) { /* If we have not yet set a firstbyte in this branch, take it from the subpattern, remembering that it was set here so that a repeat of more than one can replicate it as reqbyte if necessary. If the subpattern has no firstbyte, set "none" for the whole branch. In both cases, a zero repeat forces firstbyte to "none". */ if (firstbyte == REQ_UNSET) { if (subfirstbyte >= 0) { firstbyte = subfirstbyte; groupsetfirstbyte = TRUE; } else firstbyte = REQ_NONE; zerofirstbyte = REQ_NONE; } /* If firstbyte was previously set, convert the subpattern's firstbyte into reqbyte if there wasn't one, using the vary flag that was in existence beforehand. */ else if (subfirstbyte >= 0 && subreqbyte < 0) subreqbyte = subfirstbyte | tempreqvary; /* If the subpattern set a required byte (or set a first byte that isn't really the first byte - see above), set it. */ if (subreqbyte >= 0) reqbyte = subreqbyte; } /* For a forward assertion, we take the reqbyte, if set. This can be helpful if the pattern that follows the assertion doesn't set a different char. For example, it's useful for /(?=abcde).+/. We can't set firstbyte for an assertion, however because it leads to incorrect effect for patterns such as /(?=a)a.+/ when the "real" "a" would then become a reqbyte instead of a firstbyte. This is overcome by a scan at the end if there's no firstbyte, looking for an asserted first char. */ else if (bravalue == OP_ASSERT && subreqbyte >= 0) reqbyte = subreqbyte; /* Now update the main code pointer to the end of the group. */ code = tempcode; /* Error if hit end of pattern */ if (*ptr != ')') { *errorptr = ERR14; goto FAILED; } break; /* Check \ for being a real metacharacter; if not, fall through and handle it as a data character at the start of a string. Escape items are checked for validity in the pre-compiling pass. */ case '\\': tempptr = ptr; c = check_escape(&ptr, errorptr, *brackets, options, FALSE); /* Handle metacharacters introduced by \. For ones like \d, the ESC_ values are arranged to be the negation of the corresponding OP_values. For the back references, the values are ESC_REF plus the reference number. Only back references and those types that consume a character may be repeated. We can test for values between ESC_b and ESC_Z for the latter; this may have to change if any new ones are ever created. */ if (c < 0) { if (-c == ESC_Q) /* Handle start of quoted string */ { if (ptr[1] == '\\' && ptr[2] == 'E') ptr += 2; /* avoid empty string */ else inescq = TRUE; continue; } /* For metasequences that actually match a character, we disable the setting of a first character if it hasn't already been set. */ if (firstbyte == REQ_UNSET && -c > ESC_b && -c < ESC_Z) firstbyte = REQ_NONE; /* Set values to reset to if this is followed by a zero repeat. */ zerofirstbyte = firstbyte; zeroreqbyte = reqbyte; /* Back references are handled specially */ if (-c >= ESC_REF) { int number = -c - ESC_REF; previous = code; *code++ = OP_REF; PUT2INC(code, 0, number); } /* So are Unicode property matches, if supported. We know that get_ucp won't fail because it was tested in the pre-pass. */ #ifdef SUPPORT_UCP else if (-c == ESC_P || -c == ESC_p) { BOOL negated; int value = get_ucp(&ptr, &negated, errorptr); previous = code; *code++ = ((-c == ESC_p) != negated)? OP_PROP : OP_NOTPROP; *code++ = value; } #endif /* For the rest, we can obtain the OP value by negating the escape value */ else { previous = (-c > ESC_b && -c < ESC_Z)? code : NULL; *code++ = -c; } continue; } /* We have a data character whose value is in c. In UTF-8 mode it may have a value > 127. We set its representation in the length/buffer, and then handle it as a data character. */ #ifdef SUPPORT_UTF8 if (utf8 && c > 127) mclength = ord2utf8(c, mcbuffer); else #endif { mcbuffer[0] = c; mclength = 1; } goto ONE_CHAR; /* Handle a literal character. It is guaranteed not to be whitespace or # when the extended flag is set. If we are in UTF-8 mode, it may be a multi-byte literal character. */ default: NORMAL_CHAR: mclength = 1; mcbuffer[0] = c; #ifdef SUPPORT_UTF8 if (utf8 && (c & 0xc0) == 0xc0) { while ((ptr[1] & 0xc0) == 0x80) mcbuffer[mclength++] = *(++ptr); } #endif /* At this point we have the character's bytes in mcbuffer, and the length in mclength. When not in UTF-8 mode, the length is always 1. */ ONE_CHAR: previous = code; *code++ = ((options & PCRE_CASELESS) != 0)? OP_CHARNC : OP_CHAR; for (c = 0; c < mclength; c++) *code++ = mcbuffer[c]; /* Set the first and required bytes appropriately. If no previous first byte, set it from this character, but revert to none on a zero repeat. Otherwise, leave the firstbyte value alone, and don't change it on a zero repeat. */ if (firstbyte == REQ_UNSET) { zerofirstbyte = REQ_NONE; zeroreqbyte = reqbyte; /* If the character is more than one byte long, we can set firstbyte only if it is not to be matched caselessly. */ if (mclength == 1 || req_caseopt == 0) { firstbyte = mcbuffer[0] | req_caseopt; if (mclength != 1) reqbyte = code[-1] | cd->req_varyopt; } else firstbyte = reqbyte = REQ_NONE; } /* firstbyte was previously set; we can set reqbyte only the length is 1 or the matching is caseful. */ else { zerofirstbyte = firstbyte; zeroreqbyte = reqbyte; if (mclength == 1 || req_caseopt == 0) reqbyte = code[-1] | req_caseopt | cd->req_varyopt; } break; /* End of literal character handling */ } } /* end of big loop */ /* Control never reaches here by falling through, only by a goto for all the error states. Pass back the position in the pattern so that it can be displayed to the user for diagnosing the error. */ FAILED: *ptrptr = ptr; return FALSE; } /************************************************* * Compile sequence of alternatives * *************************************************/ /* On entry, ptr is pointing past the bracket character, but on return it points to the closing bracket, or vertical bar, or end of string. The code variable is pointing at the byte into which the BRA operator has been stored. If the ims options are changed at the start (for a (?ims: group) or during any branch, we need to insert an OP_OPT item at the start of every following branch to ensure they get set correctly at run time, and also pass the new options into every subsequent branch compile. Argument: options option bits, including any changes for this subpattern oldims previous settings of ims option bits brackets -> int containing the number of extracting brackets used codeptr -> the address of the current code pointer ptrptr -> the address of the current pattern pointer errorptr -> pointer to error message lookbehind TRUE if this is a lookbehind assertion skipbytes skip this many bytes at start (for OP_COND, OP_BRANUMBER) firstbyteptr place to put the first required character, or a negative number reqbyteptr place to put the last required character, or a negative number bcptr pointer to the chain of currently open branches cd points to the data block with tables pointers etc. Returns: TRUE on success */ static BOOL compile_regex(int options, int oldims, int *brackets, uschar **codeptr, const uschar **ptrptr, const char **errorptr, BOOL lookbehind, int skipbytes, int *firstbyteptr, int *reqbyteptr, branch_chain *bcptr, compile_data *cd) { const uschar *ptr = *ptrptr; uschar *code = *codeptr; uschar *last_branch = code; uschar *start_bracket = code; uschar *reverse_count = NULL; int firstbyte, reqbyte; int branchfirstbyte, branchreqbyte; branch_chain bc; bc.outer = bcptr; bc.current = code; firstbyte = reqbyte = REQ_UNSET; /* Offset is set zero to mark that this bracket is still open */ PUT(code, 1, 0); code += 1 + LINK_SIZE + skipbytes; /* Loop for each alternative branch */ for (;;) { /* Handle a change of ims options at the start of the branch */ if ((options & PCRE_IMS) != oldims) { *code++ = OP_OPT; *code++ = options & PCRE_IMS; } /* Set up dummy OP_REVERSE if lookbehind assertion */ if (lookbehind) { *code++ = OP_REVERSE; reverse_count = code; PUTINC(code, 0, 0); } /* Now compile the branch */ if (!compile_branch(&options, brackets, &code, &ptr, errorptr, &branchfirstbyte, &branchreqbyte, &bc, cd)) { *ptrptr = ptr; return FALSE; } /* If this is the first branch, the firstbyte and reqbyte values for the branch become the values for the regex. */ if (*last_branch != OP_ALT) { firstbyte = branchfirstbyte; reqbyte = branchreqbyte; } /* If this is not the first branch, the first char and reqbyte have to match the values from all the previous branches, except that if the previous value for reqbyte didn't have REQ_VARY set, it can still match, and we set REQ_VARY for the regex. */ else { /* If we previously had a firstbyte, but it doesn't match the new branch, we have to abandon the firstbyte for the regex, but if there was previously no reqbyte, it takes on the value of the old firstbyte. */ if (firstbyte >= 0 && firstbyte != branchfirstbyte) { if (reqbyte < 0) reqbyte = firstbyte; firstbyte = REQ_NONE; } /* If we (now or from before) have no firstbyte, a firstbyte from the branch becomes a reqbyte if there isn't a branch reqbyte. */ if (firstbyte < 0 && branchfirstbyte >= 0 && branchreqbyte < 0) branchreqbyte = branchfirstbyte; /* Now ensure that the reqbytes match */ if ((reqbyte & ~REQ_VARY) != (branchreqbyte & ~REQ_VARY)) reqbyte = REQ_NONE; else reqbyte |= branchreqbyte; /* To "or" REQ_VARY */ } /* If lookbehind, check that this branch matches a fixed-length string, and put the length into the OP_REVERSE item. Temporarily mark the end of the branch with OP_END. */ if (lookbehind) { int length; *code = OP_END; length = find_fixedlength(last_branch, options); DPRINTF(("fixed length = %d\n", length)); if (length < 0) { *errorptr = (length == -2)? ERR36 : ERR25; *ptrptr = ptr; return FALSE; } PUT(reverse_count, 0, length); } /* Reached end of expression, either ')' or end of pattern. Go back through the alternative branches and reverse the chain of offsets, with the field in the BRA item now becoming an offset to the first alternative. If there are no alternatives, it points to the end of the group. The length in the terminating ket is always the length of the whole bracketed item. If any of the ims options were changed inside the group, compile a resetting op-code following, except at the very end of the pattern. Return leaving the pointer at the terminating char. */ if (*ptr != '|') { int length = code - last_branch; do { int prev_length = GET(last_branch, 1); PUT(last_branch, 1, length); length = prev_length; last_branch -= length; } while (length > 0); /* Fill in the ket */ *code = OP_KET; PUT(code, 1, code - start_bracket); code += 1 + LINK_SIZE; /* Resetting option if needed */ if ((options & PCRE_IMS) != oldims && *ptr == ')') { *code++ = OP_OPT; *code++ = oldims; } /* Set values to pass back */ *codeptr = code; *ptrptr = ptr; *firstbyteptr = firstbyte; *reqbyteptr = reqbyte; return TRUE; } /* Another branch follows; insert an "or" node. Its length field points back to the previous branch while the bracket remains open. At the end the chain is reversed. It's done like this so that the start of the bracket has a zero offset until it is closed, making it possible to detect recursion. */ *code = OP_ALT; PUT(code, 1, code - last_branch); bc.current = last_branch = code; code += 1 + LINK_SIZE; ptr++; } /* Control never reaches here */ } /************************************************* * Check for anchored expression * *************************************************/ /* Try to find out if this is an anchored regular expression. Consider each alternative branch. If they all start with OP_SOD or OP_CIRC, or with a bracket all of whose alternatives start with OP_SOD or OP_CIRC (recurse ad lib), then it's anchored. However, if this is a multiline pattern, then only OP_SOD counts, since OP_CIRC can match in the middle. We can also consider a regex to be anchored if OP_SOM starts all its branches. This is the code for \G, which means "match at start of match position, taking into account the match offset". A branch is also implicitly anchored if it starts with .* and DOTALL is set, because that will try the rest of the pattern at all possible matching points, so there is no point trying again.... er .... .... except when the .* appears inside capturing parentheses, and there is a subsequent back reference to those parentheses. We haven't enough information to catch that case precisely. At first, the best we could do was to detect when .* was in capturing brackets and the highest back reference was greater than or equal to that level. However, by keeping a bitmap of the first 31 back references, we can catch some of the more common cases more precisely. Arguments: code points to start of expression (the bracket) options points to the options setting bracket_map a bitmap of which brackets we are inside while testing; this handles up to substring 31; after that we just have to take the less precise approach backref_map the back reference bitmap Returns: TRUE or FALSE */ static BOOL is_anchored(register const uschar *code, int *options, unsigned int bracket_map, unsigned int backref_map) { do { const uschar *scode = first_significant_code(code + 1+LINK_SIZE, options, PCRE_MULTILINE, FALSE); register int op = *scode; /* Capturing brackets */ if (op > OP_BRA) { int new_map; op -= OP_BRA; if (op > EXTRACT_BASIC_MAX) op = GET2(scode, 2+LINK_SIZE); new_map = bracket_map | ((op < 32)? (1 << op) : 1); if (!is_anchored(scode, options, new_map, backref_map)) return FALSE; } /* Other brackets */ else if (op == OP_BRA || op == OP_ASSERT || op == OP_ONCE || op == OP_COND) { if (!is_anchored(scode, options, bracket_map, backref_map)) return FALSE; } /* .* is not anchored unless DOTALL is set and it isn't in brackets that are or may be referenced. */ else if ((op == OP_TYPESTAR || op == OP_TYPEMINSTAR) && (*options & PCRE_DOTALL) != 0) { if (scode[1] != OP_ANY || (bracket_map & backref_map) != 0) return FALSE; } /* Check for explicit anchoring */ else if (op != OP_SOD && op != OP_SOM && ((*options & PCRE_MULTILINE) != 0 || op != OP_CIRC)) return FALSE; code += GET(code, 1); } while (*code == OP_ALT); /* Loop for each alternative */ return TRUE; } /************************************************* * Check for starting with ^ or .* * *************************************************/ /* This is called to find out if every branch starts with ^ or .* so that "first char" processing can be done to speed things up in multiline matching and for non-DOTALL patterns that start with .* (which must start at the beginning or after \n). As in the case of is_anchored() (see above), we have to take account of back references to capturing brackets that contain .* because in that case we can't make the assumption. Arguments: code points to start of expression (the bracket) bracket_map a bitmap of which brackets we are inside while testing; this handles up to substring 31; after that we just have to take the less precise approach backref_map the back reference bitmap Returns: TRUE or FALSE */ static BOOL is_startline(const uschar *code, unsigned int bracket_map, unsigned int backref_map) { do { const uschar *scode = first_significant_code(code + 1+LINK_SIZE, NULL, 0, FALSE); register int op = *scode; /* Capturing brackets */ if (op > OP_BRA) { int new_map; op -= OP_BRA; if (op > EXTRACT_BASIC_MAX) op = GET2(scode, 2+LINK_SIZE); new_map = bracket_map | ((op < 32)? (1 << op) : 1); if (!is_startline(scode, new_map, backref_map)) return FALSE; } /* Other brackets */ else if (op == OP_BRA || op == OP_ASSERT || op == OP_ONCE || op == OP_COND) { if (!is_startline(scode, bracket_map, backref_map)) return FALSE; } /* .* means "start at start or after \n" if it isn't in brackets that may be referenced. */ else if (op == OP_TYPESTAR || op == OP_TYPEMINSTAR) { if (scode[1] != OP_ANY || (bracket_map & backref_map) != 0) return FALSE; } /* Check for explicit circumflex */ else if (op != OP_CIRC) return FALSE; /* Move on to the next alternative */ code += GET(code, 1); } while (*code == OP_ALT); /* Loop for each alternative */ return TRUE; } /************************************************* * Check for asserted fixed first char * *************************************************/ /* During compilation, the "first char" settings from forward assertions are discarded, because they can cause conflicts with actual literals that follow. However, if we end up without a first char setting for an unanchored pattern, it is worth scanning the regex to see if there is an initial asserted first char. If all branches start with the same asserted char, or with a bracket all of whose alternatives start with the same asserted char (recurse ad lib), then we return that char, otherwise -1. Arguments: code points to start of expression (the bracket) options pointer to the options (used to check casing changes) inassert TRUE if in an assertion Returns: -1 or the fixed first char */ static int find_firstassertedchar(const uschar *code, int *options, BOOL inassert) { register int c = -1; do { int d; const uschar *scode = first_significant_code(code + 1+LINK_SIZE, options, PCRE_CASELESS, TRUE); register int op = *scode; if (op >= OP_BRA) op = OP_BRA; switch(op) { default: return -1; case OP_BRA: case OP_ASSERT: case OP_ONCE: case OP_COND: if ((d = find_firstassertedchar(scode, options, op == OP_ASSERT)) < 0) return -1; if (c < 0) c = d; else if (c != d) return -1; break; case OP_EXACT: /* Fall through */ scode += 2; case OP_CHAR: case OP_CHARNC: case OP_PLUS: case OP_MINPLUS: if (!inassert) return -1; if (c < 0) { c = scode[1]; if ((*options & PCRE_CASELESS) != 0) c |= REQ_CASELESS; } else if (c != scode[1]) return -1; break; } code += GET(code, 1); } while (*code == OP_ALT); return c; } #ifdef SUPPORT_UTF8 /************************************************* * Validate a UTF-8 string * *************************************************/ /* This function is called (optionally) at the start of compile or match, to validate that a supposed UTF-8 string is actually valid. The early check means that subsequent code can assume it is dealing with a valid string. The check can be turned off for maximum performance, but then consequences of supplying an invalid string are then undefined. Arguments: string points to the string length length of string, or -1 if the string is zero-terminated Returns: < 0 if the string is a valid UTF-8 string >= 0 otherwise; the value is the offset of the bad byte */ static int valid_utf8(const uschar *string, int length) { register const uschar *p; if (length < 0) { for (p = string; *p != 0; p++); length = p - string; } for (p = string; length-- > 0; p++) { register int ab; register int c = *p; if (c < 128) continue; if ((c & 0xc0) != 0xc0) return p - string; ab = utf8_table4[c & 0x3f]; /* Number of additional bytes */ if (length < ab) return p - string; length -= ab; /* Check top bits in the second byte */ if ((*(++p) & 0xc0) != 0x80) return p - string; /* Check for overlong sequences for each different length */ switch (ab) { /* Check for xx00 000x */ case 1: if ((c & 0x3e) == 0) return p - string; continue; /* We know there aren't any more bytes to check */ /* Check for 1110 0000, xx0x xxxx */ case 2: if (c == 0xe0 && (*p & 0x20) == 0) return p - string; break; /* Check for 1111 0000, xx00 xxxx */ case 3: if (c == 0xf0 && (*p & 0x30) == 0) return p - string; break; /* Check for 1111 1000, xx00 0xxx */ case 4: if (c == 0xf8 && (*p & 0x38) == 0) return p - string; break; /* Check for leading 0xfe or 0xff, and then for 1111 1100, xx00 00xx */ case 5: if (c == 0xfe || c == 0xff || (c == 0xfc && (*p & 0x3c) == 0)) return p - string; break; } /* Check for valid bytes after the 2nd, if any; all must start 10 */ while (--ab > 0) { if ((*(++p) & 0xc0) != 0x80) return p - string; } } return -1; } #endif /************************************************* * Compile a Regular Expression * *************************************************/ /* This function takes a string and returns a pointer to a block of store holding a compiled version of the expression. Arguments: pattern the regular expression options various option bits errorptr pointer to pointer to error text erroroffset ptr offset in pattern where error was detected tables pointer to character tables or NULL Returns: pointer to compiled data block, or NULL on error, with errorptr and erroroffset set */ EXPORT pcre * pcre_compile(const char *pattern, int options, const char **errorptr, int *erroroffset, const unsigned char *tables) { real_pcre *re; int length = 1 + LINK_SIZE; /* For initial BRA plus length */ //int runlength; int c, firstbyte, reqbyte; int bracount = 0; int branch_extra = 0; int branch_newextra; int item_count = -1; int name_count = 0; int max_name_size = 0; int lastitemlength = 0; #ifdef SUPPORT_UTF8 BOOL utf8; BOOL class_utf8; #endif BOOL inescq = FALSE; unsigned int brastackptr = 0; size_t size; uschar *code; const uschar *codestart; const uschar *ptr; compile_data compile_block; int brastack[BRASTACK_SIZE]; uschar bralenstack[BRASTACK_SIZE]; /* We can't pass back an error message if errorptr is NULL; I guess the best we can do is just return NULL. */ if (errorptr == NULL) return NULL; *errorptr = NULL; /* However, we can give a message for this error */ if (erroroffset == NULL) { *errorptr = ERR16; return NULL; } *erroroffset = 0; /* Can't support UTF8 unless PCRE has been compiled to include the code. */ #ifdef SUPPORT_UTF8 utf8 = (options & PCRE_UTF8) != 0; if (utf8 && (options & PCRE_NO_UTF8_CHECK) == 0 && (*erroroffset = valid_utf8((uschar *)pattern, -1)) >= 0) { *errorptr = ERR44; return NULL; } #else if ((options & PCRE_UTF8) != 0) { *errorptr = ERR32; return NULL; } #endif if ((options & ~PUBLIC_OPTIONS) != 0) { *errorptr = ERR17; return NULL; } /* Set up pointers to the individual character tables */ if (tables == NULL) tables = pcre_default_tables; compile_block.lcc = tables + lcc_offset; compile_block.fcc = tables + fcc_offset; compile_block.cbits = tables + cbits_offset; compile_block.ctypes = tables + ctypes_offset; /* Maximum back reference and backref bitmap. This is updated for numeric references during the first pass, but for named references during the actual compile pass. The bitmap records up to 31 back references to help in deciding whether (.*) can be treated as anchored or not. */ compile_block.top_backref = 0; compile_block.backref_map = 0; /* Reflect pattern for debugging output */ DPRINTF(("------------------------------------------------------------------\n")); DPRINTF(("%s\n", pattern)); /* The first thing to do is to make a pass over the pattern to compute the amount of store required to hold the compiled code. This does not have to be perfect as long as errors are overestimates. At the same time we can detect any flag settings right at the start, and extract them. Make an attempt to correct for any counted white space if an "extended" flag setting appears late in the pattern. We can't be so clever for #-comments. */ ptr = (const uschar *)(pattern - 1); while ((c = *(++ptr)) != 0) { int min, max; int class_optcount; int bracket_length; int duplength; /* If we are inside a \Q...\E sequence, all chars are literal */ if (inescq) { if ((options & PCRE_AUTO_CALLOUT) != 0) length += 2 + 2*LINK_SIZE; goto NORMAL_CHAR; } /* Otherwise, first check for ignored whitespace and comments */ if ((options & PCRE_EXTENDED) != 0) { if ((compile_block.ctypes[c] & ctype_space) != 0) continue; if (c == '#') { /* The space before the ; is to avoid a warning on a silly compiler on the Macintosh. */ while ((c = *(++ptr)) != 0 && c != NEWLINE) ; if (c == 0) break; continue; } } item_count++; /* Is zero for the first non-comment item */ /* Allow space for auto callout before every item except quantifiers. */ if ((options & PCRE_AUTO_CALLOUT) != 0 && c != '*' && c != '+' && c != '?' && (c != '{' || !is_counted_repeat(ptr + 1))) length += 2 + 2*LINK_SIZE; switch(c) { /* A backslashed item may be an escaped data character or it may be a character type. */ case '\\': c = check_escape(&ptr, errorptr, bracount, options, FALSE); if (*errorptr != NULL) goto PCRE_ERROR_RETURN; lastitemlength = 1; /* Default length of last item for repeats */ if (c >= 0) /* Data character */ { length += 2; /* For a one-byte character */ #ifdef SUPPORT_UTF8 if (utf8 && c > 127) { int i; for (i = 0; i < sizeof(utf8_table1)/sizeof(int); i++) if (c <= utf8_table1[i]) break; length += i; lastitemlength += i; } #endif continue; } /* If \Q, enter "literal" mode */ if (-c == ESC_Q) { inescq = TRUE; continue; } /* \X is supported only if Unicode property support is compiled */ #ifndef SUPPORT_UCP if (-c == ESC_X) { *errorptr = ERR45; goto PCRE_ERROR_RETURN; } #endif /* \P and \p are for Unicode properties, but only when the support has been compiled. Each item needs 2 bytes. */ else if (-c == ESC_P || -c == ESC_p) { #ifdef SUPPORT_UCP BOOL negated; length += 2; lastitemlength = 2; if (get_ucp(&ptr, &negated, errorptr) < 0) goto PCRE_ERROR_RETURN; continue; #else *errorptr = ERR45; goto PCRE_ERROR_RETURN; #endif } /* Other escapes need one byte */ length++; /* A back reference needs an additional 2 bytes, plus either one or 5 bytes for a repeat. We also need to keep the value of the highest back reference. */ if (c <= -ESC_REF) { int refnum = -c - ESC_REF; compile_block.backref_map |= (refnum < 32)? (1 << refnum) : 1; if (refnum > compile_block.top_backref) compile_block.top_backref = refnum; length += 2; /* For single back reference */ if (ptr[1] == '{' && is_counted_repeat(ptr+2)) { ptr = read_repeat_counts(ptr+2, &min, &max, errorptr); if (*errorptr != NULL) goto PCRE_ERROR_RETURN; if ((min == 0 && (max == 1 || max == -1)) || (min == 1 && max == -1)) length++; else length += 5; if (ptr[1] == '?') ptr++; } } continue; case '^': /* Single-byte metacharacters */ case '.': case '$': length++; lastitemlength = 1; continue; case '*': /* These repeats won't be after brackets; */ case '+': /* those are handled separately */ case '?': length++; goto POSESSIVE; /* A few lines below */ /* This covers the cases of braced repeats after a single char, metachar, class, or back reference. */ case '{': if (!is_counted_repeat(ptr+1)) goto NORMAL_CHAR; ptr = read_repeat_counts(ptr+1, &min, &max, errorptr); if (*errorptr != NULL) goto PCRE_ERROR_RETURN; /* These special cases just insert one extra opcode */ if ((min == 0 && (max == 1 || max == -1)) || (min == 1 && max == -1)) length++; /* These cases might insert additional copies of a preceding character. */ else { if (min != 1) { length -= lastitemlength; /* Uncount the original char or metachar */ if (min > 0) length += 3 + lastitemlength; } length += lastitemlength + ((max > 0)? 3 : 1); } if (ptr[1] == '?') ptr++; /* Needs no extra length */ POSESSIVE: /* Test for possessive quantifier */ if (ptr[1] == '+') { ptr++; length += 2 + 2*LINK_SIZE; /* Allow for atomic brackets */ } continue; /* An alternation contains an offset to the next branch or ket. If any ims options changed in the previous branch(es), and/or if we are in a lookbehind assertion, extra space will be needed at the start of the branch. This is handled by branch_extra. */ case '|': length += 1 + LINK_SIZE + branch_extra; continue; /* A character class uses 33 characters provided that all the character values are less than 256. Otherwise, it uses a bit map for low valued characters, and individual items for others. Don't worry about character types that aren't allowed in classes - they'll get picked up during the compile. A character class that contains only one single-byte character uses 2 or 3 bytes, depending on whether it is negated or not. Notice this where we can. (In UTF-8 mode we can do this only for chars < 128.) */ case '[': if (*(++ptr) == '^') { class_optcount = 10; /* Greater than one */ ptr++; } else class_optcount = 0; #ifdef SUPPORT_UTF8 class_utf8 = FALSE; #endif /* Written as a "do" so that an initial ']' is taken as data */ if (*ptr != 0) do { /* Inside \Q...\E everything is literal except \E */ if (inescq) { if (*ptr != '\\' || ptr[1] != 'E') goto GET_ONE_CHARACTER; inescq = FALSE; ptr += 1; continue; } /* Outside \Q...\E, check for escapes */ if (*ptr == '\\') { c = check_escape(&ptr, errorptr, bracount, options, TRUE); if (*errorptr != NULL) goto PCRE_ERROR_RETURN; /* \b is backspace inside a class; \X is literal */ if (-c == ESC_b) c = '\b'; else if (-c == ESC_X) c = 'X'; /* \Q enters quoting mode */ else if (-c == ESC_Q) { inescq = TRUE; continue; } /* Handle escapes that turn into characters */ if (c >= 0) goto NON_SPECIAL_CHARACTER; /* Escapes that are meta-things. The normal ones just affect the bit map, but Unicode properties require an XCLASS extended item. */ else { class_optcount = 10; /* \d, \s etc; make sure > 1 */ #ifdef SUPPORT_UTF8 if (-c == ESC_p || -c == ESC_P) { if (!class_utf8) { class_utf8 = TRUE; length += LINK_SIZE + 2; } length += 2; } #endif } } /* Check the syntax for POSIX stuff. The bits we actually handle are checked during the real compile phase. */ else if (*ptr == '[' && check_posix_syntax(ptr, &ptr, &compile_block)) { ptr++; class_optcount = 10; /* Make sure > 1 */ } /* Anything else increments the possible optimization count. We have to detect ranges here so that we can compute the number of extra ranges for caseless wide characters when UCP support is available. If there are wide characters, we are going to have to use an XCLASS, even for single characters. */ else { int d; GET_ONE_CHARACTER: #ifdef SUPPORT_UTF8 if (utf8) { int extra = 0; GETCHARLEN(c, ptr, extra); ptr += extra; } else c = *ptr; #else c = *ptr; #endif /* Come here from handling \ above when it escapes to a char value */ NON_SPECIAL_CHARACTER: class_optcount++; d = -1; if (ptr[1] == '-') { uschar const *hyptr = ptr++; if (ptr[1] == '\\') { ptr++; d = check_escape(&ptr, errorptr, bracount, options, TRUE); if (*errorptr != NULL) goto PCRE_ERROR_RETURN; if (-d == ESC_b) d = '\b'; /* backspace */ else if (-d == ESC_X) d = 'X'; /* literal X in a class */ } else if (ptr[1] != 0 && ptr[1] != ']') { ptr++; #ifdef SUPPORT_UTF8 if (utf8) { int extra = 0; GETCHARLEN(d, ptr, extra); ptr += extra; } else #endif d = *ptr; } if (d < 0) ptr = hyptr; /* go back to hyphen as data */ } /* If d >= 0 we have a range. In UTF-8 mode, if the end is > 255, or > 127 for caseless matching, we will need to use an XCLASS. */ if (d >= 0) { class_optcount = 10; /* Ensure > 1 */ if (d < c) { *errorptr = ERR8; goto PCRE_ERROR_RETURN; } #ifdef SUPPORT_UTF8 if (utf8 && (d > 255 || ((options & PCRE_CASELESS) != 0 && d > 127))) { uschar buffer[6]; if (!class_utf8) /* Allow for XCLASS overhead */ { class_utf8 = TRUE; length += LINK_SIZE + 2; } #ifdef SUPPORT_UCP /* If we have UCP support, find out how many extra ranges are needed to map the other case of characters within this range. We have to mimic the range optimization here, because extending the range upwards might push d over a boundary that makes is use another byte in the UTF-8 representation. */ if ((options & PCRE_CASELESS) != 0) { int occ, ocd; int cc = c; int origd = d; while (get_othercase_range(&cc, origd, &occ, &ocd)) { if (occ >= c && ocd <= d) continue; /* Skip embedded */ if (occ < c && ocd >= c - 1) /* Extend the basic range */ { /* if there is overlap, */ c = occ; /* noting that if occ < c */ continue; /* we can't have ocd > d */ } /* because a subrange is */ if (ocd > d && occ <= d + 1) /* always shorter than */ { /* the basic range. */ d = ocd; continue; } /* An extra item is needed */ length += 1 + ord2utf8(occ, buffer) + ((occ == ocd)? 0 : ord2utf8(ocd, buffer)); } } #endif /* SUPPORT_UCP */ /* The length of the (possibly extended) range */ length += 1 + ord2utf8(c, buffer) + ord2utf8(d, buffer); } #endif /* SUPPORT_UTF8 */ } /* We have a single character. There is nothing to be done unless we are in UTF-8 mode. If the char is > 255, or 127 when caseless, we must allow for an XCL_SINGLE item, doubled for caselessness if there is UCP support. */ else { #ifdef SUPPORT_UTF8 if (utf8 && (c > 255 || ((options & PCRE_CASELESS) != 0 && c > 127))) { uschar buffer[6]; class_optcount = 10; /* Ensure > 1 */ if (!class_utf8) /* Allow for XCLASS overhead */ { class_utf8 = TRUE; length += LINK_SIZE + 2; } #ifdef SUPPORT_UCP length += (((options & PCRE_CASELESS) != 0)? 2 : 1) * (1 + ord2utf8(c, buffer)); #else /* SUPPORT_UCP */ length += 1 + ord2utf8(c, buffer); #endif /* SUPPORT_UCP */ } #endif /* SUPPORT_UTF8 */ } } } while (*(++ptr) != 0 && (inescq || *ptr != ']')); /* Concludes "do" above */ if (*ptr == 0) /* Missing terminating ']' */ { *errorptr = ERR6; goto PCRE_ERROR_RETURN; } /* We can optimize when there was only one optimizable character. Repeats for positive and negated single one-byte chars are handled by the general code. Here, we handle repeats for the class opcodes. */ if (class_optcount == 1) length += 3; else { length += 33; /* A repeat needs either 1 or 5 bytes. If it is a possessive quantifier, we also need extra for wrapping the whole thing in a sub-pattern. */ if (*ptr != 0 && ptr[1] == '{' && is_counted_repeat(ptr+2)) { ptr = read_repeat_counts(ptr+2, &min, &max, errorptr); if (*errorptr != NULL) goto PCRE_ERROR_RETURN; if ((min == 0 && (max == 1 || max == -1)) || (min == 1 && max == -1)) length++; else length += 5; if (ptr[1] == '+') { ptr++; length += 2 + 2*LINK_SIZE; } else if (ptr[1] == '?') ptr++; } } continue; /* Brackets may be genuine groups or special things */ case '(': branch_newextra = 0; bracket_length = 1 + LINK_SIZE; /* Handle special forms of bracket, which all start (? */ if (ptr[1] == '?') { int set, unset; int *optset; switch (c = ptr[2]) { /* Skip over comments entirely */ case '#': ptr += 3; while (*ptr != 0 && *ptr != ')') ptr++; if (*ptr == 0) { *errorptr = ERR18; goto PCRE_ERROR_RETURN; } continue; /* Non-referencing groups and lookaheads just move the pointer on, and then behave like a non-special bracket, except that they don't increment the count of extracting brackets. Ditto for the "once only" bracket, which is in Perl from version 5.005. */ case ':': case '=': case '!': case '>': ptr += 2; break; /* (?R) specifies a recursive call to the regex, which is an extension to provide the facility which can be obtained by (?p{perl-code}) in Perl 5.6. In Perl 5.8 this has become (??{perl-code}). From PCRE 4.00, items such as (?3) specify subroutine-like "calls" to the appropriate numbered brackets. This includes both recursive and non-recursive calls. (?R) is now synonymous with (?0). */ case 'R': ptr++; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': ptr += 2; if (c != 'R') while ((digitab[*(++ptr)] & ctype_digit) != 0); if (*ptr != ')') { *errorptr = ERR29; goto PCRE_ERROR_RETURN; } length += 1 + LINK_SIZE; /* If this item is quantified, it will get wrapped inside brackets so as to use the code for quantified brackets. We jump down and use the code that handles this for real brackets. */ if (ptr[1] == '+' || ptr[1] == '*' || ptr[1] == '?' || ptr[1] == '{') { length += 2 + 2 * LINK_SIZE; /* to make bracketed */ duplength = 5 + 3 * LINK_SIZE; goto HANDLE_QUANTIFIED_BRACKETS; } continue; /* (?C) is an extension which provides "callout" - to provide a bit of the functionality of the Perl (?{...}) feature. An optional number may follow (default is zero). */ case 'C': ptr += 2; while ((digitab[*(++ptr)] & ctype_digit) != 0); if (*ptr != ')') { *errorptr = ERR39; goto PCRE_ERROR_RETURN; } length += 2 + 2*LINK_SIZE; continue; /* Named subpatterns are an extension copied from Python */ case 'P': ptr += 3; if (*ptr == '<') { const uschar *p; /* Don't amalgamate; some compilers */ p = ++ptr; /* grumble at autoincrement in declaration */ while ((compile_block.ctypes[*ptr] & ctype_word) != 0) ptr++; if (*ptr != '>') { *errorptr = ERR42; goto PCRE_ERROR_RETURN; } name_count++; if (ptr - p > max_name_size) max_name_size = (ptr - p); break; } if (*ptr == '=' || *ptr == '>') { while ((compile_block.ctypes[*(++ptr)] & ctype_word) != 0); if (*ptr != ')') { *errorptr = ERR42; goto PCRE_ERROR_RETURN; } break; } /* Unknown character after (?P */ *errorptr = ERR41; goto PCRE_ERROR_RETURN; /* Lookbehinds are in Perl from version 5.005 */ case '<': ptr += 3; if (*ptr == '=' || *ptr == '!') { branch_newextra = 1 + LINK_SIZE; length += 1 + LINK_SIZE; /* For the first branch */ break; } *errorptr = ERR24; goto PCRE_ERROR_RETURN; /* Conditionals are in Perl from version 5.005. The bracket must either be followed by a number (for bracket reference) or by an assertion group, or (a PCRE extension) by 'R' for a recursion test. */ case '(': if (ptr[3] == 'R' && ptr[4] == ')') { ptr += 4; length += 3; } else if ((digitab[ptr[3]] & ctype_digit) != 0) { ptr += 4; length += 3; while ((digitab[*ptr] & ctype_digit) != 0) ptr++; if (*ptr != ')') { *errorptr = ERR26; goto PCRE_ERROR_RETURN; } } else /* An assertion must follow */ { ptr++; /* Can treat like ':' as far as spacing is concerned */ if (ptr[2] != '?' || (ptr[3] != '=' && ptr[3] != '!' && ptr[3] != '<') ) { ptr += 2; /* To get right offset in message */ *errorptr = ERR28; goto PCRE_ERROR_RETURN; } } break; /* Else loop checking valid options until ) is met. Anything else is an error. If we are without any brackets, i.e. at top level, the settings act as if specified in the options, so massage the options immediately. This is for backward compatibility with Perl 5.004. */ default: set = unset = 0; optset = &set; ptr += 2; for (;; ptr++) { c = *ptr; switch (c) { case 'i': *optset |= PCRE_CASELESS; continue; case 'm': *optset |= PCRE_MULTILINE; continue; case 's': *optset |= PCRE_DOTALL; continue; case 'x': *optset |= PCRE_EXTENDED; continue; case 'X': *optset |= PCRE_EXTRA; continue; case 'U': *optset |= PCRE_UNGREEDY; continue; case '-': optset = &unset; continue; /* A termination by ')' indicates an options-setting-only item; if this is at the very start of the pattern (indicated by item_count being zero), we use it to set the global options. This is helpful when analyzing the pattern for first characters, etc. Otherwise nothing is done here and it is handled during the compiling process. [Historical note: Up to Perl 5.8, options settings at top level were always global settings, wherever they appeared in the pattern. That is, they were equivalent to an external setting. From 5.8 onwards, they apply only to what follows (which is what you might expect).] */ case ')': if (item_count == 0) { options = (options | set) & (~unset); set = unset = 0; /* To save length */ item_count--; /* To allow for several */ } /* Fall through */ /* A termination by ':' indicates the start of a nested group with the given options set. This is again handled at compile time, but we must allow for compiled space if any of the ims options are set. We also have to allow for resetting space at the end of the group, which is why 4 is added to the length and not just 2. If there are several changes of options within the same group, this will lead to an over-estimate on the length, but this shouldn't matter very much. We also have to allow for resetting options at the start of any alternations, which we do by setting branch_newextra to 2. Finally, we record whether the case-dependent flag ever changes within the regex. This is used by the "required character" code. */ case ':': if (((set|unset) & PCRE_IMS) != 0) { length += 4; branch_newextra = 2; if (((set|unset) & PCRE_CASELESS) != 0) options |= PCRE_ICHANGED; } goto END_OPTIONS; /* Unrecognized option character */ default: *errorptr = ERR12; goto PCRE_ERROR_RETURN; } } /* If we hit a closing bracket, that's it - this is a freestanding option-setting. We need to ensure that branch_extra is updated if necessary. The only values branch_newextra can have here are 0 or 2. If the value is 2, then branch_extra must either be 2 or 5, depending on whether this is a lookbehind group or not. */ END_OPTIONS: if (c == ')') { if (branch_newextra == 2 && (branch_extra == 0 || branch_extra == 1+LINK_SIZE)) branch_extra += branch_newextra; continue; } /* If options were terminated by ':' control comes here. Fall through to handle the group below. */ } } /* Extracting brackets must be counted so we can process escapes in a Perlish way. If the number exceeds EXTRACT_BASIC_MAX we are going to need an additional 3 bytes of store per extracting bracket. However, if PCRE_NO_AUTO)CAPTURE is set, unadorned brackets become non-capturing, so we must leave the count alone (it will aways be zero). */ else if ((options & PCRE_NO_AUTO_CAPTURE) == 0) { bracount++; if (bracount > EXTRACT_BASIC_MAX) bracket_length += 3; } /* Save length for computing whole length at end if there's a repeat that requires duplication of the group. Also save the current value of branch_extra, and start the new group with the new value. If non-zero, this will either be 2 for a (?imsx: group, or 3 for a lookbehind assertion. */ if (brastackptr >= sizeof(brastack)/sizeof(int)) { *errorptr = ERR19; goto PCRE_ERROR_RETURN; } bralenstack[brastackptr] = branch_extra; branch_extra = branch_newextra; brastack[brastackptr++] = length; length += bracket_length; continue; /* Handle ket. Look for subsequent max/min; for certain sets of values we have to replicate this bracket up to that many times. If brastackptr is 0 this is an unmatched bracket which will generate an error, but take care not to try to access brastack[-1] when computing the length and restoring the branch_extra value. */ case ')': length += 1 + LINK_SIZE; if (brastackptr > 0) { duplength = length - brastack[--brastackptr]; branch_extra = bralenstack[brastackptr]; } else duplength = 0; /* The following code is also used when a recursion such as (?3) is followed by a quantifier, because in that case, it has to be wrapped inside brackets so that the quantifier works. The value of duplength must be set before arrival. */ HANDLE_QUANTIFIED_BRACKETS: /* Leave ptr at the final char; for read_repeat_counts this happens automatically; for the others we need an increment. */ if ((c = ptr[1]) == '{' && is_counted_repeat(ptr+2)) { ptr = read_repeat_counts(ptr+2, &min, &max, errorptr); if (*errorptr != NULL) goto PCRE_ERROR_RETURN; } else if (c == '*') { min = 0; max = -1; ptr++; } else if (c == '+') { min = 1; max = -1; ptr++; } else if (c == '?') { min = 0; max = 1; ptr++; } else { min = 1; max = 1; } /* If the minimum is zero, we have to allow for an OP_BRAZERO before the group, and if the maximum is greater than zero, we have to replicate maxval-1 times; each replication acquires an OP_BRAZERO plus a nesting bracket set. */ if (min == 0) { length++; if (max > 0) length += (max - 1) * (duplength + 3 + 2*LINK_SIZE); } /* When the minimum is greater than zero, we have to replicate up to minval-1 times, with no additions required in the copies. Then, if there is a limited maximum we have to replicate up to maxval-1 times allowing for a BRAZERO item before each optional copy and nesting brackets for all but one of the optional copies. */ else { length += (min - 1) * duplength; if (max > min) /* Need this test as max=-1 means no limit */ length += (max - min) * (duplength + 3 + 2*LINK_SIZE) - (2 + 2*LINK_SIZE); } /* Allow space for once brackets for "possessive quantifier" */ if (ptr[1] == '+') { ptr++; length += 2 + 2*LINK_SIZE; } continue; /* Non-special character. It won't be space or # in extended mode, so it is always a genuine character. If we are in a \Q...\E sequence, check for the end; if not, we have a literal. */ default: NORMAL_CHAR: if (inescq && c == '\\' && ptr[1] == 'E') { inescq = FALSE; ptr++; continue; } length += 2; /* For a one-byte character */ lastitemlength = 1; /* Default length of last item for repeats */ /* In UTF-8 mode, check for additional bytes. */ #ifdef SUPPORT_UTF8 if (utf8 && (c & 0xc0) == 0xc0) { while ((ptr[1] & 0xc0) == 0x80) /* Can't flow over the end */ { /* because the end is marked */ lastitemlength++; /* by a zero byte. */ length++; ptr++; } } #endif continue; } } length += 2 + LINK_SIZE; /* For final KET and END */ if ((options & PCRE_AUTO_CALLOUT) != 0) length += 2 + 2*LINK_SIZE; /* For final callout */ if (length > MAX_PATTERN_SIZE) { *errorptr = ERR20; return NULL; } /* Compute the size of data block needed and get it, either from malloc or externally provided function. */ size = length + sizeof(real_pcre) + name_count * (max_name_size + 3); re = (real_pcre *)(pcre_malloc)(size); if (re == NULL) { *errorptr = ERR21; return NULL; } /* Put in the magic number, and save the sizes, options, and character table pointer. NULL is used for the default character tables. The nullpad field is at the end; it's there to help in the case when a regex compiled on a system with 4-byte pointers is run on another with 8-byte pointers. */ re->magic_number = MAGIC_NUMBER; re->size = size; re->options = options; re->dummy1 = re->dummy2 = 0; re->name_table_offset = sizeof(real_pcre); re->name_entry_size = max_name_size + 3; re->name_count = name_count; re->tables = (tables == pcre_default_tables)? NULL : tables; re->nullpad = NULL; /* The starting points of the name/number translation table and of the code are passed around in the compile data block. */ compile_block.names_found = 0; compile_block.name_entry_size = max_name_size + 3; compile_block.name_table = (uschar *)re + re->name_table_offset; codestart = compile_block.name_table + re->name_entry_size * re->name_count; compile_block.start_code = codestart; compile_block.start_pattern = (const uschar *)pattern; compile_block.req_varyopt = 0; compile_block.nopartial = FALSE; /* Set up a starting, non-extracting bracket, then compile the expression. On error, *errorptr will be set non-NULL, so we don't need to look at the result of the function here. */ ptr = (const uschar *)pattern; code = (uschar *)codestart; *code = OP_BRA; bracount = 0; (void)compile_regex(options, options & PCRE_IMS, &bracount, &code, &ptr, errorptr, FALSE, 0, &firstbyte, &reqbyte, NULL, &compile_block); re->top_bracket = bracount; re->top_backref = compile_block.top_backref; if (compile_block.nopartial) re->options |= PCRE_NOPARTIAL; /* If not reached end of pattern on success, there's an excess bracket. */ if (*errorptr == NULL && *ptr != 0) *errorptr = ERR22; /* Fill in the terminating state and check for disastrous overflow, but if debugging, leave the test till after things are printed out. */ *code++ = OP_END; #ifndef DEBUG if (code - codestart > length) *errorptr = ERR23; #endif /* Give an error if there's back reference to a non-existent capturing subpattern. */ if (re->top_backref > re->top_bracket) *errorptr = ERR15; /* Failed to compile, or error while post-processing */ if (*errorptr != NULL) { (pcre_free)(re); PCRE_ERROR_RETURN: *erroroffset = ptr - (const uschar *)pattern; return NULL; } /* If the anchored option was not passed, set the flag if we can determine that the pattern is anchored by virtue of ^ characters or \A or anything else (such as starting with .* when DOTALL is set). Otherwise, if we know what the first character has to be, save it, because that speeds up unanchored matches no end. If not, see if we can set the PCRE_STARTLINE flag. This is helpful for multiline matches when all branches start with ^. and also when all branches start with .* for non-DOTALL matches. */ if ((options & PCRE_ANCHORED) == 0) { int temp_options = options; if (is_anchored(codestart, &temp_options, 0, compile_block.backref_map)) re->options |= PCRE_ANCHORED; else { if (firstbyte < 0) firstbyte = find_firstassertedchar(codestart, &temp_options, FALSE); if (firstbyte >= 0) /* Remove caseless flag for non-caseable chars */ { int ch = firstbyte & 255; re->first_byte = ((firstbyte & REQ_CASELESS) != 0 && compile_block.fcc[ch] == ch)? ch : firstbyte; re->options |= PCRE_FIRSTSET; } else if (is_startline(codestart, 0, compile_block.backref_map)) re->options |= PCRE_STARTLINE; } } /* For an anchored pattern, we use the "required byte" only if it follows a variable length item in the regex. Remove the caseless flag for non-caseable bytes. */ if (reqbyte >= 0 && ((re->options & PCRE_ANCHORED) == 0 || (reqbyte & REQ_VARY) != 0)) { int ch = reqbyte & 255; re->req_byte = ((reqbyte & REQ_CASELESS) != 0 && compile_block.fcc[ch] == ch)? (reqbyte & ~REQ_CASELESS) : reqbyte; re->options |= PCRE_REQCHSET; } /* Print out the compiled data for debugging */ #ifdef DEBUG printf("Length = %d top_bracket = %d top_backref = %d\n", length, re->top_bracket, re->top_backref); if (re->options != 0) { printf("%s%s%s%s%s%s%s%s%s%s\n", ((re->options & PCRE_NOPARTIAL) != 0)? "nopartial " : "", ((re->options & PCRE_ANCHORED) != 0)? "anchored " : "", ((re->options & PCRE_CASELESS) != 0)? "caseless " : "", ((re->options & PCRE_ICHANGED) != 0)? "case state changed " : "", ((re->options & PCRE_EXTENDED) != 0)? "extended " : "", ((re->options & PCRE_MULTILINE) != 0)? "multiline " : "", ((re->options & PCRE_DOTALL) != 0)? "dotall " : "", ((re->options & PCRE_DOLLAR_ENDONLY) != 0)? "endonly " : "", ((re->options & PCRE_EXTRA) != 0)? "extra " : "", ((re->options & PCRE_UNGREEDY) != 0)? "ungreedy " : ""); } if ((re->options & PCRE_FIRSTSET) != 0) { int ch = re->first_byte & 255; const char *caseless = ((re->first_byte & REQ_CASELESS) == 0)? "" : " (caseless)"; if (isprint(ch)) printf("First char = %c%s\n", ch, caseless); else printf("First char = \\x%02x%s\n", ch, caseless); } if ((re->options & PCRE_REQCHSET) != 0) { int ch = re->req_byte & 255; const char *caseless = ((re->req_byte & REQ_CASELESS) == 0)? "" : " (caseless)"; if (isprint(ch)) printf("Req char = %c%s\n", ch, caseless); else printf("Req char = \\x%02x%s\n", ch, caseless); } print_internals(re, stdout); /* This check is done here in the debugging case so that the code that was compiled can be seen. */ if (code - codestart > length) { *errorptr = ERR23; (pcre_free)(re); *erroroffset = ptr - (uschar *)pattern; return NULL; } #endif return (pcre *)re; } /************************************************* * Match a back-reference * *************************************************/ /* If a back reference hasn't been set, the length that is passed is greater than the number of characters left in the string, so the match fails. Arguments: offset index into the offset vector eptr points into the subject length length to be matched md points to match data block ims the ims flags Returns: TRUE if matched */ static BOOL match_ref(int offset, register const uschar *eptr, int length, match_data *md, unsigned long int ims) { const uschar *p = md->start_subject + md->offset_vector[offset]; #ifdef DEBUG if (eptr >= md->end_subject) printf("matching subject "); else { printf("matching subject "); pchars(eptr, length, TRUE, md); } printf(" against backref "); pchars(p, length, FALSE, md); printf("\n"); #endif /* Always fail if not enough characters left */ if (length > md->end_subject - eptr) return FALSE; /* Separate the caselesss case for speed */ if ((ims & PCRE_CASELESS) != 0) { while (length-- > 0) if (md->lcc[*p++] != md->lcc[*eptr++]) return FALSE; } else { while (length-- > 0) if (*p++ != *eptr++) return FALSE; } return TRUE; } #ifdef SUPPORT_UTF8 /************************************************* * Match character against an XCLASS * *************************************************/ /* This function is called from within the XCLASS code below, to match a character against an extended class which might match values > 255. Arguments: c the character data points to the flag byte of the XCLASS data Returns: TRUE if character matches, else FALSE */ static BOOL match_xclass(int c, const uschar *data) { int t; BOOL negated = (*data & XCL_NOT) != 0; /* Character values < 256 are matched against a bitmap, if one is present. If not, we still carry on, because there may be ranges that start below 256 in the additional data. */ if (c < 256) { if ((*data & XCL_MAP) != 0 && (data[1 + c/8] & (1 << (c&7))) != 0) return !negated; /* char found */ } /* First skip the bit map if present. Then match against the list of Unicode properties or large chars or ranges that end with a large char. We won't ever encounter XCL_PROP or XCL_NOTPROP when UCP support is not compiled. */ if ((*data++ & XCL_MAP) != 0) data += 32; while ((t = *data++) != XCL_END) { int x, y; if (t == XCL_SINGLE) { GETCHARINC(x, data); if (c == x) return !negated; } else if (t == XCL_RANGE) { GETCHARINC(x, data); GETCHARINC(y, data); if (c >= x && c <= y) return !negated; } #ifdef SUPPORT_UCP else /* XCL_PROP & XCL_NOTPROP */ { int chartype, othercase; int rqdtype = *data++; int category = ucp_findchar(c, &chartype, &othercase); if (rqdtype >= 128) { if ((rqdtype - 128 == category) == (t == XCL_PROP)) return !negated; } else { if ((rqdtype == chartype) == (t == XCL_PROP)) return !negated; } } #endif /* SUPPORT_UCP */ } return negated; /* char did not match */ } #endif /*************************************************************************** **************************************************************************** RECURSION IN THE match() FUNCTION The match() function is highly recursive. Some regular expressions can cause it to recurse thousands of times. I was writing for Unix, so I just let it call itself recursively. This uses the stack for saving everything that has to be saved for a recursive call. On Unix, the stack can be large, and this works fine. It turns out that on non-Unix systems there are problems with programs that use a lot of stack. (This despite the fact that every last chip has oodles of memory these days, and techniques for extending the stack have been known for decades.) So.... There is a fudge, triggered by defining NO_RECURSE, which avoids recursive calls by keeping local variables that need to be preserved in blocks of memory obtained from malloc instead instead of on the stack. Macros are used to achieve this so that the actual code doesn't look very different to what it always used to. **************************************************************************** ***************************************************************************/ /* These versions of the macros use the stack, as normal */ #ifndef NO_RECURSE #define REGISTER register #define RMATCH(rx,ra,rb,rc,rd,re,rf,rg) rx = match(ra,rb,rc,rd,re,rf,rg) #define RRETURN(ra) return ra #else /* These versions of the macros manage a private stack on the heap. Note that the rd argument of RMATCH isn't actually used. It's the md argument of match(), which never changes. */ #define REGISTER #define RMATCH(rx,ra,rb,rc,rd,re,rf,rg)\ {\ heapframe *newframe = (pcre_stack_malloc)(sizeof(heapframe));\ if (setjmp(frame->Xwhere) == 0)\ {\ newframe->Xeptr = ra;\ newframe->Xecode = rb;\ newframe->Xoffset_top = rc;\ newframe->Xims = re;\ newframe->Xeptrb = rf;\ newframe->Xflags = rg;\ newframe->Xprevframe = frame;\ frame = newframe;\ DPRINTF(("restarting from line %d\n", __LINE__));\ goto HEAP_RECURSE;\ }\ else\ {\ DPRINTF(("longjumped back to line %d\n", __LINE__));\ frame = md->thisframe;\ rx = frame->Xresult;\ }\ } #define RRETURN(ra)\ {\ heapframe *newframe = frame;\ frame = newframe->Xprevframe;\ (pcre_stack_free)(newframe);\ if (frame != NULL)\ {\ frame->Xresult = ra;\ md->thisframe = frame;\ longjmp(frame->Xwhere, 1);\ }\ return ra;\ } /* Structure for remembering the local variables in a private frame */ typedef struct heapframe { struct heapframe *Xprevframe; /* Function arguments that may change */ const uschar *Xeptr; const uschar *Xecode; int Xoffset_top; long int Xims; eptrblock *Xeptrb; int Xflags; /* Function local variables */ const uschar *Xcallpat; const uschar *Xcharptr; const uschar *Xdata; const uschar *Xnext; const uschar *Xpp; const uschar *Xprev; const uschar *Xsaved_eptr; recursion_info Xnew_recursive; BOOL Xcur_is_word; BOOL Xcondition; BOOL Xminimize; BOOL Xprev_is_word; unsigned long int Xoriginal_ims; #ifdef SUPPORT_UCP int Xprop_type; int Xprop_fail_result; int Xprop_category; int Xprop_chartype; int Xprop_othercase; int Xprop_test_against; int *Xprop_test_variable; #endif int Xctype; int Xfc; int Xfi; int Xlength; int Xmax; int Xmin; int Xnumber; int Xoffset; int Xop; int Xsave_capture_last; int Xsave_offset1, Xsave_offset2, Xsave_offset3; int Xstacksave[REC_STACK_SAVE_MAX]; eptrblock Xnewptrb; /* Place to pass back result, and where to jump back to */ int Xresult; jmp_buf Xwhere; } heapframe; #endif /*************************************************************************** ***************************************************************************/ /************************************************* * Match from current position * *************************************************/ /* On entry ecode points to the first opcode, and eptr to the first character in the subject string, while eptrb holds the value of eptr at the start of the last bracketed group - used for breaking infinite loops matching zero-length strings. This function is called recursively in many circumstances. Whenever it returns a negative (error) response, the outer incarnation must also return the same response. Performance note: It might be tempting to extract commonly used fields from the md structure (e.g. utf8, end_subject) into individual variables to improve performance. Tests using gcc on a SPARC disproved this; in the first case, it made performance worse. Arguments: eptr pointer in subject ecode position in code offset_top current top pointer md pointer to "static" info for the match ims current /i, /m, and /s options eptrb pointer to chain of blocks containing eptr at start of brackets - for testing for empty matches flags can contain match_condassert - this is an assertion condition match_isgroup - this is the start of a bracketed group Returns: MATCH_MATCH if matched ) these values are >= 0 MATCH_NOMATCH if failed to match ) a negative PCRE_ERROR_xxx value if aborted by an error condition (e.g. stopped by recursion limit) */ static int match(REGISTER const uschar *eptr, REGISTER const uschar *ecode, int offset_top, match_data *md, unsigned long int ims, eptrblock *eptrb, int flags) { /* These variables do not need to be preserved over recursion in this function, so they can be ordinary variables in all cases. Mark them with "register" because they are used a lot in loops. */ register int rrc; /* Returns from recursive calls */ register int i; /* Used for loops not involving calls to RMATCH() */ register int c; /* Character values not kept over RMATCH() calls */ /* When recursion is not being used, all "local" variables that have to be preserved over calls to RMATCH() are part of a "frame" which is obtained from heap storage. Set up the top-level frame here; others are obtained from the heap whenever RMATCH() does a "recursion". See the macro definitions above. */ #ifdef NO_RECURSE heapframe *frame = (pcre_stack_malloc)(sizeof(heapframe)); frame->Xprevframe = NULL; /* Marks the top level */ /* Copy in the original argument variables */ frame->Xeptr = eptr; frame->Xecode = ecode; frame->Xoffset_top = offset_top; frame->Xims = ims; frame->Xeptrb = eptrb; frame->Xflags = flags; /* This is where control jumps back to to effect "recursion" */ HEAP_RECURSE: /* Macros make the argument variables come from the current frame */ #define eptr frame->Xeptr #define ecode frame->Xecode #define offset_top frame->Xoffset_top #define ims frame->Xims #define eptrb frame->Xeptrb #define flags frame->Xflags /* Ditto for the local variables */ #ifdef SUPPORT_UTF8 #define charptr frame->Xcharptr #endif #define callpat frame->Xcallpat #define data frame->Xdata #define next frame->Xnext #define pp frame->Xpp #define prev frame->Xprev #define saved_eptr frame->Xsaved_eptr #define new_recursive frame->Xnew_recursive #define cur_is_word frame->Xcur_is_word #define condition frame->Xcondition #define minimize frame->Xminimize #define prev_is_word frame->Xprev_is_word #define original_ims frame->Xoriginal_ims #ifdef SUPPORT_UCP #define prop_type frame->Xprop_type #define prop_fail_result frame->Xprop_fail_result #define prop_category frame->Xprop_category #define prop_chartype frame->Xprop_chartype #define prop_othercase frame->Xprop_othercase #define prop_test_against frame->Xprop_test_against #define prop_test_variable frame->Xprop_test_variable #endif #define ctype frame->Xctype #define fc frame->Xfc #define fi frame->Xfi #define length frame->Xlength #define max frame->Xmax #define min frame->Xmin #define number frame->Xnumber #define offset frame->Xoffset #define op frame->Xop #define save_capture_last frame->Xsave_capture_last #define save_offset1 frame->Xsave_offset1 #define save_offset2 frame->Xsave_offset2 #define save_offset3 frame->Xsave_offset3 #define stacksave frame->Xstacksave #define newptrb frame->Xnewptrb /* When recursion is being used, local variables are allocated on the stack and get preserved during recursion in the normal way. In this environment, fi and i, and fc and c, can be the same variables. */ #else #define fi i #define fc c #ifdef SUPPORT_UTF8 /* Many of these variables are used ony */ const uschar *charptr; /* small blocks of the code. My normal */ #endif /* style of coding would have declared */ const uschar *callpat; /* them within each of those blocks. */ const uschar *data; /* However, in order to accommodate the */ const uschar *next; /* version of this code that uses an */ const uschar *pp; /* external "stack" implemented on the */ const uschar *prev; /* heap, it is easier to declare them */ const uschar *saved_eptr; /* all here, so the declarations can */ /* be cut out in a block. The only */ recursion_info new_recursive; /* declarations within blocks below are */ /* for variables that do not have to */ BOOL cur_is_word; /* be preserved over a recursive call */ BOOL condition; /* to RMATCH(). */ BOOL minimize; BOOL prev_is_word; unsigned long int original_ims; #ifdef SUPPORT_UCP int prop_type; int prop_fail_result; int prop_category; int prop_chartype; int prop_othercase; int prop_test_against; int *prop_test_variable; #endif int ctype; int length; int max; int min; int number; int offset; int op; int save_capture_last; int save_offset1, save_offset2, save_offset3; int stacksave[REC_STACK_SAVE_MAX]; eptrblock newptrb; #endif /* These statements are here to stop the compiler complaining about unitialized variables. */ #ifdef SUPPORT_UCP prop_fail_result = 0; prop_test_against = 0; prop_test_variable = NULL; #endif /* OK, now we can get on with the real code of the function. Recursion is specified by the macros RMATCH and RRETURN. When NO_RECURSE is *not* defined, these just turn into a recursive call to match() and a "return", respectively. However, RMATCH isn't like a function call because it's quite a complicated macro. It has to be used in one particular way. This shouldn't, however, impact performance when true recursion is being used. */ if (md->match_call_count++ >= md->match_limit) RRETURN(PCRE_ERROR_MATCHLIMIT); original_ims = ims; /* Save for resetting on ')' */ /* At the start of a bracketed group, add the current subject pointer to the stack of such pointers, to be re-instated at the end of the group when we hit the closing ket. When match() is called in other circumstances, we don't add to this stack. */ if ((flags & match_isgroup) != 0) { newptrb.epb_prev = eptrb; newptrb.epb_saved_eptr = eptr; eptrb = &newptrb; } /* Now start processing the operations. */ for (;;) { op = *ecode; minimize = FALSE; /* For partial matching, remember if we ever hit the end of the subject after matching at least one subject character. */ if (md->partial && eptr >= md->end_subject && eptr > md->start_match) md->hitend = TRUE; /* Opening capturing bracket. If there is space in the offset vector, save the current subject position in the working slot at the top of the vector. We mustn't change the current values of the data slot, because they may be set from a previous iteration of this group, and be referred to by a reference inside the group. If the bracket fails to match, we need to restore this value and also the values of the final offsets, in case they were set by a previous iteration of the same bracket. If there isn't enough space in the offset vector, treat this as if it were a non-capturing bracket. Don't worry about setting the flag for the error case here; that is handled in the code for KET. */ if (op > OP_BRA) { number = op - OP_BRA; /* For extended extraction brackets (large number), we have to fish out the number from a dummy opcode at the start. */ if (number > EXTRACT_BASIC_MAX) number = GET2(ecode, 2+LINK_SIZE); offset = number << 1; #ifdef DEBUG printf("start bracket %d subject=", number); pchars(eptr, 16, TRUE, md); printf("\n"); #endif if (offset < md->offset_max) { save_offset1 = md->offset_vector[offset]; save_offset2 = md->offset_vector[offset+1]; save_offset3 = md->offset_vector[md->offset_end - number]; save_capture_last = md->capture_last; DPRINTF(("saving %d %d %d\n", save_offset1, save_offset2, save_offset3)); md->offset_vector[md->offset_end - number] = eptr - md->start_subject; do { RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb, match_isgroup); if (rrc != MATCH_NOMATCH) RRETURN(rrc); md->capture_last = save_capture_last; ecode += GET(ecode, 1); } while (*ecode == OP_ALT); DPRINTF(("bracket %d failed\n", number)); md->offset_vector[offset] = save_offset1; md->offset_vector[offset+1] = save_offset2; md->offset_vector[md->offset_end - number] = save_offset3; RRETURN(MATCH_NOMATCH); } /* Insufficient room for saving captured contents */ else op = OP_BRA; } /* Other types of node can be handled by a switch */ switch(op) { case OP_BRA: /* Non-capturing bracket: optimized */ DPRINTF(("start bracket 0\n")); do { RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb, match_isgroup); if (rrc != MATCH_NOMATCH) RRETURN(rrc); ecode += GET(ecode, 1); } while (*ecode == OP_ALT); DPRINTF(("bracket 0 failed\n")); RRETURN(MATCH_NOMATCH); /* Conditional group: compilation checked that there are no more than two branches. If the condition is false, skipping the first branch takes us past the end if there is only one branch, but that's OK because that is exactly what going to the ket would do. */ case OP_COND: if (ecode[LINK_SIZE+1] == OP_CREF) /* Condition extract or recurse test */ { offset = GET2(ecode, LINK_SIZE+2) << 1; /* Doubled ref number */ condition = (offset == CREF_RECURSE * 2)? (md->recursive != NULL) : (offset < offset_top && md->offset_vector[offset] >= 0); RMATCH(rrc, eptr, ecode + (condition? (LINK_SIZE + 4) : (LINK_SIZE + 1 + GET(ecode, 1))), offset_top, md, ims, eptrb, match_isgroup); RRETURN(rrc); } /* The condition is an assertion. Call match() to evaluate it - setting the final argument TRUE causes it to stop at the end of an assertion. */ else { RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, NULL, match_condassert | match_isgroup); if (rrc == MATCH_MATCH) { ecode += 1 + LINK_SIZE + GET(ecode, LINK_SIZE+2); while (*ecode == OP_ALT) ecode += GET(ecode, 1); } else if (rrc != MATCH_NOMATCH) { RRETURN(rrc); /* Need braces because of following else */ } else ecode += GET(ecode, 1); RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb, match_isgroup); RRETURN(rrc); } /* Control never reaches here */ /* Skip over conditional reference or large extraction number data if encountered. */ case OP_CREF: case OP_BRANUMBER: ecode += 3; break; /* End of the pattern. If we are in a recursion, we should restore the offsets appropriately and continue from after the call. */ case OP_END: if (md->recursive != NULL && md->recursive->group_num == 0) { recursion_info *rec = md->recursive; DPRINTF(("Hit the end in a (?0) recursion\n")); md->recursive = rec->prevrec; memmove(md->offset_vector, rec->offset_save, rec->saved_max * sizeof(int)); md->start_match = rec->save_start; ims = original_ims; ecode = rec->after_call; break; } /* Otherwise, if PCRE_NOTEMPTY is set, fail if we have matched an empty string - backtracking will then try other alternatives, if any. */ if (md->notempty && eptr == md->start_match) RRETURN(MATCH_NOMATCH); md->end_match_ptr = eptr; /* Record where we ended */ md->end_offset_top = offset_top; /* and how many extracts were taken */ RRETURN(MATCH_MATCH); /* Change option settings */ case OP_OPT: ims = ecode[1]; ecode += 2; DPRINTF(("ims set to %02lx\n", ims)); break; /* Assertion brackets. Check the alternative branches in turn - the matching won't pass the KET for an assertion. If any one branch matches, the assertion is true. Lookbehind assertions have an OP_REVERSE item at the start of each branch to move the current point backwards, so the code at this level is identical to the lookahead case. */ case OP_ASSERT: case OP_ASSERTBACK: do { RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, NULL, match_isgroup); if (rrc == MATCH_MATCH) break; if (rrc != MATCH_NOMATCH) RRETURN(rrc); ecode += GET(ecode, 1); } while (*ecode == OP_ALT); if (*ecode == OP_KET) RRETURN(MATCH_NOMATCH); /* If checking an assertion for a condition, return MATCH_MATCH. */ if ((flags & match_condassert) != 0) RRETURN(MATCH_MATCH); /* Continue from after the assertion, updating the offsets high water mark, since extracts may have been taken during the assertion. */ do ecode += GET(ecode,1); while (*ecode == OP_ALT); ecode += 1 + LINK_SIZE; offset_top = md->end_offset_top; continue; /* Negative assertion: all branches must fail to match */ case OP_ASSERT_NOT: case OP_ASSERTBACK_NOT: do { RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, NULL, match_isgroup); if (rrc == MATCH_MATCH) RRETURN(MATCH_NOMATCH); if (rrc != MATCH_NOMATCH) RRETURN(rrc); ecode += GET(ecode,1); } while (*ecode == OP_ALT); if ((flags & match_condassert) != 0) RRETURN(MATCH_MATCH); ecode += 1 + LINK_SIZE; continue; /* Move the subject pointer back. This occurs only at the start of each branch of a lookbehind assertion. If we are too close to the start to move back, this match function fails. When working with UTF-8 we move back a number of characters, not bytes. */ case OP_REVERSE: #ifdef SUPPORT_UTF8 if (md->utf8) { c = GET(ecode,1); for (i = 0; i < c; i++) { eptr--; if (eptr < md->start_subject) RRETURN(MATCH_NOMATCH); BACKCHAR(eptr) } } else #endif /* No UTF-8 support, or not in UTF-8 mode: count is byte count */ { eptr -= GET(ecode,1); if (eptr < md->start_subject) RRETURN(MATCH_NOMATCH); } /* Skip to next op code */ ecode += 1 + LINK_SIZE; break; /* The callout item calls an external function, if one is provided, passing details of the match so far. This is mainly for debugging, though the function is able to force a failure. */ case OP_CALLOUT: if (pcre_callout != NULL) { pcre_callout_block cb; cb.version = 1; /* Version 1 of the callout block */ cb.callout_number = ecode[1]; cb.offset_vector = md->offset_vector; cb.subject = (const char *)md->start_subject; cb.subject_length = md->end_subject - md->start_subject; cb.start_match = md->start_match - md->start_subject; cb.current_position = eptr - md->start_subject; cb.pattern_position = GET(ecode, 2); cb.next_item_length = GET(ecode, 2 + LINK_SIZE); cb.capture_top = offset_top/2; cb.capture_last = md->capture_last; cb.callout_data = md->callout_data; if ((rrc = (*pcre_callout)(&cb)) > 0) RRETURN(MATCH_NOMATCH); if (rrc < 0) RRETURN(rrc); } ecode += 2 + 2*LINK_SIZE; break; /* Recursion either matches the current regex, or some subexpression. The offset data is the offset to the starting bracket from the start of the whole pattern. (This is so that it works from duplicated subpatterns.) If there are any capturing brackets started but not finished, we have to save their starting points and reinstate them after the recursion. However, we don't know how many such there are (offset_top records the completed total) so we just have to save all the potential data. There may be up to 65535 such values, which is too large to put on the stack, but using malloc for small numbers seems expensive. As a compromise, the stack is used when there are no more than REC_STACK_SAVE_MAX values to store; otherwise malloc is used. A problem is what to do if the malloc fails ... there is no way of returning to the top level with an error. Save the top REC_STACK_SAVE_MAX values on the stack, and accept that the rest may be wrong. There are also other values that have to be saved. We use a chained sequence of blocks that actually live on the stack. Thanks to Robin Houston for the original version of this logic. */ case OP_RECURSE: { callpat = md->start_code + GET(ecode, 1); new_recursive.group_num = *callpat - OP_BRA; /* For extended extraction brackets (large number), we have to fish out the number from a dummy opcode at the start. */ if (new_recursive.group_num > EXTRACT_BASIC_MAX) new_recursive.group_num = GET2(callpat, 2+LINK_SIZE); /* Add to "recursing stack" */ new_recursive.prevrec = md->recursive; md->recursive = &new_recursive; /* Find where to continue from afterwards */ ecode += 1 + LINK_SIZE; new_recursive.after_call = ecode; /* Now save the offset data. */ new_recursive.saved_max = md->offset_end; if (new_recursive.saved_max <= REC_STACK_SAVE_MAX) new_recursive.offset_save = stacksave; else { new_recursive.offset_save = (int *)(pcre_malloc)(new_recursive.saved_max * sizeof(int)); if (new_recursive.offset_save == NULL) RRETURN(PCRE_ERROR_NOMEMORY); } memcpy(new_recursive.offset_save, md->offset_vector, new_recursive.saved_max * sizeof(int)); new_recursive.save_start = md->start_match; md->start_match = eptr; /* OK, now we can do the recursion. For each top-level alternative we restore the offset and recursion data. */ DPRINTF(("Recursing into group %d\n", new_recursive.group_num)); do { RMATCH(rrc, eptr, callpat + 1 + LINK_SIZE, offset_top, md, ims, eptrb, match_isgroup); if (rrc == MATCH_MATCH) { md->recursive = new_recursive.prevrec; if (new_recursive.offset_save != stacksave) (pcre_free)(new_recursive.offset_save); RRETURN(MATCH_MATCH); } else if (rrc != MATCH_NOMATCH) RRETURN(rrc); md->recursive = &new_recursive; memcpy(md->offset_vector, new_recursive.offset_save, new_recursive.saved_max * sizeof(int)); callpat += GET(callpat, 1); } while (*callpat == OP_ALT); DPRINTF(("Recursion didn't match\n")); md->recursive = new_recursive.prevrec; if (new_recursive.offset_save != stacksave) (pcre_free)(new_recursive.offset_save); RRETURN(MATCH_NOMATCH); } /* Control never reaches here */ /* "Once" brackets are like assertion brackets except that after a match, the point in the subject string is not moved back. Thus there can never be a move back into the brackets. Friedl calls these "atomic" subpatterns. Check the alternative branches in turn - the matching won't pass the KET for this kind of subpattern. If any one branch matches, we carry on as at the end of a normal bracket, leaving the subject pointer. */ case OP_ONCE: { prev = ecode; saved_eptr = eptr; do { RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb, match_isgroup); if (rrc == MATCH_MATCH) break; if (rrc != MATCH_NOMATCH) RRETURN(rrc); ecode += GET(ecode,1); } while (*ecode == OP_ALT); /* If hit the end of the group (which could be repeated), fail */ if (*ecode != OP_ONCE && *ecode != OP_ALT) RRETURN(MATCH_NOMATCH); /* Continue as from after the assertion, updating the offsets high water mark, since extracts may have been taken. */ do ecode += GET(ecode,1); while (*ecode == OP_ALT); offset_top = md->end_offset_top; eptr = md->end_match_ptr; /* For a non-repeating ket, just continue at this level. This also happens for a repeating ket if no characters were matched in the group. This is the forcible breaking of infinite loops as implemented in Perl 5.005. If there is an options reset, it will get obeyed in the normal course of events. */ if (*ecode == OP_KET || eptr == saved_eptr) { ecode += 1+LINK_SIZE; break; } /* The repeating kets try the rest of the pattern or restart from the preceding bracket, in the appropriate order. We need to reset any options that changed within the bracket before re-running it, so check the next opcode. */ if (ecode[1+LINK_SIZE] == OP_OPT) { ims = (ims & ~PCRE_IMS) | ecode[4]; DPRINTF(("ims set to %02lx at group repeat\n", ims)); } if (*ecode == OP_KETRMIN) { RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); RMATCH(rrc, eptr, prev, offset_top, md, ims, eptrb, match_isgroup); if (rrc != MATCH_NOMATCH) RRETURN(rrc); } else /* OP_KETRMAX */ { RMATCH(rrc, eptr, prev, offset_top, md, ims, eptrb, match_isgroup); if (rrc != MATCH_NOMATCH) RRETURN(rrc); RMATCH(rrc, eptr, ecode + 1+LINK_SIZE, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); } } RRETURN(MATCH_NOMATCH); /* An alternation is the end of a branch; scan along to find the end of the bracketed group and go to there. */ case OP_ALT: do ecode += GET(ecode,1); while (*ecode == OP_ALT); break; /* BRAZERO and BRAMINZERO occur just before a bracket group, indicating that it may occur zero times. It may repeat infinitely, or not at all - i.e. it could be ()* or ()? in the pattern. Brackets with fixed upper repeat limits are compiled as a number of copies, with the optional ones preceded by BRAZERO or BRAMINZERO. */ case OP_BRAZERO: { next = ecode+1; RMATCH(rrc, eptr, next, offset_top, md, ims, eptrb, match_isgroup); if (rrc != MATCH_NOMATCH) RRETURN(rrc); do next += GET(next,1); while (*next == OP_ALT); ecode = next + 1+LINK_SIZE; } break; case OP_BRAMINZERO: { next = ecode+1; do next += GET(next,1); while (*next == OP_ALT); RMATCH(rrc, eptr, next + 1+LINK_SIZE, offset_top, md, ims, eptrb, match_isgroup); if (rrc != MATCH_NOMATCH) RRETURN(rrc); ecode++; } break; /* End of a group, repeated or non-repeating. If we are at the end of an assertion "group", stop matching and return MATCH_MATCH, but record the current high water mark for use by positive assertions. Do this also for the "once" (not-backup up) groups. */ case OP_KET: case OP_KETRMIN: case OP_KETRMAX: { prev = ecode - GET(ecode, 1); saved_eptr = eptrb->epb_saved_eptr; /* Back up the stack of bracket start pointers. */ eptrb = eptrb->epb_prev; if (*prev == OP_ASSERT || *prev == OP_ASSERT_NOT || *prev == OP_ASSERTBACK || *prev == OP_ASSERTBACK_NOT || *prev == OP_ONCE) { md->end_match_ptr = eptr; /* For ONCE */ md->end_offset_top = offset_top; RRETURN(MATCH_MATCH); } /* In all other cases except a conditional group we have to check the group number back at the start and if necessary complete handling an extraction by setting the offsets and bumping the high water mark. */ if (*prev != OP_COND) { number = *prev - OP_BRA; /* For extended extraction brackets (large number), we have to fish out the number from a dummy opcode at the start. */ if (number > EXTRACT_BASIC_MAX) number = GET2(prev, 2+LINK_SIZE); offset = number << 1; #ifdef DEBUG printf("end bracket %d", number); printf("\n"); #endif /* Test for a numbered group. This includes groups called as a result of recursion. Note that whole-pattern recursion is coded as a recurse into group 0, so it won't be picked up here. Instead, we catch it when the OP_END is reached. */ if (number > 0) { md->capture_last = number; if (offset >= md->offset_max) md->offset_overflow = TRUE; else { md->offset_vector[offset] = md->offset_vector[md->offset_end - number]; md->offset_vector[offset+1] = eptr - md->start_subject; if (offset_top <= offset) offset_top = offset + 2; } /* Handle a recursively called group. Restore the offsets appropriately and continue from after the call. */ if (md->recursive != NULL && md->recursive->group_num == number) { recursion_info *rec = md->recursive; DPRINTF(("Recursion (%d) succeeded - continuing\n", number)); md->recursive = rec->prevrec; md->start_match = rec->save_start; memcpy(md->offset_vector, rec->offset_save, rec->saved_max * sizeof(int)); ecode = rec->after_call; ims = original_ims; break; } } } /* Reset the value of the ims flags, in case they got changed during the group. */ ims = original_ims; DPRINTF(("ims reset to %02lx\n", ims)); /* For a non-repeating ket, just continue at this level. This also happens for a repeating ket if no characters were matched in the group. This is the forcible breaking of infinite loops as implemented in Perl 5.005. If there is an options reset, it will get obeyed in the normal course of events. */ if (*ecode == OP_KET || eptr == saved_eptr) { ecode += 1 + LINK_SIZE; break; } /* The repeating kets try the rest of the pattern or restart from the preceding bracket, in the appropriate order. */ if (*ecode == OP_KETRMIN) { RMATCH(rrc, eptr, ecode + 1+LINK_SIZE, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); RMATCH(rrc, eptr, prev, offset_top, md, ims, eptrb, match_isgroup); if (rrc != MATCH_NOMATCH) RRETURN(rrc); } else /* OP_KETRMAX */ { RMATCH(rrc, eptr, prev, offset_top, md, ims, eptrb, match_isgroup); if (rrc != MATCH_NOMATCH) RRETURN(rrc); RMATCH(rrc, eptr, ecode + 1+LINK_SIZE, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); } } RRETURN(MATCH_NOMATCH); /* Start of subject unless notbol, or after internal newline if multiline */ case OP_CIRC: if (md->notbol && eptr == md->start_subject) RRETURN(MATCH_NOMATCH); if ((ims & PCRE_MULTILINE) != 0) { if (eptr != md->start_subject && eptr[-1] != NEWLINE) RRETURN(MATCH_NOMATCH); ecode++; break; } /* ... else fall through */ /* Start of subject assertion */ case OP_SOD: if (eptr != md->start_subject) RRETURN(MATCH_NOMATCH); ecode++; break; /* Start of match assertion */ case OP_SOM: if (eptr != md->start_subject + md->start_offset) RRETURN(MATCH_NOMATCH); ecode++; break; /* Assert before internal newline if multiline, or before a terminating newline unless endonly is set, else end of subject unless noteol is set. */ case OP_DOLL: if ((ims & PCRE_MULTILINE) != 0) { if (eptr < md->end_subject) { if (*eptr != NEWLINE) RRETURN(MATCH_NOMATCH); } else { if (md->noteol) RRETURN(MATCH_NOMATCH); } ecode++; break; } else { if (md->noteol) RRETURN(MATCH_NOMATCH); if (!md->endonly) { if (eptr < md->end_subject - 1 || (eptr == md->end_subject - 1 && *eptr != NEWLINE)) RRETURN(MATCH_NOMATCH); ecode++; break; } } /* ... else fall through */ /* End of subject assertion (\z) */ case OP_EOD: if (eptr < md->end_subject) RRETURN(MATCH_NOMATCH); ecode++; break; /* End of subject or ending \n assertion (\Z) */ case OP_EODN: if (eptr < md->end_subject - 1 || (eptr == md->end_subject - 1 && *eptr != NEWLINE)) RRETURN(MATCH_NOMATCH); ecode++; break; /* Word boundary assertions */ case OP_NOT_WORD_BOUNDARY: case OP_WORD_BOUNDARY: { /* Find out if the previous and current characters are "word" characters. It takes a bit more work in UTF-8 mode. Characters > 255 are assumed to be "non-word" characters. */ #ifdef SUPPORT_UTF8 if (md->utf8) { if (eptr == md->start_subject) prev_is_word = FALSE; else { const uschar *lastptr = eptr - 1; while((*lastptr & 0xc0) == 0x80) lastptr--; GETCHAR(c, lastptr); prev_is_word = c < 256 && (md->ctypes[c] & ctype_word) != 0; } if (eptr >= md->end_subject) cur_is_word = FALSE; else { GETCHAR(c, eptr); cur_is_word = c < 256 && (md->ctypes[c] & ctype_word) != 0; } } else #endif /* More streamlined when not in UTF-8 mode */ { prev_is_word = (eptr != md->start_subject) && ((md->ctypes[eptr[-1]] & ctype_word) != 0); cur_is_word = (eptr < md->end_subject) && ((md->ctypes[*eptr] & ctype_word) != 0); } /* Now see if the situation is what we want */ if ((*ecode++ == OP_WORD_BOUNDARY)? cur_is_word == prev_is_word : cur_is_word != prev_is_word) RRETURN(MATCH_NOMATCH); } break; /* Match a single character type; inline for speed */ case OP_ANY: if ((ims & PCRE_DOTALL) == 0 && eptr < md->end_subject && *eptr == NEWLINE) RRETURN(MATCH_NOMATCH); if (eptr++ >= md->end_subject) RRETURN(MATCH_NOMATCH); #ifdef SUPPORT_UTF8 if (md->utf8) while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; #endif ecode++; break; /* Match a single byte, even in UTF-8 mode. This opcode really does match any byte, even newline, independent of the setting of PCRE_DOTALL. */ case OP_ANYBYTE: if (eptr++ >= md->end_subject) RRETURN(MATCH_NOMATCH); ecode++; break; case OP_NOT_DIGIT: if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINCTEST(c, eptr); if ( #ifdef SUPPORT_UTF8 c < 256 && #endif (md->ctypes[c] & ctype_digit) != 0 ) RRETURN(MATCH_NOMATCH); ecode++; break; case OP_DIGIT: if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINCTEST(c, eptr); if ( #ifdef SUPPORT_UTF8 c >= 256 || #endif (md->ctypes[c] & ctype_digit) == 0 ) RRETURN(MATCH_NOMATCH); ecode++; break; case OP_NOT_WHITESPACE: if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINCTEST(c, eptr); if ( #ifdef SUPPORT_UTF8 c < 256 && #endif (md->ctypes[c] & ctype_space) != 0 ) RRETURN(MATCH_NOMATCH); ecode++; break; case OP_WHITESPACE: if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINCTEST(c, eptr); if ( #ifdef SUPPORT_UTF8 c >= 256 || #endif (md->ctypes[c] & ctype_space) == 0 ) RRETURN(MATCH_NOMATCH); ecode++; break; case OP_NOT_WORDCHAR: if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINCTEST(c, eptr); if ( #ifdef SUPPORT_UTF8 c < 256 && #endif (md->ctypes[c] & ctype_word) != 0 ) RRETURN(MATCH_NOMATCH); ecode++; break; case OP_WORDCHAR: if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINCTEST(c, eptr); if ( #ifdef SUPPORT_UTF8 c >= 256 || #endif (md->ctypes[c] & ctype_word) == 0 ) RRETURN(MATCH_NOMATCH); ecode++; break; #ifdef SUPPORT_UCP /* Check the next character by Unicode property. We will get here only if the support is in the binary; otherwise a compile-time error occurs. */ case OP_PROP: case OP_NOTPROP: if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINCTEST(c, eptr); { int chartype, rqdtype; int othercase; int category = ucp_findchar(c, &chartype, &othercase); rqdtype = *(++ecode); ecode++; if (rqdtype >= 128) { if ((rqdtype - 128 != category) == (op == OP_PROP)) RRETURN(MATCH_NOMATCH); } else { if ((rqdtype != chartype) == (op == OP_PROP)) RRETURN(MATCH_NOMATCH); } } break; /* Match an extended Unicode sequence. We will get here only if the support is in the binary; otherwise a compile-time error occurs. */ case OP_EXTUNI: if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINCTEST(c, eptr); { int chartype; int othercase; int category = ucp_findchar(c, &chartype, &othercase); if (category == ucp_M) RRETURN(MATCH_NOMATCH); while (eptr < md->end_subject) { int len = 1; if (!md->utf8) c = *eptr; else { GETCHARLEN(c, eptr, len); } category = ucp_findchar(c, &chartype, &othercase); if (category != ucp_M) break; eptr += len; } } ecode++; break; #endif /* Match a back reference, possibly repeatedly. Look past the end of the item to see if there is repeat information following. The code is similar to that for character classes, but repeated for efficiency. Then obey similar code to character type repeats - written out again for speed. However, if the referenced string is the empty string, always treat it as matched, any number of times (otherwise there could be infinite loops). */ case OP_REF: { offset = GET2(ecode, 1) << 1; /* Doubled ref number */ ecode += 3; /* Advance past item */ /* If the reference is unset, set the length to be longer than the amount of subject left; this ensures that every attempt at a match fails. We can't just fail here, because of the possibility of quantifiers with zero minima. */ length = (offset >= offset_top || md->offset_vector[offset] < 0)? md->end_subject - eptr + 1 : md->offset_vector[offset+1] - md->offset_vector[offset]; /* Set up for repetition, or handle the non-repeated case */ switch (*ecode) { case OP_CRSTAR: case OP_CRMINSTAR: case OP_CRPLUS: case OP_CRMINPLUS: case OP_CRQUERY: case OP_CRMINQUERY: c = *ecode++ - OP_CRSTAR; minimize = (c & 1) != 0; min = rep_min[c]; /* Pick up values from tables; */ max = rep_max[c]; /* zero for max => infinity */ if (max == 0) max = INT_MAX; break; case OP_CRRANGE: case OP_CRMINRANGE: minimize = (*ecode == OP_CRMINRANGE); min = GET2(ecode, 1); max = GET2(ecode, 3); if (max == 0) max = INT_MAX; ecode += 5; break; default: /* No repeat follows */ if (!match_ref(offset, eptr, length, md, ims)) RRETURN(MATCH_NOMATCH); eptr += length; continue; /* With the main loop */ } /* If the length of the reference is zero, just continue with the main loop. */ if (length == 0) continue; /* First, ensure the minimum number of matches are present. We get back the length of the reference string explicitly rather than passing the address of eptr, so that eptr can be a register variable. */ for (i = 1; i <= min; i++) { if (!match_ref(offset, eptr, length, md, ims)) RRETURN(MATCH_NOMATCH); eptr += length; } /* If min = max, continue at the same level without recursion. They are not both allowed to be zero. */ if (min == max) continue; /* If minimizing, keep trying and advancing the pointer */ if (minimize) { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || !match_ref(offset, eptr, length, md, ims)) RRETURN(MATCH_NOMATCH); eptr += length; } /* Control never gets here */ } /* If maximizing, find the longest string and work backwards */ else { pp = eptr; for (i = min; i < max; i++) { if (!match_ref(offset, eptr, length, md, ims)) break; eptr += length; } while (eptr >= pp) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); eptr -= length; } RRETURN(MATCH_NOMATCH); } } /* Control never gets here */ /* Match a bit-mapped character class, possibly repeatedly. This op code is used when all the characters in the class have values in the range 0-255, and either the matching is caseful, or the characters are in the range 0-127 when UTF-8 processing is enabled. The only difference between OP_CLASS and OP_NCLASS occurs when a data character outside the range is encountered. First, look past the end of the item to see if there is repeat information following. Then obey similar code to character type repeats - written out again for speed. */ case OP_NCLASS: case OP_CLASS: { data = ecode + 1; /* Save for matching */ ecode += 33; /* Advance past the item */ switch (*ecode) { case OP_CRSTAR: case OP_CRMINSTAR: case OP_CRPLUS: case OP_CRMINPLUS: case OP_CRQUERY: case OP_CRMINQUERY: c = *ecode++ - OP_CRSTAR; minimize = (c & 1) != 0; min = rep_min[c]; /* Pick up values from tables; */ max = rep_max[c]; /* zero for max => infinity */ if (max == 0) max = INT_MAX; break; case OP_CRRANGE: case OP_CRMINRANGE: minimize = (*ecode == OP_CRMINRANGE); min = GET2(ecode, 1); max = GET2(ecode, 3); if (max == 0) max = INT_MAX; ecode += 5; break; default: /* No repeat follows */ min = max = 1; break; } /* First, ensure the minimum number of matches are present. */ #ifdef SUPPORT_UTF8 /* UTF-8 mode */ if (md->utf8) { for (i = 1; i <= min; i++) { if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINC(c, eptr); if (c > 255) { if (op == OP_CLASS) RRETURN(MATCH_NOMATCH); } else { if ((data[c/8] & (1 << (c&7))) == 0) RRETURN(MATCH_NOMATCH); } } } else #endif /* Not UTF-8 mode */ { for (i = 1; i <= min; i++) { if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); c = *eptr++; if ((data[c/8] & (1 << (c&7))) == 0) RRETURN(MATCH_NOMATCH); } } /* If max == min we can continue with the main loop without the need to recurse. */ if (min == max) continue; /* If minimizing, keep testing the rest of the expression and advancing the pointer while it matches the class. */ if (minimize) { #ifdef SUPPORT_UTF8 /* UTF-8 mode */ if (md->utf8) { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINC(c, eptr); if (c > 255) { if (op == OP_CLASS) RRETURN(MATCH_NOMATCH); } else { if ((data[c/8] & (1 << (c&7))) == 0) RRETURN(MATCH_NOMATCH); } } } else #endif /* Not UTF-8 mode */ { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); c = *eptr++; if ((data[c/8] & (1 << (c&7))) == 0) RRETURN(MATCH_NOMATCH); } } /* Control never gets here */ } /* If maximizing, find the longest possible run, then work backwards. */ else { pp = eptr; #ifdef SUPPORT_UTF8 /* UTF-8 mode */ if (md->utf8) { for (i = min; i < max; i++) { int len = 1; if (eptr >= md->end_subject) break; GETCHARLEN(c, eptr, len); if (c > 255) { if (op == OP_CLASS) break; } else { if ((data[c/8] & (1 << (c&7))) == 0) break; } eptr += len; } for (;;) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (eptr-- == pp) break; /* Stop if tried at original pos */ BACKCHAR(eptr); } } else #endif /* Not UTF-8 mode */ { for (i = min; i < max; i++) { if (eptr >= md->end_subject) break; c = *eptr; if ((data[c/8] & (1 << (c&7))) == 0) break; eptr++; } while (eptr >= pp) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); eptr--; if (rrc != MATCH_NOMATCH) RRETURN(rrc); } } RRETURN(MATCH_NOMATCH); } } /* Control never gets here */ /* Match an extended character class. This opcode is encountered only in UTF-8 mode, because that's the only time it is compiled. */ #ifdef SUPPORT_UTF8 case OP_XCLASS: { data = ecode + 1 + LINK_SIZE; /* Save for matching */ ecode += GET(ecode, 1); /* Advance past the item */ switch (*ecode) { case OP_CRSTAR: case OP_CRMINSTAR: case OP_CRPLUS: case OP_CRMINPLUS: case OP_CRQUERY: case OP_CRMINQUERY: c = *ecode++ - OP_CRSTAR; minimize = (c & 1) != 0; min = rep_min[c]; /* Pick up values from tables; */ max = rep_max[c]; /* zero for max => infinity */ if (max == 0) max = INT_MAX; break; case OP_CRRANGE: case OP_CRMINRANGE: minimize = (*ecode == OP_CRMINRANGE); min = GET2(ecode, 1); max = GET2(ecode, 3); if (max == 0) max = INT_MAX; ecode += 5; break; default: /* No repeat follows */ min = max = 1; break; } /* First, ensure the minimum number of matches are present. */ for (i = 1; i <= min; i++) { if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINC(c, eptr); if (!match_xclass(c, data)) RRETURN(MATCH_NOMATCH); } /* If max == min we can continue with the main loop without the need to recurse. */ if (min == max) continue; /* If minimizing, keep testing the rest of the expression and advancing the pointer while it matches the class. */ if (minimize) { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINC(c, eptr); if (!match_xclass(c, data)) RRETURN(MATCH_NOMATCH); } /* Control never gets here */ } /* If maximizing, find the longest possible run, then work backwards. */ else { pp = eptr; for (i = min; i < max; i++) { int len = 1; if (eptr >= md->end_subject) break; GETCHARLEN(c, eptr, len); if (!match_xclass(c, data)) break; eptr += len; } for(;;) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (eptr-- == pp) break; /* Stop if tried at original pos */ BACKCHAR(eptr) } RRETURN(MATCH_NOMATCH); } /* Control never gets here */ } #endif /* End of XCLASS */ /* Match a single character, casefully */ case OP_CHAR: #ifdef SUPPORT_UTF8 if (md->utf8) { length = 1; ecode++; GETCHARLEN(fc, ecode, length); if (length > md->end_subject - eptr) RRETURN(MATCH_NOMATCH); while (length-- > 0) if (*ecode++ != *eptr++) RRETURN(MATCH_NOMATCH); } else #endif /* Non-UTF-8 mode */ { if (md->end_subject - eptr < 1) RRETURN(MATCH_NOMATCH); if (ecode[1] != *eptr++) RRETURN(MATCH_NOMATCH); ecode += 2; } break; /* Match a single character, caselessly */ case OP_CHARNC: #ifdef SUPPORT_UTF8 if (md->utf8) { length = 1; ecode++; GETCHARLEN(fc, ecode, length); if (length > md->end_subject - eptr) RRETURN(MATCH_NOMATCH); /* If the pattern character's value is < 128, we have only one byte, and can use the fast lookup table. */ if (fc < 128) { if (md->lcc[*ecode++] != md->lcc[*eptr++]) RRETURN(MATCH_NOMATCH); } /* Otherwise we must pick up the subject character */ else { int dc; GETCHARINC(dc, eptr); ecode += length; /* If we have Unicode property support, we can use it to test the other case of the character, if there is one. The result of ucp_findchar() is < 0 if the char isn't found, and othercase is returned as zero if there isn't one. */ if (fc != dc) { #ifdef SUPPORT_UCP int chartype; int othercase; if (ucp_findchar(fc, &chartype, &othercase) < 0 || dc != othercase) #endif RRETURN(MATCH_NOMATCH); } } } else #endif /* SUPPORT_UTF8 */ /* Non-UTF-8 mode */ { if (md->end_subject - eptr < 1) RRETURN(MATCH_NOMATCH); if (md->lcc[ecode[1]] != md->lcc[*eptr++]) RRETURN(MATCH_NOMATCH); ecode += 2; } break; /* Match a single character repeatedly; different opcodes share code. */ case OP_EXACT: min = max = GET2(ecode, 1); ecode += 3; goto REPEATCHAR; case OP_UPTO: case OP_MINUPTO: min = 0; max = GET2(ecode, 1); minimize = *ecode == OP_MINUPTO; ecode += 3; goto REPEATCHAR; case OP_STAR: case OP_MINSTAR: case OP_PLUS: case OP_MINPLUS: case OP_QUERY: case OP_MINQUERY: c = *ecode++ - OP_STAR; minimize = (c & 1) != 0; min = rep_min[c]; /* Pick up values from tables; */ max = rep_max[c]; /* zero for max => infinity */ if (max == 0) max = INT_MAX; /* Common code for all repeated single-character matches. We can give up quickly if there are fewer than the minimum number of characters left in the subject. */ REPEATCHAR: #ifdef SUPPORT_UTF8 if (md->utf8) { length = 1; charptr = ecode; GETCHARLEN(fc, ecode, length); if (min * length > md->end_subject - eptr) RRETURN(MATCH_NOMATCH); ecode += length; /* Handle multibyte character matching specially here. There is support for caseless matching if UCP support is present. */ if (length > 1) { int oclength = 0; uschar occhars[8]; #ifdef SUPPORT_UCP int othercase; int chartype; if ((ims & PCRE_CASELESS) != 0 && ucp_findchar(fc, &chartype, &othercase) >= 0 && othercase > 0) oclength = ord2utf8(othercase, occhars); #endif /* SUPPORT_UCP */ for (i = 1; i <= min; i++) { if (memcmp(eptr, charptr, length) == 0) eptr += length; /* Need braces because of following else */ else if (oclength == 0) { RRETURN(MATCH_NOMATCH); } else { if (memcmp(eptr, occhars, oclength) != 0) RRETURN(MATCH_NOMATCH); eptr += oclength; } } if (min == max) continue; if (minimize) { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); if (memcmp(eptr, charptr, length) == 0) eptr += length; /* Need braces because of following else */ else if (oclength == 0) { RRETURN(MATCH_NOMATCH); } else { if (memcmp(eptr, occhars, oclength) != 0) RRETURN(MATCH_NOMATCH); eptr += oclength; } } /* Control never gets here */ } else { pp = eptr; for (i = min; i < max; i++) { if (eptr > md->end_subject - length) break; if (memcmp(eptr, charptr, length) == 0) eptr += length; else if (oclength == 0) break; else { if (memcmp(eptr, occhars, oclength) != 0) break; eptr += oclength; } } while (eptr >= pp) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); eptr -= length; } RRETURN(MATCH_NOMATCH); } /* Control never gets here */ } /* If the length of a UTF-8 character is 1, we fall through here, and obey the code as for non-UTF-8 characters below, though in this case the value of fc will always be < 128. */ } else #endif /* SUPPORT_UTF8 */ /* When not in UTF-8 mode, load a single-byte character. */ { if (min > md->end_subject - eptr) RRETURN(MATCH_NOMATCH); fc = *ecode++; } /* The value of fc at this point is always less than 256, though we may or may not be in UTF-8 mode. The code is duplicated for the caseless and caseful cases, for speed, since matching characters is likely to be quite common. First, ensure the minimum number of matches are present. If min = max, continue at the same level without recursing. Otherwise, if minimizing, keep trying the rest of the expression and advancing one matching character if failing, up to the maximum. Alternatively, if maximizing, find the maximum number of characters and work backwards. */ DPRINTF(("matching %c{%d,%d} against subject %.*s\n", fc, min, max, max, eptr)); if ((ims & PCRE_CASELESS) != 0) { fc = md->lcc[fc]; for (i = 1; i <= min; i++) if (fc != md->lcc[*eptr++]) RRETURN(MATCH_NOMATCH); if (min == max) continue; if (minimize) { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || eptr >= md->end_subject || fc != md->lcc[*eptr++]) RRETURN(MATCH_NOMATCH); } /* Control never gets here */ } else { pp = eptr; for (i = min; i < max; i++) { if (eptr >= md->end_subject || fc != md->lcc[*eptr]) break; eptr++; } while (eptr >= pp) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); eptr--; if (rrc != MATCH_NOMATCH) RRETURN(rrc); } RRETURN(MATCH_NOMATCH); } /* Control never gets here */ } /* Caseful comparisons (includes all multi-byte characters) */ else { for (i = 1; i <= min; i++) if (fc != *eptr++) RRETURN(MATCH_NOMATCH); if (min == max) continue; if (minimize) { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || eptr >= md->end_subject || fc != *eptr++) RRETURN(MATCH_NOMATCH); } /* Control never gets here */ } else { pp = eptr; for (i = min; i < max; i++) { if (eptr >= md->end_subject || fc != *eptr) break; eptr++; } while (eptr >= pp) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); eptr--; if (rrc != MATCH_NOMATCH) RRETURN(rrc); } RRETURN(MATCH_NOMATCH); } } /* Control never gets here */ /* Match a negated single one-byte character. The character we are checking can be multibyte. */ case OP_NOT: if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); ecode++; GETCHARINCTEST(c, eptr); if ((ims & PCRE_CASELESS) != 0) { #ifdef SUPPORT_UTF8 if (c < 256) #endif c = md->lcc[c]; if (md->lcc[*ecode++] == c) RRETURN(MATCH_NOMATCH); } else { if (*ecode++ == c) RRETURN(MATCH_NOMATCH); } break; /* Match a negated single one-byte character repeatedly. This is almost a repeat of the code for a repeated single character, but I haven't found a nice way of commoning these up that doesn't require a test of the positive/negative option for each character match. Maybe that wouldn't add very much to the time taken, but character matching *is* what this is all about... */ case OP_NOTEXACT: min = max = GET2(ecode, 1); ecode += 3; goto REPEATNOTCHAR; case OP_NOTUPTO: case OP_NOTMINUPTO: min = 0; max = GET2(ecode, 1); minimize = *ecode == OP_NOTMINUPTO; ecode += 3; goto REPEATNOTCHAR; case OP_NOTSTAR: case OP_NOTMINSTAR: case OP_NOTPLUS: case OP_NOTMINPLUS: case OP_NOTQUERY: case OP_NOTMINQUERY: c = *ecode++ - OP_NOTSTAR; minimize = (c & 1) != 0; min = rep_min[c]; /* Pick up values from tables; */ max = rep_max[c]; /* zero for max => infinity */ if (max == 0) max = INT_MAX; /* Common code for all repeated single-byte matches. We can give up quickly if there are fewer than the minimum number of bytes left in the subject. */ REPEATNOTCHAR: if (min > md->end_subject - eptr) RRETURN(MATCH_NOMATCH); fc = *ecode++; /* The code is duplicated for the caseless and caseful cases, for speed, since matching characters is likely to be quite common. First, ensure the minimum number of matches are present. If min = max, continue at the same level without recursing. Otherwise, if minimizing, keep trying the rest of the expression and advancing one matching character if failing, up to the maximum. Alternatively, if maximizing, find the maximum number of characters and work backwards. */ DPRINTF(("negative matching %c{%d,%d} against subject %.*s\n", fc, min, max, max, eptr)); if ((ims & PCRE_CASELESS) != 0) { fc = md->lcc[fc]; #ifdef SUPPORT_UTF8 /* UTF-8 mode */ if (md->utf8) { register int d; for (i = 1; i <= min; i++) { GETCHARINC(d, eptr); if (d < 256) d = md->lcc[d]; if (fc == d) RRETURN(MATCH_NOMATCH); } } else #endif /* Not UTF-8 mode */ { for (i = 1; i <= min; i++) if (fc == md->lcc[*eptr++]) RRETURN(MATCH_NOMATCH); } if (min == max) continue; if (minimize) { #ifdef SUPPORT_UTF8 /* UTF-8 mode */ if (md->utf8) { register int d; for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); GETCHARINC(d, eptr); if (d < 256) d = md->lcc[d]; if (fi >= max || eptr >= md->end_subject || fc == d) RRETURN(MATCH_NOMATCH); } } else #endif /* Not UTF-8 mode */ { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || eptr >= md->end_subject || fc == md->lcc[*eptr++]) RRETURN(MATCH_NOMATCH); } } /* Control never gets here */ } /* Maximize case */ else { pp = eptr; #ifdef SUPPORT_UTF8 /* UTF-8 mode */ if (md->utf8) { register int d; for (i = min; i < max; i++) { int len = 1; if (eptr >= md->end_subject) break; GETCHARLEN(d, eptr, len); if (d < 256) d = md->lcc[d]; if (fc == d) break; eptr += len; } for(;;) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (eptr-- == pp) break; /* Stop if tried at original pos */ BACKCHAR(eptr); } } else #endif /* Not UTF-8 mode */ { for (i = min; i < max; i++) { if (eptr >= md->end_subject || fc == md->lcc[*eptr]) break; eptr++; } while (eptr >= pp) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); eptr--; } } RRETURN(MATCH_NOMATCH); } /* Control never gets here */ } /* Caseful comparisons */ else { #ifdef SUPPORT_UTF8 /* UTF-8 mode */ if (md->utf8) { register int d; for (i = 1; i <= min; i++) { GETCHARINC(d, eptr); if (fc == d) RRETURN(MATCH_NOMATCH); } } else #endif /* Not UTF-8 mode */ { for (i = 1; i <= min; i++) if (fc == *eptr++) RRETURN(MATCH_NOMATCH); } if (min == max) continue; if (minimize) { #ifdef SUPPORT_UTF8 /* UTF-8 mode */ if (md->utf8) { register int d; for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); GETCHARINC(d, eptr); if (fi >= max || eptr >= md->end_subject || fc == d) RRETURN(MATCH_NOMATCH); } } else #endif /* Not UTF-8 mode */ { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || eptr >= md->end_subject || fc == *eptr++) RRETURN(MATCH_NOMATCH); } } /* Control never gets here */ } /* Maximize case */ else { pp = eptr; #ifdef SUPPORT_UTF8 /* UTF-8 mode */ if (md->utf8) { register int d; for (i = min; i < max; i++) { int len = 1; if (eptr >= md->end_subject) break; GETCHARLEN(d, eptr, len); if (fc == d) break; eptr += len; } for(;;) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (eptr-- == pp) break; /* Stop if tried at original pos */ BACKCHAR(eptr); } } else #endif /* Not UTF-8 mode */ { for (i = min; i < max; i++) { if (eptr >= md->end_subject || fc == *eptr) break; eptr++; } while (eptr >= pp) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); eptr--; } } RRETURN(MATCH_NOMATCH); } } /* Control never gets here */ /* Match a single character type repeatedly; several different opcodes share code. This is very similar to the code for single characters, but we repeat it in the interests of efficiency. */ case OP_TYPEEXACT: min = max = GET2(ecode, 1); minimize = TRUE; ecode += 3; goto REPEATTYPE; case OP_TYPEUPTO: case OP_TYPEMINUPTO: min = 0; max = GET2(ecode, 1); minimize = *ecode == OP_TYPEMINUPTO; ecode += 3; goto REPEATTYPE; case OP_TYPESTAR: case OP_TYPEMINSTAR: case OP_TYPEPLUS: case OP_TYPEMINPLUS: case OP_TYPEQUERY: case OP_TYPEMINQUERY: c = *ecode++ - OP_TYPESTAR; minimize = (c & 1) != 0; min = rep_min[c]; /* Pick up values from tables; */ max = rep_max[c]; /* zero for max => infinity */ if (max == 0) max = INT_MAX; /* Common code for all repeated single character type matches. Note that in UTF-8 mode, '.' matches a character of any length, but for the other character types, the valid characters are all one-byte long. */ REPEATTYPE: ctype = *ecode++; /* Code for the character type */ #ifdef SUPPORT_UCP if (ctype == OP_PROP || ctype == OP_NOTPROP) { prop_fail_result = ctype == OP_NOTPROP; prop_type = *ecode++; if (prop_type >= 128) { prop_test_against = prop_type - 128; prop_test_variable = &prop_category; } else { prop_test_against = prop_type; prop_test_variable = &prop_chartype; } } else prop_type = -1; #endif /* First, ensure the minimum number of matches are present. Use inline code for maximizing the speed, and do the type test once at the start (i.e. keep it out of the loop). Also we can test that there are at least the minimum number of bytes before we start. This isn't as effective in UTF-8 mode, but it does no harm. Separate the UTF-8 code completely as that is tidier. Also separate the UCP code, which can be the same for both UTF-8 and single-bytes. */ if (min > md->end_subject - eptr) RRETURN(MATCH_NOMATCH); if (min > 0) { #ifdef SUPPORT_UCP if (prop_type > 0) { for (i = 1; i <= min; i++) { GETCHARINC(c, eptr); prop_category = ucp_findchar(c, &prop_chartype, &prop_othercase); if ((*prop_test_variable == prop_test_against) == prop_fail_result) RRETURN(MATCH_NOMATCH); } } /* Match extended Unicode sequences. We will get here only if the support is in the binary; otherwise a compile-time error occurs. */ else if (ctype == OP_EXTUNI) { for (i = 1; i <= min; i++) { GETCHARINCTEST(c, eptr); prop_category = ucp_findchar(c, &prop_chartype, &prop_othercase); if (prop_category == ucp_M) RRETURN(MATCH_NOMATCH); while (eptr < md->end_subject) { int len = 1; if (!md->utf8) c = *eptr; else { GETCHARLEN(c, eptr, len); } prop_category = ucp_findchar(c, &prop_chartype, &prop_othercase); if (prop_category != ucp_M) break; eptr += len; } } } else #endif /* SUPPORT_UCP */ /* Handle all other cases when the coding is UTF-8 */ #ifdef SUPPORT_UTF8 if (md->utf8) switch(ctype) { case OP_ANY: for (i = 1; i <= min; i++) { if (eptr >= md->end_subject || (*eptr++ == NEWLINE && (ims & PCRE_DOTALL) == 0)) RRETURN(MATCH_NOMATCH); while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; } break; case OP_ANYBYTE: eptr += min; break; case OP_NOT_DIGIT: for (i = 1; i <= min; i++) { if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINC(c, eptr); if (c < 128 && (md->ctypes[c] & ctype_digit) != 0) RRETURN(MATCH_NOMATCH); } break; case OP_DIGIT: for (i = 1; i <= min; i++) { if (eptr >= md->end_subject || *eptr >= 128 || (md->ctypes[*eptr++] & ctype_digit) == 0) RRETURN(MATCH_NOMATCH); /* No need to skip more bytes - we know it's a 1-byte character */ } break; case OP_NOT_WHITESPACE: for (i = 1; i <= min; i++) { if (eptr >= md->end_subject || (*eptr < 128 && (md->ctypes[*eptr++] & ctype_space) != 0)) RRETURN(MATCH_NOMATCH); while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; } break; case OP_WHITESPACE: for (i = 1; i <= min; i++) { if (eptr >= md->end_subject || *eptr >= 128 || (md->ctypes[*eptr++] & ctype_space) == 0) RRETURN(MATCH_NOMATCH); /* No need to skip more bytes - we know it's a 1-byte character */ } break; case OP_NOT_WORDCHAR: for (i = 1; i <= min; i++) { if (eptr >= md->end_subject || (*eptr < 128 && (md->ctypes[*eptr++] & ctype_word) != 0)) RRETURN(MATCH_NOMATCH); while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; } break; case OP_WORDCHAR: for (i = 1; i <= min; i++) { if (eptr >= md->end_subject || *eptr >= 128 || (md->ctypes[*eptr++] & ctype_word) == 0) RRETURN(MATCH_NOMATCH); /* No need to skip more bytes - we know it's a 1-byte character */ } break; default: RRETURN(PCRE_ERROR_INTERNAL); } /* End switch(ctype) */ else #endif /* SUPPORT_UTF8 */ /* Code for the non-UTF-8 case for minimum matching of operators other than OP_PROP and OP_NOTPROP. */ switch(ctype) { case OP_ANY: if ((ims & PCRE_DOTALL) == 0) { for (i = 1; i <= min; i++) if (*eptr++ == NEWLINE) RRETURN(MATCH_NOMATCH); } else eptr += min; break; case OP_ANYBYTE: eptr += min; break; case OP_NOT_DIGIT: for (i = 1; i <= min; i++) if ((md->ctypes[*eptr++] & ctype_digit) != 0) RRETURN(MATCH_NOMATCH); break; case OP_DIGIT: for (i = 1; i <= min; i++) if ((md->ctypes[*eptr++] & ctype_digit) == 0) RRETURN(MATCH_NOMATCH); break; case OP_NOT_WHITESPACE: for (i = 1; i <= min; i++) if ((md->ctypes[*eptr++] & ctype_space) != 0) RRETURN(MATCH_NOMATCH); break; case OP_WHITESPACE: for (i = 1; i <= min; i++) if ((md->ctypes[*eptr++] & ctype_space) == 0) RRETURN(MATCH_NOMATCH); break; case OP_NOT_WORDCHAR: for (i = 1; i <= min; i++) if ((md->ctypes[*eptr++] & ctype_word) != 0) RRETURN(MATCH_NOMATCH); break; case OP_WORDCHAR: for (i = 1; i <= min; i++) if ((md->ctypes[*eptr++] & ctype_word) == 0) RRETURN(MATCH_NOMATCH); break; default: RRETURN(PCRE_ERROR_INTERNAL); } } /* If min = max, continue at the same level without recursing */ if (min == max) continue; /* If minimizing, we have to test the rest of the pattern before each subsequent match. Again, separate the UTF-8 case for speed, and also separate the UCP cases. */ if (minimize) { #ifdef SUPPORT_UCP if (prop_type > 0) { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINC(c, eptr); prop_category = ucp_findchar(c, &prop_chartype, &prop_othercase); if ((*prop_test_variable == prop_test_against) == prop_fail_result) RRETURN(MATCH_NOMATCH); } } /* Match extended Unicode sequences. We will get here only if the support is in the binary; otherwise a compile-time error occurs. */ else if (ctype == OP_EXTUNI) { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINCTEST(c, eptr); prop_category = ucp_findchar(c, &prop_chartype, &prop_othercase); if (prop_category == ucp_M) RRETURN(MATCH_NOMATCH); while (eptr < md->end_subject) { int len = 1; if (!md->utf8) c = *eptr; else { GETCHARLEN(c, eptr, len); } prop_category = ucp_findchar(c, &prop_chartype, &prop_othercase); if (prop_category != ucp_M) break; eptr += len; } } } else #endif /* SUPPORT_UCP */ #ifdef SUPPORT_UTF8 /* UTF-8 mode */ if (md->utf8) { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); GETCHARINC(c, eptr); switch(ctype) { case OP_ANY: if ((ims & PCRE_DOTALL) == 0 && c == NEWLINE) RRETURN(MATCH_NOMATCH); break; case OP_ANYBYTE: break; case OP_NOT_DIGIT: if (c < 256 && (md->ctypes[c] & ctype_digit) != 0) RRETURN(MATCH_NOMATCH); break; case OP_DIGIT: if (c >= 256 || (md->ctypes[c] & ctype_digit) == 0) RRETURN(MATCH_NOMATCH); break; case OP_NOT_WHITESPACE: if (c < 256 && (md->ctypes[c] & ctype_space) != 0) RRETURN(MATCH_NOMATCH); break; case OP_WHITESPACE: if (c >= 256 || (md->ctypes[c] & ctype_space) == 0) RRETURN(MATCH_NOMATCH); break; case OP_NOT_WORDCHAR: if (c < 256 && (md->ctypes[c] & ctype_word) != 0) RRETURN(MATCH_NOMATCH); break; case OP_WORDCHAR: if (c >= 256 && (md->ctypes[c] & ctype_word) == 0) RRETURN(MATCH_NOMATCH); break; default: RRETURN(PCRE_ERROR_INTERNAL); } } } else #endif /* Not UTF-8 mode */ { for (fi = min;; fi++) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); c = *eptr++; switch(ctype) { case OP_ANY: if ((ims & PCRE_DOTALL) == 0 && c == NEWLINE) RRETURN(MATCH_NOMATCH); break; case OP_ANYBYTE: break; case OP_NOT_DIGIT: if ((md->ctypes[c] & ctype_digit) != 0) RRETURN(MATCH_NOMATCH); break; case OP_DIGIT: if ((md->ctypes[c] & ctype_digit) == 0) RRETURN(MATCH_NOMATCH); break; case OP_NOT_WHITESPACE: if ((md->ctypes[c] & ctype_space) != 0) RRETURN(MATCH_NOMATCH); break; case OP_WHITESPACE: if ((md->ctypes[c] & ctype_space) == 0) RRETURN(MATCH_NOMATCH); break; case OP_NOT_WORDCHAR: if ((md->ctypes[c] & ctype_word) != 0) RRETURN(MATCH_NOMATCH); break; case OP_WORDCHAR: if ((md->ctypes[c] & ctype_word) == 0) RRETURN(MATCH_NOMATCH); break; default: RRETURN(PCRE_ERROR_INTERNAL); } } } /* Control never gets here */ } /* If maximizing it is worth using inline code for speed, doing the type test once at the start (i.e. keep it out of the loop). Again, keep the UTF-8 and UCP stuff separate. */ else { pp = eptr; /* Remember where we started */ #ifdef SUPPORT_UCP if (prop_type > 0) { for (i = min; i < max; i++) { int len = 1; if (eptr >= md->end_subject) break; GETCHARLEN(c, eptr, len); prop_category = ucp_findchar(c, &prop_chartype, &prop_othercase); if ((*prop_test_variable == prop_test_against) == prop_fail_result) break; eptr+= len; } /* eptr is now past the end of the maximum run */ for(;;) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (eptr-- == pp) break; /* Stop if tried at original pos */ BACKCHAR(eptr); } } /* Match extended Unicode sequences. We will get here only if the support is in the binary; otherwise a compile-time error occurs. */ else if (ctype == OP_EXTUNI) { for (i = min; i < max; i++) { if (eptr >= md->end_subject) break; GETCHARINCTEST(c, eptr); prop_category = ucp_findchar(c, &prop_chartype, &prop_othercase); if (prop_category == ucp_M) break; while (eptr < md->end_subject) { int len = 1; if (!md->utf8) c = *eptr; else { GETCHARLEN(c, eptr, len); } prop_category = ucp_findchar(c, &prop_chartype, &prop_othercase); if (prop_category != ucp_M) break; eptr += len; } } /* eptr is now past the end of the maximum run */ for(;;) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (eptr-- == pp) break; /* Stop if tried at original pos */ for (;;) /* Move back over one extended */ { int len = 1; BACKCHAR(eptr); if (!md->utf8) c = *eptr; else { GETCHARLEN(c, eptr, len); } prop_category = ucp_findchar(c, &prop_chartype, &prop_othercase); if (prop_category != ucp_M) break; eptr--; } } } else #endif /* SUPPORT_UCP */ #ifdef SUPPORT_UTF8 /* UTF-8 mode */ if (md->utf8) { switch(ctype) { case OP_ANY: /* Special code is required for UTF8, but when the maximum is unlimited we don't need it, so we repeat the non-UTF8 code. This is probably worth it, because .* is quite a common idiom. */ if (max < INT_MAX) { if ((ims & PCRE_DOTALL) == 0) { for (i = min; i < max; i++) { if (eptr >= md->end_subject || *eptr == NEWLINE) break; eptr++; while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; } } else { for (i = min; i < max; i++) { eptr++; while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; } } } /* Handle unlimited UTF-8 repeat */ else { if ((ims & PCRE_DOTALL) == 0) { for (i = min; i < max; i++) { if (eptr >= md->end_subject || *eptr == NEWLINE) break; eptr++; } break; } else { c = max - min; if (c > md->end_subject - eptr) c = md->end_subject - eptr; eptr += c; } } break; /* The byte case is the same as non-UTF8 */ case OP_ANYBYTE: c = max - min; if (c > md->end_subject - eptr) c = md->end_subject - eptr; eptr += c; break; case OP_NOT_DIGIT: for (i = min; i < max; i++) { int len = 1; if (eptr >= md->end_subject) break; GETCHARLEN(c, eptr, len); if (c < 256 && (md->ctypes[c] & ctype_digit) != 0) break; eptr+= len; } break; case OP_DIGIT: for (i = min; i < max; i++) { int len = 1; if (eptr >= md->end_subject) break; GETCHARLEN(c, eptr, len); if (c >= 256 ||(md->ctypes[c] & ctype_digit) == 0) break; eptr+= len; } break; case OP_NOT_WHITESPACE: for (i = min; i < max; i++) { int len = 1; if (eptr >= md->end_subject) break; GETCHARLEN(c, eptr, len); if (c < 256 && (md->ctypes[c] & ctype_space) != 0) break; eptr+= len; } break; case OP_WHITESPACE: for (i = min; i < max; i++) { int len = 1; if (eptr >= md->end_subject) break; GETCHARLEN(c, eptr, len); if (c >= 256 ||(md->ctypes[c] & ctype_space) == 0) break; eptr+= len; } break; case OP_NOT_WORDCHAR: for (i = min; i < max; i++) { int len = 1; if (eptr >= md->end_subject) break; GETCHARLEN(c, eptr, len); if (c < 256 && (md->ctypes[c] & ctype_word) != 0) break; eptr+= len; } break; case OP_WORDCHAR: for (i = min; i < max; i++) { int len = 1; if (eptr >= md->end_subject) break; GETCHARLEN(c, eptr, len); if (c >= 256 || (md->ctypes[c] & ctype_word) == 0) break; eptr+= len; } break; default: RRETURN(PCRE_ERROR_INTERNAL); } /* eptr is now past the end of the maximum run */ for(;;) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); if (rrc != MATCH_NOMATCH) RRETURN(rrc); if (eptr-- == pp) break; /* Stop if tried at original pos */ BACKCHAR(eptr); } } else #endif /* Not UTF-8 mode */ { switch(ctype) { case OP_ANY: if ((ims & PCRE_DOTALL) == 0) { for (i = min; i < max; i++) { if (eptr >= md->end_subject || *eptr == NEWLINE) break; eptr++; } break; } /* For DOTALL case, fall through and treat as \C */ case OP_ANYBYTE: c = max - min; if (c > md->end_subject - eptr) c = md->end_subject - eptr; eptr += c; break; case OP_NOT_DIGIT: for (i = min; i < max; i++) { if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_digit) != 0) break; eptr++; } break; case OP_DIGIT: for (i = min; i < max; i++) { if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_digit) == 0) break; eptr++; } break; case OP_NOT_WHITESPACE: for (i = min; i < max; i++) { if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_space) != 0) break; eptr++; } break; case OP_WHITESPACE: for (i = min; i < max; i++) { if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_space) == 0) break; eptr++; } break; case OP_NOT_WORDCHAR: for (i = min; i < max; i++) { if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_word) != 0) break; eptr++; } break; case OP_WORDCHAR: for (i = min; i < max; i++) { if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_word) == 0) break; eptr++; } break; default: RRETURN(PCRE_ERROR_INTERNAL); } /* eptr is now past the end of the maximum run */ while (eptr >= pp) { RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0); eptr--; if (rrc != MATCH_NOMATCH) RRETURN(rrc); } } /* Get here if we can't make it match with any permitted repetitions */ RRETURN(MATCH_NOMATCH); } /* Control never gets here */ /* There's been some horrible disaster. Since all codes > OP_BRA are for capturing brackets, and there shouldn't be any gaps between 0 and OP_BRA, arrival here can only mean there is something seriously wrong in the code above or the OP_xxx definitions. */ default: DPRINTF(("Unknown opcode %d\n", *ecode)); RRETURN(PCRE_ERROR_UNKNOWN_NODE); } /* Do not stick any code in here without much thought; it is assumed that "continue" in the code above comes out to here to repeat the main loop. */ } /* End of main loop */ /* Control never reaches here */ } /*************************************************************************** **************************************************************************** RECURSION IN THE match() FUNCTION Undefine all the macros that were defined above to handle this. */ #ifdef NO_RECURSE #undef eptr #undef ecode #undef offset_top #undef ims #undef eptrb #undef flags #undef callpat #undef charptr #undef data #undef next #undef pp #undef prev #undef saved_eptr #undef new_recursive #undef cur_is_word #undef condition #undef minimize #undef prev_is_word #undef original_ims #undef ctype #undef length #undef max #undef min #undef number #undef offset #undef op #undef save_capture_last #undef save_offset1 #undef save_offset2 #undef save_offset3 #undef stacksave #undef newptrb #endif /* These two are defined as macros in both cases */ #undef fc #undef fi /*************************************************************************** ***************************************************************************/ /************************************************* * Execute a Regular Expression * *************************************************/ /* This function applies a compiled re to a subject string and picks out portions of the string if it matches. Two elements in the vector are set for each substring: the offsets to the start and end of the substring. Arguments: argument_re points to the compiled expression extra_data points to extra data or is NULL subject points to the subject string length length of subject string (may contain binary zeros) start_offset where to start in the subject string options option bits offsets points to a vector of ints to be filled in with offsets offsetcount the number of elements in the vector Returns: > 0 => success; value is the number of elements filled in = 0 => success, but offsets is not big enough -1 => failed to match < -1 => some kind of unexpected problem */ EXPORT int pcre_exec(const pcre *argument_re, const pcre_extra *extra_data, const char *subject, int length, int start_offset, int options, int *offsets, int offsetcount) { int rc, resetcount, ocount; int first_byte = -1; int req_byte = -1; int req_byte2 = -1; unsigned long int ims = 0; BOOL using_temporary_offsets = FALSE; BOOL anchored; BOOL startline; BOOL first_byte_caseless = FALSE; BOOL req_byte_caseless = FALSE; match_data match_block; const uschar *tables; const uschar *start_bits = NULL; const uschar *start_match = (const uschar *)subject + start_offset; const uschar *end_subject; const uschar *req_byte_ptr = start_match - 1; pcre_study_data internal_study; const pcre_study_data *study; real_pcre internal_re; const real_pcre *external_re = (const real_pcre *)argument_re; const real_pcre *re = external_re; /* Plausibility checks */ if ((options & ~PUBLIC_EXEC_OPTIONS) != 0) return PCRE_ERROR_BADOPTION; if (re == NULL || subject == NULL || (offsets == NULL && offsetcount > 0)) return PCRE_ERROR_NULL; if (offsetcount < 0) return PCRE_ERROR_BADCOUNT; /* Fish out the optional data from the extra_data structure, first setting the default values. */ study = NULL; match_block.match_limit = MATCH_LIMIT; match_block.callout_data = NULL; /* The table pointer is always in native byte order. */ tables = external_re->tables; if (extra_data != NULL) { register unsigned int flags = extra_data->flags; if ((flags & PCRE_EXTRA_STUDY_DATA) != 0) study = (const pcre_study_data *)extra_data->study_data; if ((flags & PCRE_EXTRA_MATCH_LIMIT) != 0) match_block.match_limit = extra_data->match_limit; if ((flags & PCRE_EXTRA_CALLOUT_DATA) != 0) match_block.callout_data = extra_data->callout_data; if ((flags & PCRE_EXTRA_TABLES) != 0) tables = extra_data->tables; } /* If the exec call supplied NULL for tables, use the inbuilt ones. This is a feature that makes it possible to save compiled regex and re-use them in other programs later. */ if (tables == NULL) tables = pcre_default_tables; /* Check that the first field in the block is the magic number. If it is not, test for a regex that was compiled on a host of opposite endianness. If this is the case, flipped values are put in internal_re and internal_study if there was study data too. */ if (re->magic_number != MAGIC_NUMBER) { re = try_flipped(re, &internal_re, study, &internal_study); if (re == NULL) return PCRE_ERROR_BADMAGIC; if (study != NULL) study = &internal_study; } /* Set up other data */ anchored = ((re->options | options) & PCRE_ANCHORED) != 0; startline = (re->options & PCRE_STARTLINE) != 0; /* The code starts after the real_pcre block and the capture name table. */ match_block.start_code = (const uschar *)external_re + re->name_table_offset + re->name_count * re->name_entry_size; match_block.start_subject = (const uschar *)subject; match_block.start_offset = start_offset; match_block.end_subject = match_block.start_subject + length; end_subject = match_block.end_subject; match_block.endonly = (re->options & PCRE_DOLLAR_ENDONLY) != 0; match_block.utf8 = (re->options & PCRE_UTF8) != 0; match_block.notbol = (options & PCRE_NOTBOL) != 0; match_block.noteol = (options & PCRE_NOTEOL) != 0; match_block.notempty = (options & PCRE_NOTEMPTY) != 0; match_block.partial = (options & PCRE_PARTIAL) != 0; match_block.hitend = FALSE; match_block.recursive = NULL; /* No recursion at top level */ match_block.lcc = tables + lcc_offset; match_block.ctypes = tables + ctypes_offset; /* Partial matching is supported only for a restricted set of regexes at the moment. */ if (match_block.partial && (re->options & PCRE_NOPARTIAL) != 0) return PCRE_ERROR_BADPARTIAL; /* Check a UTF-8 string if required. Unfortunately there's no way of passing back the character offset. */ #ifdef SUPPORT_UTF8 if (match_block.utf8 && (options & PCRE_NO_UTF8_CHECK) == 0) { if (valid_utf8((uschar *)subject, length) >= 0) return PCRE_ERROR_BADUTF8; if (start_offset > 0 && start_offset < length) { int tb = ((uschar *)subject)[start_offset]; if (tb > 127) { tb &= 0xc0; if (tb != 0 && tb != 0xc0) return PCRE_ERROR_BADUTF8_OFFSET; } } } #endif /* The ims options can vary during the matching as a result of the presence of (?ims) items in the pattern. They are kept in a local variable so that restoring at the exit of a group is easy. */ ims = re->options & (PCRE_CASELESS|PCRE_MULTILINE|PCRE_DOTALL); /* If the expression has got more back references than the offsets supplied can hold, we get a temporary chunk of working store to use during the matching. Otherwise, we can use the vector supplied, rounding down its size to a multiple of 3. */ ocount = offsetcount - (offsetcount % 3); if (re->top_backref > 0 && re->top_backref >= ocount/3) { ocount = re->top_backref * 3 + 3; match_block.offset_vector = (int *)(pcre_malloc)(ocount * sizeof(int)); if (match_block.offset_vector == NULL) return PCRE_ERROR_NOMEMORY; using_temporary_offsets = TRUE; DPRINTF(("Got memory to hold back references\n")); } else match_block.offset_vector = offsets; match_block.offset_end = ocount; match_block.offset_max = (2*ocount)/3; match_block.offset_overflow = FALSE; match_block.capture_last = -1; /* Compute the minimum number of offsets that we need to reset each time. Doing this makes a huge difference to execution time when there aren't many brackets in the pattern. */ resetcount = 2 + re->top_bracket * 2; if (resetcount > offsetcount) resetcount = ocount; /* Reset the working variable associated with each extraction. These should never be used unless previously set, but they get saved and restored, and so we initialize them to avoid reading uninitialized locations. */ if (match_block.offset_vector != NULL) { register int *iptr = match_block.offset_vector + ocount; register int *iend = iptr - resetcount/2 + 1; while (--iptr >= iend) *iptr = -1; } /* Set up the first character to match, if available. The first_byte value is never set for an anchored regular expression, but the anchoring may be forced at run time, so we have to test for anchoring. The first char may be unset for an unanchored pattern, of course. If there's no first char and the pattern was studied, there may be a bitmap of possible first characters. */ if (!anchored) { if ((re->options & PCRE_FIRSTSET) != 0) { first_byte = re->first_byte & 255; if ((first_byte_caseless = ((re->first_byte & REQ_CASELESS) != 0)) == TRUE) first_byte = match_block.lcc[first_byte]; } else if (!startline && study != NULL && (study->options & PCRE_STUDY_MAPPED) != 0) start_bits = study->start_bits; } /* For anchored or unanchored matches, there may be a "last known required character" set. */ if ((re->options & PCRE_REQCHSET) != 0) { req_byte = re->req_byte & 255; req_byte_caseless = (re->req_byte & REQ_CASELESS) != 0; req_byte2 = (tables + fcc_offset)[req_byte]; /* case flipped */ } /* Loop for handling unanchored repeated matching attempts; for anchored regexs the loop runs just once. */ do { /* Reset the maximum number of extractions we might see. */ if (match_block.offset_vector != NULL) { register int *iptr = match_block.offset_vector; register int *iend = iptr + resetcount; while (iptr < iend) *iptr++ = -1; } /* Advance to a unique first char if possible */ if (first_byte >= 0) { if (first_byte_caseless) while (start_match < end_subject && match_block.lcc[*start_match] != first_byte) start_match++; else while (start_match < end_subject && *start_match != first_byte) start_match++; } /* Or to just after \n for a multiline match if possible */ else if (startline) { if (start_match > match_block.start_subject + start_offset) { while (start_match < end_subject && start_match[-1] != NEWLINE) start_match++; } } /* Or to a non-unique first char after study */ else if (start_bits != NULL) { while (start_match < end_subject) { register unsigned int c = *start_match; if ((start_bits[c/8] & (1 << (c&7))) == 0) start_match++; else break; } } #ifdef DEBUG /* Sigh. Some compilers never learn. */ printf(">>>> Match against: "); pchars(start_match, end_subject - start_match, TRUE, &match_block); printf("\n"); #endif /* If req_byte is set, we know that that character must appear in the subject for the match to succeed. If the first character is set, req_byte must be later in the subject; otherwise the test starts at the match point. This optimization can save a huge amount of backtracking in patterns with nested unlimited repeats that aren't going to match. Writing separate code for cased/caseless versions makes it go faster, as does using an autoincrement and backing off on a match. HOWEVER: when the subject string is very, very long, searching to its end can take a long time, and give bad performance on quite ordinary patterns. This showed up when somebody was matching /^C/ on a 32-megabyte string... so we don't do this when the string is sufficiently long. ALSO: this processing is disabled when partial matching is requested. */ if (req_byte >= 0 && end_subject - start_match < REQ_BYTE_MAX && !match_block.partial) { register const uschar *p = start_match + ((first_byte >= 0)? 1 : 0); /* We don't need to repeat the search if we haven't yet reached the place we found it at last time. */ if (p > req_byte_ptr) { if (req_byte_caseless) { while (p < end_subject) { register int pp = *p++; if (pp == req_byte || pp == req_byte2) { p--; break; } } } else { while (p < end_subject) { if (*p++ == req_byte) { p--; break; } } } /* If we can't find the required character, break the matching loop */ if (p >= end_subject) break; /* If we have found the required character, save the point where we found it, so that we don't search again next time round the loop if the start hasn't passed this character yet. */ req_byte_ptr = p; } } /* When a match occurs, substrings will be set for all internal extractions; we just need to set up the whole thing as substring 0 before returning. If there were too many extractions, set the return code to zero. In the case where we had to get some local store to hold offsets for backreferences, copy those back references that we can. In this case there need not be overflow if certain parts of the pattern were not used. */ match_block.start_match = start_match; match_block.match_call_count = 0; rc = match(start_match, match_block.start_code, 2, &match_block, ims, NULL, match_isgroup); if (rc == MATCH_NOMATCH) { start_match++; #ifdef SUPPORT_UTF8 if (match_block.utf8) while(start_match < end_subject && (*start_match & 0xc0) == 0x80) start_match++; #endif continue; } if (rc != MATCH_MATCH) { DPRINTF((">>>> error: returning %d\n", rc)); return rc; } /* We have a match! Copy the offset information from temporary store if necessary */ if (using_temporary_offsets) { if (offsetcount >= 4) { memcpy(offsets + 2, match_block.offset_vector + 2, (offsetcount - 2) * sizeof(int)); DPRINTF(("Copied offsets from temporary memory\n")); } if (match_block.end_offset_top > offsetcount) match_block.offset_overflow = TRUE; DPRINTF(("Freeing temporary memory\n")); (pcre_free)(match_block.offset_vector); } rc = match_block.offset_overflow? 0 : match_block.end_offset_top/2; if (offsetcount < 2) rc = 0; else { offsets[0] = start_match - match_block.start_subject; offsets[1] = match_block.end_match_ptr - match_block.start_subject; } DPRINTF((">>>> returning %d\n", rc)); return rc; } /* This "while" is the end of the "do" above */ while (!anchored && start_match <= end_subject); if (using_temporary_offsets) { DPRINTF(("Freeing temporary memory\n")); (pcre_free)(match_block.offset_vector); } if (match_block.partial && match_block.hitend) { DPRINTF((">>>> returning PCRE_ERROR_PARTIAL\n")); return PCRE_ERROR_PARTIAL; } else { DPRINTF((">>>> returning PCRE_ERROR_NOMATCH\n")); return PCRE_ERROR_NOMATCH; } } /* End of pcre.c */ mvdsv-0.35/src/pcre/pcre.h000066400000000000000000000237251427146041000154320ustar00rootroot00000000000000/* 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. */ /************************************************* * Perl-Compatible Regular Expressions * *************************************************/ /* In its original form, this is the .in file that is transformed by "configure" into pcre.h. Copyright (c) 1997-2004 University of Cambridge ----------------------------------------------------------------------------- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Cambridge nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ----------------------------------------------------------------------------- */ #ifndef _PCRE_H #define _PCRE_H /* The file pcre.h is build by "configure". Do not edit it; instead make changes to pcre.in. */ #define PCRE_MAJOR 5 #define PCRE_MINOR 0 #define PCRE_DATE 13-Sep-2004 /* Win32 uses DLL by default */ #ifdef _WIN32 # ifdef PCRE_DEFINITION # ifdef DLL_EXPORT # define PCRE_DATA_SCOPE __declspec(dllexport) # endif # else # ifndef PCRE_STATIC # define PCRE_DATA_SCOPE extern __declspec(dllimport) # endif # endif #endif #ifndef PCRE_DATA_SCOPE # define PCRE_DATA_SCOPE extern #endif /* Have to include stdlib.h in order to ensure that size_t is defined; it is needed here for malloc. */ #include /* Allow for C++ users */ #ifdef __cplusplus extern "C" { #endif /* Options */ #define PCRE_CASELESS 0x0001 #define PCRE_MULTILINE 0x0002 #define PCRE_DOTALL 0x0004 #define PCRE_EXTENDED 0x0008 #define PCRE_ANCHORED 0x0010 #define PCRE_DOLLAR_ENDONLY 0x0020 #define PCRE_EXTRA 0x0040 #define PCRE_NOTBOL 0x0080 #define PCRE_NOTEOL 0x0100 #define PCRE_UNGREEDY 0x0200 #define PCRE_NOTEMPTY 0x0400 #define PCRE_UTF8 0x0800 #define PCRE_NO_AUTO_CAPTURE 0x1000 #define PCRE_NO_UTF8_CHECK 0x2000 #define PCRE_AUTO_CALLOUT 0x4000 #define PCRE_PARTIAL 0x8000 /* Exec-time and get/set-time error codes */ #define PCRE_ERROR_NOMATCH (-1) #define PCRE_ERROR_NULL (-2) #define PCRE_ERROR_BADOPTION (-3) #define PCRE_ERROR_BADMAGIC (-4) #define PCRE_ERROR_UNKNOWN_NODE (-5) #define PCRE_ERROR_NOMEMORY (-6) #define PCRE_ERROR_NOSUBSTRING (-7) #define PCRE_ERROR_MATCHLIMIT (-8) #define PCRE_ERROR_CALLOUT (-9) /* Never used by PCRE itself */ #define PCRE_ERROR_BADUTF8 (-10) #define PCRE_ERROR_BADUTF8_OFFSET (-11) #define PCRE_ERROR_PARTIAL (-12) #define PCRE_ERROR_BADPARTIAL (-13) #define PCRE_ERROR_INTERNAL (-14) #define PCRE_ERROR_BADCOUNT (-15) /* Request types for pcre_fullinfo() */ #define PCRE_INFO_OPTIONS 0 #define PCRE_INFO_SIZE 1 #define PCRE_INFO_CAPTURECOUNT 2 #define PCRE_INFO_BACKREFMAX 3 #define PCRE_INFO_FIRSTBYTE 4 #define PCRE_INFO_FIRSTCHAR 4 /* For backwards compatibility */ #define PCRE_INFO_FIRSTTABLE 5 #define PCRE_INFO_LASTLITERAL 6 #define PCRE_INFO_NAMEENTRYSIZE 7 #define PCRE_INFO_NAMECOUNT 8 #define PCRE_INFO_NAMETABLE 9 #define PCRE_INFO_STUDYSIZE 10 #define PCRE_INFO_DEFAULT_TABLES 11 /* Request types for pcre_config() */ #define PCRE_CONFIG_UTF8 0 #define PCRE_CONFIG_NEWLINE 1 #define PCRE_CONFIG_LINK_SIZE 2 #define PCRE_CONFIG_POSIX_MALLOC_THRESHOLD 3 #define PCRE_CONFIG_MATCH_LIMIT 4 #define PCRE_CONFIG_STACKRECURSE 5 #define PCRE_CONFIG_UNICODE_PROPERTIES 6 /* Bit flags for the pcre_extra structure */ #define PCRE_EXTRA_STUDY_DATA 0x0001 #define PCRE_EXTRA_MATCH_LIMIT 0x0002 #define PCRE_EXTRA_CALLOUT_DATA 0x0004 #define PCRE_EXTRA_TABLES 0x0008 /* Types */ struct real_pcre; /* declaration; the definition is private */ typedef struct real_pcre pcre; /* The structure for passing additional data to pcre_exec(). This is defined in such as way as to be extensible. Always add new fields at the end, in order to remain compatible. */ typedef struct pcre_extra { unsigned long int flags; /* Bits for which fields are set */ void *study_data; /* Opaque data from pcre_study() */ unsigned long int match_limit; /* Maximum number of calls to match() */ void *callout_data; /* Data passed back in callouts */ const unsigned char *tables; /* Pointer to character tables */ } pcre_extra; /* The structure for passing out data via the pcre_callout_function. We use a structure so that new fields can be added on the end in future versions, without changing the API of the function, thereby allowing old clients to work without modification. */ typedef struct pcre_callout_block { int version; /* Identifies version of block */ /* ------------------------ Version 0 ------------------------------- */ int callout_number; /* Number compiled into pattern */ int *offset_vector; /* The offset vector */ const char *subject; /* The subject being matched */ int subject_length; /* The length of the subject */ int start_match; /* Offset to start of this match attempt */ int current_position; /* Where we currently are in the subject */ int capture_top; /* Max current capture */ int capture_last; /* Most recently closed capture */ void *callout_data; /* Data passed in with the call */ /* ------------------- Added for Version 1 -------------------------- */ int pattern_position; /* Offset to next item in the pattern */ int next_item_length; /* Length of next item in the pattern */ /* ------------------------------------------------------------------ */ } pcre_callout_block; /* Indirection for store get and free functions. These can be set to alternative malloc/free functions if required. Special ones are used in the non-recursive case for "frames". There is also an optional callout function that is triggered by the (?) regex item. Some magic is required for Win32 DLL; it is null on other OS. For Virtual Pascal, these have to be different again. */ #ifndef VPCOMPAT PCRE_DATA_SCOPE void *(*pcre_malloc)(size_t); PCRE_DATA_SCOPE void (*pcre_free)(void *); PCRE_DATA_SCOPE void *(*pcre_stack_malloc)(size_t); PCRE_DATA_SCOPE void (*pcre_stack_free)(void *); PCRE_DATA_SCOPE int (*pcre_callout)(pcre_callout_block *); #else /* VPCOMPAT */ extern void *pcre_malloc(size_t); extern void pcre_free(void *); extern void *pcre_stack_malloc(size_t); extern void pcre_stack_free(void *); extern int pcre_callout(pcre_callout_block *); #endif /* VPCOMPAT */ /* Exported PCRE functions */ extern pcre *pcre_compile(const char *, int, const char **, int *, const unsigned char *); extern int pcre_config(int, void *); extern int pcre_copy_named_substring(const pcre *, const char *, int *, int, const char *, char *, int); extern int pcre_copy_substring(const char *, int *, int, int, char *, int); extern int pcre_exec(const pcre *, const pcre_extra *, const char *, int, int, int, int *, int); extern void pcre_free_substring(const char *); extern void pcre_free_substring_list(const char **); extern int pcre_fullinfo(const pcre *, const pcre_extra *, int, void *); extern int pcre_get_named_substring(const pcre *, const char *, int *, int, const char *, const char **); extern int pcre_get_stringnumber(const pcre *, const char *); extern int pcre_get_substring(const char *, int *, int, int, const char **); extern int pcre_get_substring_list(const char *, int *, int, const char ***); extern int pcre_info(const pcre *, int *, int *); extern const unsigned char *pcre_maketables(void); extern pcre_extra *pcre_study(const pcre *, int, const char **); extern const char *pcre_version(void); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* End of pcre.h */ mvdsv-0.35/src/pmove.c000066400000000000000000000570601427146041000146700ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifdef SERVERONLY #include "qwsvdef.h" #else #include "quakedef.h" #include "pmove.h" #endif movevars_t movevars; playermove_t pmove; static float pm_frametime; static vec3_t pm_forward, pm_right; static vec3_t groundnormal; vec3_t player_mins = {-16, -16, -24}; vec3_t player_maxs = {16, 16, 32}; #define STEPSIZE 18 #define pm_flyfriction 4 #define BLOCKED_FLOOR 1 #define BLOCKED_STEP 2 #define BLOCKED_OTHER 4 #define BLOCKED_ANY 7 #define MAX_JUMPFIX_DOTPRODUCT -0.1 // Add an entity to touch list, discarding duplicates static void PM_AddTouchedEnt (int num) { int i; if (pmove.numtouch == sizeof(pmove.touchindex)/sizeof(pmove.touchindex[0])) return; for (i = 0; i < pmove.numtouch; i++) if (pmove.touchindex[i] == num) return; // already added pmove.touchindex[pmove.numtouch] = num; pmove.numtouch++; } //Slide off of the impacting object //returns the blocked flags (1 = floor, 2 = step / wall) #define STOP_EPSILON 0.1 static void PM_ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) { float backoff, change; int i; backoff = DotProduct (in, normal) * overbounce; for (i = 0; i < 3; i++) { change = normal[i] * backoff; out[i] = in[i] - change; if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) out[i] = 0; } } //The basic solid body movement clip that slides along multiple planes #define MAX_CLIP_PLANES 5 static int PM_SlideMove (void) { vec3_t dir, planes[MAX_CLIP_PLANES], primal_velocity, original_velocity, end; int bumpcount, numbumps, i, j, blocked, numplanes; float d, time_left; trace_t trace; numbumps = 4; blocked = 0; VectorCopy (pmove.velocity, original_velocity); VectorCopy (pmove.velocity, primal_velocity); numplanes = 0; time_left = pm_frametime; for (bumpcount = 0; bumpcount < numbumps; bumpcount++) { VectorMA(pmove.origin, time_left, pmove.velocity, end); trace = PM_PlayerTrace (pmove.origin, end); if (trace.startsolid || trace.allsolid) { // entity is trapped in another solid VectorClear (pmove.velocity); return 3; } if (trace.fraction > 0) { // actually covered some distance VectorCopy (trace.endpos, pmove.origin); numplanes = 0; } if (trace.fraction == 1) { break; // moved the entire distance } // save entity for contact PM_AddTouchedEnt (trace.e.entnum); if (trace.plane.normal[2] >= MIN_STEP_NORMAL) blocked |= BLOCKED_FLOOR; else if (!trace.plane.normal[2]) blocked |= BLOCKED_STEP; else blocked |= BLOCKED_OTHER; time_left -= time_left * trace.fraction; // cliped to another plane if (numplanes >= MAX_CLIP_PLANES) { // this shouldn't really happen VectorClear (pmove.velocity); break; } VectorCopy (trace.plane.normal, planes[numplanes]); numplanes++; // modify original_velocity so it parallels all of the clip planes for (i = 0; i < numplanes; i++) { PM_ClipVelocity (original_velocity, planes[i], pmove.velocity, 1); for (j = 0; j < numplanes; j++) { if (j != i) { if (DotProduct(pmove.velocity, planes[j]) < 0) { break; // not ok } } } if (j == numplanes) { break; } } if (i != numplanes) { // go along this plane } else { // go along the crease if (numplanes != 2) { VectorClear (pmove.velocity); break; } CrossProduct (planes[0], planes[1], dir); d = DotProduct (dir, pmove.velocity); VectorScale (dir, d, pmove.velocity); } // if velocity is against the original velocity, stop dead // to avoid tiny occilations in sloping corners if (DotProduct (pmove.velocity, primal_velocity) <= 0) { VectorClear (pmove.velocity); break; } } if (pmove.waterjumptime) { VectorCopy(primal_velocity, pmove.velocity); } return blocked; } //Each intersection will try to step over the obstruction instead of sliding along it. static int PM_StepSlideMove (qbool in_air) { vec3_t original, originalvel, down, up, downvel, dest; float downdist, updist, stepsize; trace_t trace; int blocked; // try sliding forward both on ground and up 16 pixels // take the move that goes farthest VectorCopy (pmove.origin, original); VectorCopy (pmove.velocity, originalvel); blocked = PM_SlideMove (); if (!blocked) { return blocked; // moved the entire distance } if (in_air) { // don't let us step up unless it's indeed a step we bumped in // (that is, there's solid ground below) float *org; if (!(blocked & BLOCKED_STEP)) { return blocked; } org = (originalvel[2] < 0) ? pmove.origin : original; VectorCopy (org, dest); dest[2] -= STEPSIZE; trace = PM_PlayerTrace (org, dest); if (trace.fraction == 1 || trace.plane.normal[2] < MIN_STEP_NORMAL) { return blocked; } // adjust stepsize, otherwise it would be possible to walk up a // a step higher than STEPSIZE stepsize = STEPSIZE - (org[2] - trace.endpos[2]); } else { stepsize = STEPSIZE; } VectorCopy (pmove.origin, down); VectorCopy (pmove.velocity, downvel); VectorCopy (original, pmove.origin); VectorCopy (originalvel, pmove.velocity); // move up a stair height VectorCopy (pmove.origin, dest); dest[2] += stepsize; trace = PM_PlayerTrace (pmove.origin, dest); if (!trace.startsolid && !trace.allsolid) { VectorCopy(trace.endpos, pmove.origin); } if (in_air && originalvel[2] < 0) { pmove.velocity[2] = 0; } PM_SlideMove (); // press down the stepheight VectorCopy (pmove.origin, dest); dest[2] -= stepsize; trace = PM_PlayerTrace (pmove.origin, dest); if (trace.fraction != 1 && trace.plane.normal[2] < MIN_STEP_NORMAL) { goto usedown; } if (!trace.startsolid && !trace.allsolid) { VectorCopy(trace.endpos, pmove.origin); } if (pmove.origin[2] < original[2]) { goto usedown; } VectorCopy (pmove.origin, up); // decide which one went farther downdist = (down[0] - original[0]) * (down[0] - original[0]) + (down[1] - original[1]) * (down[1] - original[1]); updist = (up[0] - original[0]) * (up[0] - original[0]) + (up[1] - original[1]) * (up[1] - original[1]); if (downdist >= updist) { usedown: VectorCopy (down, pmove.origin); VectorCopy (downvel, pmove.velocity); return blocked; } // copy z value from slide move pmove.velocity[2] = downvel[2]; if (!pmove.onground && pmove.waterlevel < 2 && (blocked & BLOCKED_STEP)) { float scale; // in pm_airstep mode, walking up a 16 unit high step // will kill 16% of horizontal velocity scale = 1 - 0.01*(pmove.origin[2] - original[2]); pmove.velocity[0] *= scale; pmove.velocity[1] *= scale; } return blocked; } //Handles both ground friction and water friction static void PM_Friction(void) { float speed, newspeed, control, friction, drop; vec3_t start, stop; trace_t trace; if (pmove.waterjumptime) return; speed = VectorLength(pmove.velocity); if (speed < 1) { pmove.velocity[0] = pmove.velocity[1] = 0; if (pmove.pm_type == PM_FLY) { pmove.velocity[2] = 0; } return; } if (pmove.waterlevel >= 2) { // apply water friction, even if in fly mode drop = speed * movevars.waterfriction * pmove.waterlevel * pm_frametime; } else if (pmove.pm_type == PM_FLY) { // apply flymode friction drop = speed * pm_flyfriction * pm_frametime; } else if (pmove.onground) { // apply ground friction friction = movevars.friction; // if the leading edge is over a dropoff, increase friction start[0] = stop[0] = pmove.origin[0] + pmove.velocity[0] / speed * 16; start[1] = stop[1] = pmove.origin[1] + pmove.velocity[1] / speed * 16; start[2] = pmove.origin[2] + player_mins[2]; stop[2] = start[2] - 34; trace = PM_PlayerTrace(start, stop); if (trace.fraction == 1) { friction *= 2; } control = speed < movevars.stopspeed ? movevars.stopspeed : speed; drop = control * friction * pm_frametime; } else { return; // in air, no friction } // scale the velocity newspeed = speed - drop; newspeed = max(newspeed, 0); newspeed /= speed; VectorScale(pmove.velocity, newspeed, pmove.velocity); } static void PM_Accelerate(vec3_t wishdir, float wishspeed, float accel) { float addspeed, accelspeed, currentspeed; if (pmove.pm_type == PM_DEAD) return; if (pmove.waterjumptime) return; currentspeed = DotProduct(pmove.velocity, wishdir); addspeed = wishspeed - currentspeed; if (addspeed <= 0) return; accelspeed = accel * pm_frametime * wishspeed; if (accelspeed > addspeed) accelspeed = addspeed; VectorMA(pmove.velocity, accelspeed, wishdir, pmove.velocity); } #ifndef SERVERONLY #ifdef EXPERIMENTAL_SHOW_ACCELERATION qbool player_in_air = false; float cosinus_val = 0.f; qbool flag_player_pmove; #endif #endif static void PM_AirAccelerate(vec3_t wishdir, float wishspeed, float accel) { float addspeed, accelspeed, currentspeed, wishspd = wishspeed; float originalspeed = 0.0, newspeed = 0.0, speedcap = 0.0; if (pmove.pm_type == PM_DEAD) return; if (pmove.waterjumptime) return; if (movevars.bunnyspeedcap > 0) originalspeed = sqrt(pmove.velocity[0] * pmove.velocity[0] + pmove.velocity[1] * pmove.velocity[1]); wishspd = min(wishspd, 30); currentspeed = DotProduct(pmove.velocity, wishdir); addspeed = wishspd - currentspeed; #ifdef EXPERIMENTAL_SHOW_ACCELERATION if (flag_player_pmove) { cosinus_val = 0.f; originalspeed = sqrt(pmove.velocity[0] * pmove.velocity[0] + pmove.velocity[1] * pmove.velocity[1]); if (originalspeed > 1.f) { cosinus_val = currentspeed / originalspeed; } player_in_air = true; } #endif if (addspeed <= 0) return; accelspeed = accel * wishspeed * pm_frametime; accelspeed = min(accelspeed, addspeed); VectorMA(pmove.velocity, accelspeed, wishdir, pmove.velocity); if (movevars.bunnyspeedcap > 0) { newspeed = sqrt(pmove.velocity[0] * pmove.velocity[0] + pmove.velocity[1] * pmove.velocity[1]); if (newspeed > originalspeed) { speedcap = movevars.maxspeed * movevars.bunnyspeedcap; if (newspeed > speedcap) { if (originalspeed < speedcap) originalspeed = speedcap; pmove.velocity[0] *= originalspeed / newspeed; pmove.velocity[1] *= originalspeed / newspeed; } } } } static int PM_WaterMove(void) { vec3_t wishvel, wishdir; float wishspeed; int i; // user intentions for (i = 0; i < 3; i++) wishvel[i] = pm_forward[i] * pmove.cmd.forwardmove + pm_right[i] * pmove.cmd.sidemove; if (pmove.pm_type != PM_FLY && !pmove.cmd.forwardmove && !pmove.cmd.sidemove && !pmove.cmd.upmove) wishvel[2] -= 60; // drift towards bottom else wishvel[2] += pmove.cmd.upmove; VectorCopy(wishvel, wishdir); wishspeed = VectorNormalize(wishdir); if (wishspeed > movevars.maxspeed) { VectorScale(wishvel, movevars.maxspeed / wishspeed, wishvel); wishspeed = movevars.maxspeed; } wishspeed *= 0.7; // water acceleration PM_Accelerate(wishdir, wishspeed, movevars.wateraccelerate); return PM_StepSlideMove(false); } static int PM_FlyMove(void) { vec3_t wishvel, wishdir; float wishspeed; int i; for (i = 0; i < 3; i++) wishvel[i] = pm_forward[i] * pmove.cmd.forwardmove + pm_right[i] * pmove.cmd.sidemove; wishvel[2] += pmove.cmd.upmove; VectorCopy(wishvel, wishdir); wishspeed = VectorNormalize(wishdir); if (wishspeed > movevars.maxspeed) { VectorScale(wishvel, movevars.maxspeed / wishspeed, wishvel); wishspeed = movevars.maxspeed; } PM_Accelerate(wishdir, wishspeed, movevars.accelerate); return PM_StepSlideMove(false); } static int PM_AirMove(void) { float fmove, smove, wishspeed; vec3_t wishvel, wishdir; int i; fmove = pmove.cmd.forwardmove; smove = pmove.cmd.sidemove; pm_forward[2] = 0; pm_right[2] = 0; VectorNormalize(pm_forward); VectorNormalize(pm_right); for (i = 0; i < 2; i++) wishvel[i] = pm_forward[i] * fmove + pm_right[i] * smove; wishvel[2] = 0; VectorCopy(wishvel, wishdir); wishspeed = VectorNormalize(wishdir); // clamp to server defined max speed if (wishspeed > movevars.maxspeed) { VectorScale(wishvel, movevars.maxspeed / wishspeed, wishvel); wishspeed = movevars.maxspeed; } if (pmove.onground) { if (movevars.slidefix) { pmove.velocity[2] = min(pmove.velocity[2], 0); // bound above by 0 PM_Accelerate(wishdir, wishspeed, movevars.accelerate); // add gravity pmove.velocity[2] -= movevars.entgravity * movevars.gravity * pm_frametime; } else { pmove.velocity[2] = 0; PM_Accelerate(wishdir, wishspeed, movevars.accelerate); } if (!pmove.velocity[0] && !pmove.velocity[1]) { pmove.velocity[2] = 0; return 0; } return PM_StepSlideMove(false); } else { int blocked; // not on ground, so little effect on velocity PM_AirAccelerate(wishdir, wishspeed, movevars.accelerate); // add gravity pmove.velocity[2] -= movevars.entgravity * movevars.gravity * pm_frametime; if (movevars.airstep) blocked = PM_StepSlideMove(true); else blocked = PM_SlideMove(); if (movevars.pground) { if (blocked & BLOCKED_FLOOR) { pmove.onground = true; } } return blocked; } } #define MAXGROUNDSPEED_DEFAULT 180 #define MAXGROUNDSPEED_MAXIMUM 240 static void PM_RampEdgeAdjustNormal(vec3_t normal, int flags) { int i; for (i = 0; i < 3; ++i) { if (flags & (PHYSICSNORMAL_FLIPX << i)) { if (pmove.velocity[i] < 0) { normal[i] = -normal[i]; } else if (pmove.velocity[0] == 0) { normal[i] = 0; } } } } #define PM_FarFromGround(trace) (((trace).fraction == 1 || (trace).plane.normal[2] < MIN_STEP_NORMAL)) static trace_t PM_CategorizePositionRunTrace(vec3_t point) { trace_t trace = { 0 }; trace = PM_PlayerTrace(pmove.origin, point); if (!PM_FarFromGround(trace)) { VectorCopy(trace.plane.normal, groundnormal); } return trace; } void PM_CategorizePosition(void) { trace_t trace = { 0 }; vec3_t point; int cont; mphysicsnormal_t ground; pmove.maxgroundspeed = MAXGROUNDSPEED_DEFAULT; // if the player hull point one unit down is solid, the player is on ground // see if standing on something solid point[0] = pmove.origin[0]; point[1] = pmove.origin[1]; point[2] = pmove.origin[2] - 1; if (movevars.rampjump) { // Increase speed limit for player as steepness of the floor increases trace = PM_CategorizePositionRunTrace(point); ground = CM_PhysicsNormal(trace.physicsnormal); if (ground.flags & PHYSICSNORMAL_SET) { VectorCopy(ground.normal, groundnormal); PM_RampEdgeAdjustNormal(groundnormal, ground.flags); VectorNormalize(groundnormal); if (movevars.rampjump && !PM_FarFromGround(trace) && trace.e.entnum == 0 && groundnormal[2] > MIN_STEP_NORMAL && groundnormal[2] < 1 && DotProduct(groundnormal, pmove.velocity) < MAX_JUMPFIX_DOTPRODUCT) { // They are moving up a ramp, increase maxspeed to check if we keep them on it float range = 1.0 - asin(groundnormal[2]) * 2 / M_PI; // asin() returns 0...PI/2, so range is [1...0] // Max out at 45 degree ramps... range = min(range, 0.5f) * 2; pmove.maxgroundspeed += (MAXGROUNDSPEED_MAXIMUM - MAXGROUNDSPEED_DEFAULT) * range; } } } if (pmove.velocity[2] > pmove.maxgroundspeed) { pmove.onground = false; } else if (!movevars.pground || pmove.onground) { if (!movevars.rampjump) { trace = PM_CategorizePositionRunTrace(point); } if (PM_FarFromGround(trace)) { pmove.onground = false; } else { pmove.onground = true; pmove.groundent = trace.e.entnum; pmove.waterjumptime = 0; } // standing on an entity other than the world if (trace.e.entnum > 0) { PM_AddTouchedEnt(trace.e.entnum); } } // get waterlevel pmove.waterlevel = 0; pmove.watertype = CONTENTS_EMPTY; point[2] = pmove.origin[2] + player_mins[2] + 1; cont = PM_PointContents (point); if (cont <= CONTENTS_WATER) { pmove.watertype = cont; pmove.waterlevel = 1; point[2] = pmove.origin[2] + (player_mins[2] + player_maxs[2]) * 0.5; cont = PM_PointContents (point); if (cont <= CONTENTS_WATER) { pmove.waterlevel = 2; point[2] = pmove.origin[2] + 22; cont = PM_PointContents (point); if (cont <= CONTENTS_WATER) { pmove.waterlevel = 3; } } } if (!movevars.pground) { if (pmove.onground && pmove.pm_type != PM_FLY && pmove.waterlevel < 2) { // snap to ground so that we can't jump higher than we're supposed to if (!trace.startsolid && !trace.allsolid) { VectorCopy(trace.endpos, pmove.origin); } } } } static void PM_CheckJump (void) { if (pmove.pm_type == PM_FLY) return; if (pmove.pm_type == PM_DEAD) { pmove.jump_held = true; // don't jump on respawn return; } if (!(pmove.cmd.buttons & BUTTON_JUMP)) { pmove.jump_held = false; return; } if (pmove.waterjumptime) { return; } if (pmove.waterlevel >= 2) { // swimming, not jumping pmove.onground = false; if (pmove.watertype == CONTENTS_WATER) pmove.velocity[2] = 100; else if (pmove.watertype == CONTENTS_SLIME) pmove.velocity[2] = 80; else pmove.velocity[2] = 50; return; } if (!pmove.onground) return; // in air, so no effect if (pmove.jump_held && !pmove.jump_msec) return; // don't pogo stick if (!movevars.pground) { // check for jump bug // groundplane normal was set in the call to PM_CategorizePosition if ((movevars.rampjump || pmove.velocity[2] < 0) && DotProduct(pmove.velocity, groundnormal) < MAX_JUMPFIX_DOTPRODUCT) { // pmove.velocity is pointing into the ground, clip it PM_ClipVelocity(pmove.velocity, groundnormal, pmove.velocity, 1); } } pmove.onground = false; if (pmove.maxgroundspeed > MAXGROUNDSPEED_DEFAULT && pmove.velocity[2] > MAXGROUNDSPEED_DEFAULT) { // we adjusted maxspeed to keep them on ground, need to reduce velocity here so they can't jump too high pmove.velocity[2] = MAXGROUNDSPEED_DEFAULT; } pmove.velocity[2] += 270; if (movevars.ktjump > 0) { // meag: pmove.velocity[2] = max(pmove.velocity[2], 270); (?) if (movevars.ktjump > 1) movevars.ktjump = 1; if (pmove.velocity[2] < 270) pmove.velocity[2] = pmove.velocity[2] * (1 - movevars.ktjump) + 270 * movevars.ktjump; } pmove.jump_held = true; // don't jump again until released pmove.jump_msec = pmove.cmd.msec; } static void PM_CheckWaterJump (void) { vec3_t flatforward; vec3_t spot; int cont; if (pmove.waterjumptime) return; // don't hop out if we just jumped in if (pmove.velocity[2] < -180) return; // see if near an edge flatforward[0] = pm_forward[0]; flatforward[1] = pm_forward[1]; flatforward[2] = 0; VectorNormalize (flatforward); VectorMA (pmove.origin, 24, flatforward, spot); spot[2] += 8; cont = PM_PointContents_AllBSPs (spot); if (cont != CONTENTS_SOLID) return; spot[2] += 24; cont = PM_PointContents_AllBSPs (spot); if (cont != CONTENTS_EMPTY) return; // jump out of water VectorScale (flatforward, 50, pmove.velocity); pmove.velocity[2] = 310; pmove.waterjumptime = 2; // safety net pmove.jump_held = true; // don't jump again until released } //If pmove.origin is in a solid position, //try nudging slightly on all axis to //allow for the cut precision of the net coordinates static void PM_NudgePosition (void) { static int sign[3] = {0, -1, 1}; int x, y, z, i; vec3_t base; VectorCopy (pmove.origin, base); for (i = 0; i < 3; i++) pmove.origin[i] = ((int) (pmove.origin[i] * 8)) * 0.125; for (z = 0; z <= 2; z++) { for (y = 0; y <= 2; y++) { for (x = 0; x <= 2; x++) { pmove.origin[0] = base[0] + (sign[x] * 0.125); pmove.origin[1] = base[1] + (sign[y] * 0.125); pmove.origin[2] = base[2] + (sign[z] * 0.125); if (PM_TestPlayerPosition (pmove.origin)) return; } } } // some maps spawn the player several units into the ground for (z = 1; z <= 18; z++) { pmove.origin[0] = base[0]; pmove.origin[1] = base[1]; pmove.origin[2] = base[2] + z; if (PM_TestPlayerPosition(pmove.origin)) return; } VectorCopy (base, pmove.origin); } static void PM_SpectatorMove(void) { float newspeed, currentspeed, addspeed, accelspeed, wishspeed; float speed, drop, friction, control, fmove, smove; vec3_t wishvel, wishdir; int i; // friction speed = VectorLength(pmove.velocity); if (speed < 1) { VectorClear(pmove.velocity); } else { friction = movevars.friction * 1.5; // extra friction control = speed < movevars.stopspeed ? movevars.stopspeed : speed; drop = control * friction * pm_frametime; // scale the velocity newspeed = speed - drop; if (newspeed < 0) { newspeed = 0; } newspeed /= speed; VectorScale(pmove.velocity, newspeed, pmove.velocity); } // accelerate fmove = pmove.cmd.forwardmove; smove = pmove.cmd.sidemove; VectorNormalize(pm_forward); VectorNormalize(pm_right); for (i = 0; i < 3; i++) { wishvel[i] = pm_forward[i] * fmove + pm_right[i] * smove; } wishvel[2] += pmove.cmd.upmove; VectorCopy(wishvel, wishdir); wishspeed = VectorNormalize(wishdir); // clamp to server defined max speed if (wishspeed > movevars.spectatormaxspeed) { VectorScale(wishvel, movevars.spectatormaxspeed / wishspeed, wishvel); wishspeed = movevars.spectatormaxspeed; } currentspeed = DotProduct(pmove.velocity, wishdir); addspeed = wishspeed - currentspeed; // Buggy QW spectator mode, kept for compatibility if (pmove.pm_type == PM_OLD_SPECTATOR) { if (addspeed <= 0) { return; } } if (addspeed > 0) { accelspeed = movevars.accelerate * pm_frametime * wishspeed; accelspeed = min(accelspeed, addspeed); VectorMA(pmove.velocity, accelspeed, wishdir, pmove.velocity); } // move VectorMA(pmove.origin, pm_frametime, pmove.velocity, pmove.origin); } //Returns with origin, angles, and velocity modified in place. //Numtouch and touchindex[] will be set if any of the physents were contacted during the move. int PM_PlayerMove(void) { int blocked = 0; #ifndef SERVERONLY #ifdef EXPERIMENTAL_SHOW_ACCELERATION if (flag_player_pmove) player_in_air = false; #endif #endif pm_frametime = pmove.cmd.msec * 0.001; pmove.numtouch = 0; if (pmove.pm_type == PM_NONE || pmove.pm_type == PM_LOCK) { PM_CategorizePosition(); return 0; } // take angles directly from command VectorCopy(pmove.cmd.angles, pmove.angles); AngleVectors(pmove.angles, pm_forward, pm_right, NULL); if (pmove.pm_type == PM_SPECTATOR || pmove.pm_type == PM_OLD_SPECTATOR) { PM_SpectatorMove(); pmove.onground = false; return 0; } PM_NudgePosition(); // set onground, watertype, and waterlevel PM_CategorizePosition(); if (pmove.waterlevel == 2 && pmove.pm_type != PM_FLY) PM_CheckWaterJump(); if (pmove.velocity[2] < 0 || pmove.pm_type == PM_DEAD) pmove.waterjumptime = 0; if (pmove.waterjumptime) { pmove.waterjumptime -= pm_frametime; if (pmove.waterjumptime < 0) pmove.waterjumptime = 0; } if (pmove.jump_msec) { pmove.jump_msec += pmove.cmd.msec; if (pmove.jump_msec > 50) pmove.jump_msec = 0; } PM_CheckJump(); PM_Friction(); if (pmove.waterlevel >= 2) blocked = PM_WaterMove(); else if (pmove.pm_type == PM_FLY) blocked = PM_FlyMove(); else blocked = PM_AirMove(); // set onground, watertype, and waterlevel for final spot PM_CategorizePosition(); if (!movevars.pground) { // this is to make sure landing sound is not played twice // and falling damage is calculated correctly if (pmove.onground && pmove.velocity[2] < -300) { if (DotProduct(pmove.velocity, groundnormal) < MAX_JUMPFIX_DOTPRODUCT) { PM_ClipVelocity(pmove.velocity, groundnormal, pmove.velocity, 1); } } } return blocked; } mvdsv-0.35/src/pmove.h000066400000000000000000000054711427146041000146740ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef __PMOVE_H__ #define __PMOVE_H__ #define MAX_PHYSENTS 64 typedef struct { vec3_t origin; cmodel_t *model; // only for bsp models vec3_t mins, maxs; // only for non-bsp models int info; // for client or server to identify } physent_t; typedef enum { PM_NORMAL, // normal ground movement PM_OLD_SPECTATOR, // fly, no clip to world (QW bug) PM_SPECTATOR, // fly, no clip to world PM_DEAD, // no acceleration PM_FLY, // fly, bump into walls PM_NONE, // can't move PM_LOCK // server controls origin and view angles } pmtype_t; typedef struct { // player state vec3_t origin; vec3_t angles; vec3_t velocity; qbool jump_held; int jump_msec; // msec since last jump float waterjumptime; int pm_type; // world state int numphysent; physent_t physents[MAX_PHYSENTS]; // 0 should be the world // input usercmd_t cmd; // results int numtouch; int touchindex[MAX_PHYSENTS]; qbool onground; int groundent; // index in physents array, only valid when onground is true int waterlevel; int watertype; int maxgroundspeed; } playermove_t; typedef struct { float gravity; float stopspeed; float maxspeed; float spectatormaxspeed; float accelerate; float airaccelerate; float wateraccelerate; float friction; float waterfriction; float entgravity; float bunnyspeedcap; float ktjump; qbool slidefix; // NQ-style movement down ramps qbool airstep; qbool pground; // NQ-style "onground" flag handling. int rampjump; // if set, all vertical velocity clipped by groundplane during jump frame. If 0, only when falling (standard jumpfix) } movevars_t; extern movevars_t movevars; extern playermove_t pmove; int PM_PlayerMove (void); int PM_PointContents (vec3_t point); int PM_PointContents_AllBSPs (vec3_t p); void PM_CategorizePosition (void); qbool PM_TestPlayerPosition (vec3_t point); trace_t PM_PlayerTrace (vec3_t start, vec3_t end); trace_t PM_TraceLine (vec3_t start, vec3_t end); #define MIN_STEP_NORMAL 0.7 // roughly 45 degrees #endif /* !__PMOVE_H__ */ mvdsv-0.35/src/pmovetst.c000066400000000000000000000141741427146041000154220ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifdef SERVERONLY #include "qwsvdef.h" #else #include "quakedef.h" #include "pmove.h" #endif extern vec3_t player_mins; extern vec3_t player_maxs; static void PM_TraceBounds (vec3_t start, vec3_t end, vec3_t boxmins, vec3_t boxmaxs) { int i; for (i = 0; i < 3; i++) { if (end[i] > start[i]) { boxmins[i] = start[i] - 1; boxmaxs[i] = end[i] + 1; } else { boxmins[i] = end[i] - 1; boxmaxs[i] = start[i] + 1; } } } static qbool PM_CullTraceBox(vec3_t mins, vec3_t maxs, vec3_t offset, vec3_t emins, vec3_t emaxs, vec3_t hullmins, vec3_t hullmaxs) { return ( mins[0] + hullmins[0] > offset[0] + emaxs[0] || maxs[0] + hullmaxs[0] < offset[0] + emins[0] || mins[1] + hullmins[1] > offset[1] + emaxs[1] || maxs[1] + hullmaxs[1] < offset[1] + emins[1] || mins[2] + hullmins[2] > offset[2] + emaxs[2] || maxs[2] + hullmaxs[2] < offset[2] + emins[2] ); } /* ================== PM_PointContents ================== */ int PM_PointContents (vec3_t p) { hull_t *hull = &pmove.physents[0].model->hulls[0]; return CM_HullPointContents (hull, hull->firstclipnode, p); } /* ================== PM_PointContents_AllBSPs Checks world and bsp entities, but not bboxes (like traceline with nomonsters set) For waterjump test ================== */ int PM_PointContents_AllBSPs (vec3_t p) { int i; physent_t *pe; hull_t *hull; vec3_t test; int result, final; final = CONTENTS_EMPTY; for (i = 0; i < pmove.numphysent; i++) { pe = &pmove.physents[i]; if (!pe->model) continue; // ignore non-bsp hull = &pmove.physents[i].model->hulls[0]; VectorSubtract (p, pe->origin, test); result = CM_HullPointContents (hull, hull->firstclipnode, test); if (result == CONTENTS_SOLID) return CONTENTS_SOLID; if (final == CONTENTS_EMPTY) final = result; } return final; } /* ================ PM_TestPlayerPosition Returns false if the given player position is not valid (in solid) ================ */ qbool PM_TestPlayerPosition (vec3_t pos) { int i; physent_t *pe; vec3_t mins, maxs, offset, test; hull_t *hull; for (i = 0; i < pmove.numphysent; i++) { pe = &pmove.physents[i]; // get the clipping hull if (pe->model) { hull = &pmove.physents[i].model->hulls[1]; VectorSubtract(hull->clip_mins, player_mins, offset); VectorAdd(offset, pe->origin, offset); } else { VectorSubtract(pe->mins, player_maxs, mins); VectorSubtract(pe->maxs, player_mins, maxs); hull = CM_HullForBox(mins, maxs); VectorCopy(pe->origin, offset); } VectorSubtract(pos, offset, test); if (CM_HullPointContents(hull, hull->firstclipnode, test) == CONTENTS_SOLID) { return false; } } return true; } /* ================ PM_PlayerTrace ================ */ trace_t PM_PlayerTrace (vec3_t start, vec3_t end) { trace_t trace, total; vec3_t offset; vec3_t start_l, end_l; hull_t *hull; int i; physent_t *pe; vec3_t mins, maxs, tracemins, tracemaxs; // fill in a default trace memset (&total, 0, sizeof(trace_t)); total.fraction = 1; total.e.entnum = -1; VectorCopy (end, total.endpos); PM_TraceBounds(start, end, tracemins, tracemaxs); for (i = 0; i < pmove.numphysent; i++) { pe = &pmove.physents[i]; // get the clipping hull if (pe->model) { hull = &pmove.physents[i].model->hulls[1]; if (i > 0 && PM_CullTraceBox(tracemins, tracemaxs, pe->origin, pe->model->mins, pe->model->maxs, hull->clip_mins, hull->clip_maxs)) { continue; } VectorSubtract(hull->clip_mins, player_mins, offset); VectorAdd(offset, pe->origin, offset); } else { VectorSubtract(pe->mins, player_maxs, mins); VectorSubtract(pe->maxs, player_mins, maxs); if (PM_CullTraceBox(tracemins, tracemaxs, pe->origin, mins, maxs, vec3_origin, vec3_origin)) { continue; } hull = CM_HullForBox(mins, maxs); VectorCopy(pe->origin, offset); } VectorSubtract(start, offset, start_l); VectorSubtract(end, offset, end_l); // trace a line through the appropriate clipping hull trace = CM_HullTrace(hull, start_l, end_l); // fix trace up by the offset VectorAdd(trace.endpos, offset, trace.endpos); if (trace.allsolid) { trace.startsolid = true; } if (trace.startsolid) { trace.fraction = 0; } // did we clip the move? if (trace.fraction < total.fraction) { total = trace; total.e.entnum = i; } } return total; } /* ================ PM_TraceLine ================ */ trace_t PM_TraceLine (vec3_t start, vec3_t end) { int i; hull_t *hull; physent_t *pe; vec3_t offset, start_l, end_l; trace_t trace, total; // fill in a default trace memset (&total, 0, sizeof(trace_t)); total.fraction = 1; total.e.entnum = -1; VectorCopy (end, total.endpos); for (i = 0; i < pmove.numphysent; i++) { pe = &pmove.physents[i]; // get the clipping hull hull = (pe->model) ? (&pmove.physents[i].model->hulls[0]) : (CM_HullForBox(pe->mins, pe->maxs)); // PM_HullForEntity (ent, mins, maxs, offset); VectorCopy(pe->origin, offset); VectorSubtract(start, offset, start_l); VectorSubtract(end, offset, end_l); // trace a line through the appropriate clipping hull trace = CM_HullTrace(hull, start_l, end_l); // fix trace up by the offset VectorAdd(trace.endpos, offset, trace.endpos); if (trace.allsolid) { trace.startsolid = true; } if (trace.startsolid) { trace.fraction = 0; } // did we clip the move? if (trace.fraction < total.fraction) { total = trace; total.e.entnum = i; } } return total; } mvdsv-0.35/src/pr2.h000066400000000000000000000066621427146041000142540ustar00rootroot00000000000000/* * QW262 * Copyright (C) 2004 [sd] angel * * 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 * * */ #ifndef __PR2_H__ #define __PR2_H__ intptr_t PR2_GameSystemCalls( intptr_t *args ); extern cvar_t sv_progtype; extern vm_t* sv_vm; void PR2_Init(void); #define PR_Init PR2_Init void PR2_UnLoadProgs(void); #define PR_UnLoadProgs PR2_UnLoadProgs void PR2_LoadProgs(void); #define PR_LoadProgs PR2_LoadProgs void PR2_GameStartFrame(qbool isBotFrame); #define PR_GameStartFrame PR2_GameStartFrame void PR2_LoadEnts(char *data); #define PR_LoadEnts PR2_LoadEnts void PR2_GameClientConnect(int spec); #define PR_GameClientConnect PR2_GameClientConnect void PR2_GamePutClientInServer(int spec); #define PR_GamePutClientInServer PR2_GamePutClientInServer void PR2_GameClientDisconnect(int spec); #define PR_GameClientDisconnect PR2_GameClientDisconnect void PR2_GameClientPreThink(int spec); #define PR_GameClientPreThink PR2_GameClientPreThink void PR2_GameClientPostThink(int spec); #define PR_GameClientPostThink PR2_GameClientPostThink qbool PR2_ClientCmd(void); #define PR_ClientCmd PR2_ClientCmd void PR2_ClientKill(void); #define PR_ClientKill PR2_ClientKill qbool PR2_ClientSay(int isTeamSay, char *message); #define PR_ClientSay PR2_ClientSay void PR2_GameSetNewParms(void); #define PR_GameSetNewParms PR2_GameSetNewParms void PR2_GameSetChangeParms(void); #define PR_GameSetChangeParms PR2_GameSetChangeParms void PR2_EdictTouch(func_t f); #define PR_EdictTouch PR2_EdictTouch void PR2_EdictThink(func_t f); #define PR_EdictThink PR2_EdictThink void PR2_EdictBlocked(func_t f); #define PR_EdictBlocked PR2_EdictBlocked qbool PR2_UserInfoChanged(int after); #define PR_UserInfoChanged PR2_UserInfoChanged void PR2_GameShutDown(void); #define PR_GameShutDown PR2_GameShutDown void PR2_GameConsoleCommand(void); void PR2_PausedTic(float duration); #define PR_PausedTic PR2_PausedTic char* PR2_GetString(intptr_t reference); //#define PR_GetString PR2_GetString char* PR2_GetEntityString(string_t reference); #define PR_GetEntityString PR2_GetEntityString void PR2_SetEntityString(edict_t* ed, string_t* target, char* value); void PR2_SetGlobalString(string_t* target, char* value); #define PR_SetEntityString(entity, address, value) PR2_SetEntityString(entity, &address, value) #define PR_SetGlobalString(address, value) PR2_SetGlobalString(&address, value) void PR2_RunError(char *error, ...); eval_t* PR2_GetEdictFieldValue(edict_t *ed, char *field); #define PR_GetEdictFieldValue PR2_GetEdictFieldValue int ED2_FindFieldOffset(char *field); #define ED_FindFieldOffset ED2_FindFieldOffset void PR2_InitProg(void); #define PR_InitProg PR2_InitProg void PR2_ClearEdict(edict_t* e); #define PR_ClearEdict PR2_ClearEdict #endif /* !__PR2_H__ */ mvdsv-0.35/src/pr2_cmds.c000066400000000000000000001635701427146041000152570ustar00rootroot00000000000000/* * QW262 * Copyright (C) 2004 [sd] angel * * 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 * * * */ #ifndef CLIENTONLY #ifdef USE_PR2 #include "qwsvdef.h" #include "vm.h" #include "vm_local.h" #define SETUSERINFO_STAR (1<<0) // allow set star keys #ifdef SERVERONLY #define Cbuf_AddTextEx(x, y) Cbuf_AddText(y) #define Cbuf_ExecuteEx(x) Cbuf_Execute() #endif const char *pr2_ent_data_ptr; vm_t *sv_vm = NULL; extern gameData_t gamedata; static int PASSFLOAT(float f) { floatint_t fi; fi.f = f; return fi.i; } #if 0 // Provided for completness. static float GETFLOAT(int i) { floatint_t fi; fi.i = i; return fi.f; } #endif int NUM_FOR_GAME_EDICT(byte *e) { int b; b = (byte *)e - (byte *)sv.game_edicts; b /= pr_edict_size; if (b < 0 || b >= sv.num_edicts) SV_Error("NUM_FOR_GAME_EDICT: bad pointer"); return b; } intptr_t PR2_EntityStringLocation(string_t offset, int max_size); void static PR2_SetEntityString_model(edict_t *ed, string_t *target, char *s) { if (!sv_vm) { PR1_SetString(target, s); return; } switch (sv_vm->type) { case VMI_NONE: PR1_SetString(target, s); return; case VMI_NATIVE: if (sv_vm->pr2_references) { char **location = (char **)PR2_EntityStringLocation(*target, sizeof(char *)); if (location) { *location = s; } } #ifndef idx64 else if (target) { *target = (string_t)s; } #endif return; case VMI_BYTECODE: case VMI_COMPILED: { int off = VM_ExplicitPtr2VM(sv_vm, (byte *)s); if (sv_vm->pr2_references) { string_t *location = (string_t *)PR2_EntityStringLocation(*target, sizeof(string_t)); if (location) { *location = off; } } else { *target = off; } } return; } *target = 0; } /* ============ PR2_RunError Aborts the currently executing function ============ */ void PR2_RunError(char *error, ...) { va_list argptr; char string[1024]; va_start(argptr, error); vsnprintf(string, sizeof(string), error, argptr); va_end(argptr); sv_error = true; Con_Printf("%s\n", string); SV_Error("Program error: %s", string); } void PR2_CheckEmptyString(char *s) { if (!s || s[0] <= ' ') PR2_RunError("Bad string"); } void PF2_precache_sound(char *s) { int i; if (sv.state != ss_loading) PR2_RunError("PF_Precache_*: Precache can only be done in spawn " "functions"); PR2_CheckEmptyString(s); for (i = 0; i < MAX_SOUNDS; i++) { if (!sv.sound_precache[i]) { sv.sound_precache[i] = s; return; } if (!strcmp(sv.sound_precache[i], s)) return; } PR2_RunError ("PF_precache_sound: overflow"); } void PF2_precache_model(char *s) { int i; if (sv.state != ss_loading) PR2_RunError("PF_Precache_*: Precache can only be done in spawn " "functions"); PR2_CheckEmptyString(s); for (i = 0; i < MAX_MODELS; i++) { if (!sv.model_precache[i]) { sv.model_precache[i] = s; return; } if (!strcmp(sv.model_precache[i], s)) return; } PR2_RunError ("PF_precache_model: overflow"); } intptr_t PF2_precache_vwep_model(char *s) { int i; if (sv.state != ss_loading) PR2_RunError("PF_Precache_*: Precache can only be done in spawn " "functions"); PR2_CheckEmptyString(s); // the strings are transferred via the stufftext mechanism, hence the stringency if (strchr(s, '"') || strchr(s, ';') || strchr(s, '\n' ) || strchr(s, '\t') || strchr(s, ' ')) PR2_RunError ("Bad string\n"); for (i = 0; i < MAX_VWEP_MODELS; i++) { if (!sv.vw_model_name[i]) { sv.vw_model_name[i] = s; return i; } } PR2_RunError ("PF_precache_vwep_model: overflow"); return 0; } /* ================= PF2_setorigin This is the only valid way to move an object without using the physics of the world (setting velocity and waiting). Directly changing origin will not set internal links correctly, so clipping would be messed up. This should be called when an object is spawned, and then only if it is teleported. setorigin (entity, origin) ================= */ void PF2_setorigin(edict_t *e, float x, float y, float z) { vec3_t origin; origin[0] = x; origin[1] = y; origin[2] = z; VectorCopy(origin, e->v->origin); SV_AntilagReset(e); SV_LinkEdict(e, false); } /* ================= PF2_setsize the size box is rotated by the current angle setsize (entity, minvector, maxvector) ================= */ void PF2_setsize(edict_t *e, float x1, float y1, float z1, float x2, float y2, float z2) { // vec3_t min, max; e->v->mins[0] = x1; e->v->mins[1] = y1; e->v->mins[2] = z1; e->v->maxs[0] = x2; e->v->maxs[1] = y2; e->v->maxs[2] = z2; VectorSubtract(e->v->maxs, e->v->mins, e->v->size); SV_LinkEdict(e, false); } /* ================= PF2_setmodel setmodel(entity, model) Also sets size, mins, and maxs for inline bmodels ================= */ void PF2_setmodel(edict_t *e, char *m) { char **check; int i; cmodel_t *mod; if (!m) m = ""; // check to see if model was properly precached for (i = 0, check = sv.model_precache; *check; i++, check++) if (!strcmp(*check, m)) break; if (!*check) PR2_RunError("no precache: %s\n", m); PR2_SetEntityString_model(e, &e->v->model, m); e->v->modelindex = i; // if it is an inline model, get the size information for it if (m[0] == '*') { mod = CM_InlineModel (m); VectorCopy(mod->mins, e->v->mins); VectorCopy(mod->maxs, e->v->maxs); VectorSubtract(mod->maxs, mod->mins, e->v->size); SV_LinkEdict(e, false); } } /* ================= PF2_sprint single print to a specific client sprint(clientent, value) ================= */ // trap_SPrint() flags #define SPRINT_IGNOREINDEMO ( 1<<0) // do not put such message in mvd demo void PF2_sprint(int entnum, int level, char *s, int flags) { client_t *client, *cl; int i; if (gamedata.APIversion < 15) flags = 0; if (entnum < 1 || entnum > MAX_CLIENTS) { Con_Printf("tried to sprint to a non-client %d \n", entnum); return; } client = &svs.clients[entnum - 1]; // do not print to client in such state if (client->state < cs_connected) return; if (flags & SPRINT_IGNOREINDEMO) SV_ClientPrintf2 (client, level, "%s", s); // this does't go to mvd demo else SV_ClientPrintf (client, level, "%s", s); // this will be in mvd demo too //bliP: spectator print -> if ((int)sv_specprint.value & SPECPRINT_SPRINT) { for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (!cl->state || !cl->spectator) continue; if ((cl->spec_track == entnum) && (cl->spec_print & SPECPRINT_SPRINT)) { if (flags & SPRINT_IGNOREINDEMO) SV_ClientPrintf2 (cl, level, "%s", s); // this does't go to mvd demo else SV_ClientPrintf (cl, level, "%s", s); // this will be in mvd demo too } } } //<- } /* ================= PF2_centerprint single print to a specific client centerprint(clientent, value) ================= */ void PF2_centerprint(int entnum, char *s) { client_t *cl, *spec; int i; if (entnum < 1 || entnum > MAX_CLIENTS) { Con_Printf("tried to centerprint to a non-client %d \n", entnum); return; } cl = &svs.clients[entnum - 1]; ClientReliableWrite_Begin(cl, svc_centerprint, 2 + strlen(s)); ClientReliableWrite_String(cl, s); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, entnum - 1, 2 + strlen(s))) { MVD_MSG_WriteByte(svc_centerprint); MVD_MSG_WriteString(s); } } //bliP: spectator print -> if ((int)sv_specprint.value & SPECPRINT_CENTERPRINT) { for (i = 0, spec = svs.clients; i < MAX_CLIENTS; i++, spec++) { if (!cl->state || !spec->spectator) continue; if ((spec->spec_track == entnum) && (cl->spec_print & SPECPRINT_CENTERPRINT)) { ClientReliableWrite_Begin (spec, svc_centerprint, 2 + strlen(s)); ClientReliableWrite_String (spec, s); } } } //<- } /* ================= PF2_ambientsound ================= */ void PF2_ambientsound(float x, float y, float z, char *samp, float vol, float attenuation) { char **check; int i, soundnum; vec3_t pos; pos[0] = x; pos[1] = y; pos[2] = z; if( !samp ) samp = ""; // check to see if samp was properly precached for (soundnum = 0, check = sv.sound_precache; *check; check++, soundnum++) if (!strcmp(*check, samp)) break; if (!*check) { Con_Printf("no precache: %s\n", samp); return; } // add an svc_spawnambient command to the level signon packet MSG_WriteByte(&sv.signon, svc_spawnstaticsound); for (i = 0; i < 3; i++) MSG_WriteCoord(&sv.signon, pos[i]); MSG_WriteByte(&sv.signon, soundnum); MSG_WriteByte(&sv.signon, vol * 255); MSG_WriteByte(&sv.signon, attenuation * 64); } /* ================= PF2_traceline Used for use tracing and shot targeting Traces are blocked by bbox and exact bsp entityes, and also slide box entities if the tryents flag is set. traceline (vector1, vector2, tryents) ================= */ void PF2_traceline(float v1_x, float v1_y, float v1_z, float v2_x, float v2_y, float v2_z, int nomonsters, int entnum) { trace_t trace; edict_t *ent; vec3_t v1, v2; ent = EDICT_NUM(entnum); v1[0] = v1_x; v1[1] = v1_y; v1[2] = v1_z; v2[0] = v2_x; v2[1] = v2_y; v2[2] = v2_z; if (sv_antilag.value == 2) { if (!(entnum >= 1 && entnum <= MAX_CLIENTS && svs.clients[entnum - 1].isBot)) { nomonsters |= MOVE_LAGGED; } } trace = SV_Trace(v1, vec3_origin, vec3_origin, v2, nomonsters, ent); pr_global_struct->trace_allsolid = trace.allsolid; pr_global_struct->trace_startsolid = trace.startsolid; pr_global_struct->trace_fraction = trace.fraction; pr_global_struct->trace_inwater = trace.inwater; pr_global_struct->trace_inopen = trace.inopen; VectorCopy (trace.endpos, pr_global_struct->trace_endpos); VectorCopy (trace.plane.normal, pr_global_struct->trace_plane_normal); pr_global_struct->trace_plane_dist = trace.plane.dist; if (trace.e.ent) pr_global_struct->trace_ent = EDICT_TO_PROG(trace.e.ent); else pr_global_struct->trace_ent = EDICT_TO_PROG(sv.edicts); } /* ================= PF2_TraceCapsule Used for use tracing and shot targeting Traces are blocked by bbox and exact bsp entityes, and also slide box entities if the tryents flag is set. ================= */ void PF2_TraceCapsule(float v1_x, float v1_y, float v1_z, float v2_x, float v2_y, float v2_z, int nomonsters, edict_t *ent, float min_x, float min_y, float min_z, float max_x, float max_y, float max_z) { trace_t trace; vec3_t v1, v2, v3, v4; v1[0] = v1_x; v1[1] = v1_y; v1[2] = v1_z; v2[0] = v2_x; v2[1] = v2_y; v2[2] = v2_z; v3[0] = min_x; v3[1] = min_y; v3[2] = min_z; v4[0] = max_x; v4[1] = max_y; v4[2] = max_z; trace = SV_Trace(v1, v3, v4, v2, nomonsters, ent); pr_global_struct->trace_allsolid = trace.allsolid; pr_global_struct->trace_startsolid = trace.startsolid; pr_global_struct->trace_fraction = trace.fraction; pr_global_struct->trace_inwater = trace.inwater; pr_global_struct->trace_inopen = trace.inopen; VectorCopy (trace.endpos, pr_global_struct->trace_endpos); VectorCopy (trace.plane.normal, pr_global_struct->trace_plane_normal); pr_global_struct->trace_plane_dist = trace.plane.dist; if (trace.e.ent) pr_global_struct->trace_ent = EDICT_TO_PROG(trace.e.ent); else pr_global_struct->trace_ent = EDICT_TO_PROG(sv.edicts); } /* ================= PF2_checkclient Returns a client (or object that has a client enemy) that would be a valid target. If there are more than one valid options, they are cycled each frame If (self.origin + self.viewofs) is not in the PVS of the current target, it is not returned at all. name checkclient () ================= */ static byte *checkpvs; int PF2_newcheckclient(int check) { int i; edict_t *ent; vec3_t org; // cycle to the next one if (check < 1) check = 1; if (check > MAX_CLIENTS) check = MAX_CLIENTS; if (check == MAX_CLIENTS) i = 1; else i = check + 1; for ( ; ; i++) { if (i == MAX_CLIENTS + 1) i = 1; ent = EDICT_NUM(i); if (i == check) break; // didn't find anything else if (ent->e.free) continue; if (ent->v->health <= 0) continue; if ((int) ent->v->flags & FL_NOTARGET) continue; // anything that is a client, or has a client as an enemy break; } // get the PVS for the entity VectorAdd (ent->v->origin, ent->v->view_ofs, org); checkpvs = CM_LeafPVS (CM_PointInLeaf(org)); return i; } #define MAX_CHECK 16 intptr_t PF2_checkclient(void) { edict_t *ent, *self; int l; vec3_t view; // find a new check if on a new frame if (sv.time - sv.lastchecktime >= 0.1) { sv.lastcheck = PF2_newcheckclient(sv.lastcheck); sv.lastchecktime = sv.time; } // return check if it might be visible ent = EDICT_NUM(sv.lastcheck); if (ent->e.free || ent->v->health <= 0) { // RETURN_EDICT(sv.edicts); return NUM_FOR_EDICT(sv.edicts); } // if current entity can't possibly see the check entity, return 0 self = PROG_TO_EDICT(pr_global_struct->self); VectorAdd(self->v->origin, self->v->view_ofs, view); l = CM_Leafnum(CM_PointInLeaf(view)) - 1; if ((l < 0) || !(checkpvs[l >> 3] & (1 << (l & 7)))) { return NUM_FOR_EDICT(sv.edicts); } return NUM_FOR_EDICT(ent); } //============================================================================ // modified by Tonik /* ================= PF2_stuffcmd Sends text over to the client's execution buffer stuffcmd (clientent, value) ================= */ // trap_stuffcmd() flags #define STUFFCMD_IGNOREINDEMO ( 1<<0) // do not put in mvd demo #define STUFFCMD_DEMOONLY ( 1<<1) // put in mvd demo only void PF2_stuffcmd(int entnum, char *str, int flags) { char *buf = NULL; client_t *cl, *spec; int j; if (gamedata.APIversion < 15) flags = 0; if( !str ) PR2_RunError("PF2_stuffcmd: NULL pointer"); // put in mvd demo only if (flags & STUFFCMD_DEMOONLY) { if (strchr( str, '\n' )) // we have \n trail { if (sv.mvdrecording) { if (MVDWrite_Begin(dem_all, 0, 2 + strlen(str))) { MVD_MSG_WriteByte(svc_stufftext); MVD_MSG_WriteString(str); } } } return; // do not send to client in any case } if (entnum < 1 || entnum > MAX_CLIENTS) PR2_RunError("Parm 0 not a client"); cl = &svs.clients[entnum - 1]; if (!strncmp(str, "disconnect\n", MAX_STUFFTEXT)) { // so long and thanks for all the fish cl->drop = true; return; } buf = cl->stufftext_buf; if (strlen(buf) + strlen(str) >= MAX_STUFFTEXT) PR2_RunError("stufftext buffer overflow"); strlcat (buf, str, MAX_STUFFTEXT); if( strchr( buf, '\n' ) ) { ClientReliableWrite_Begin(cl, svc_stufftext, 2 + strlen(buf)); ClientReliableWrite_String(cl, buf); if (!(flags & STUFFCMD_IGNOREINDEMO)) // STUFFCMD_IGNOREINDEMO flag is NOT set { if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 2 + strlen(buf))) { MVD_MSG_WriteByte(svc_stufftext); MVD_MSG_WriteString(buf); } } } //bliP: spectator print -> if ((int)sv_specprint.value & SPECPRINT_STUFFCMD) { for (j = 0, spec = svs.clients; j < MAX_CLIENTS; j++, spec++) { if (!cl->state || !spec->spectator) continue; if ((spec->spec_track == entnum) && (cl->spec_print & SPECPRINT_STUFFCMD)) { ClientReliableWrite_Begin (spec, svc_stufftext, 2+strlen(buf)); ClientReliableWrite_String (spec, buf); } } } buf[0] = 0; } } /* ================= PF2_executecmd ================= */ void PF2_executecmd(void) { int old_other, old_self; // mod_consolecmd will be executed, so we need to store this old_self = pr_global_struct->self; old_other = pr_global_struct->other; Cbuf_ExecuteEx(&cbuf_server); pr_global_struct->self = old_self; pr_global_struct->other = old_other; } /* ================= PF2_readcmd void readmcmd (string str,string buff, int sizeofbuff) ================= */ void PF2_readcmd(char *str, char *buf, int sizebuff) { extern char outputbuf[]; extern redirect_t sv_redirected; redirect_t old; Cbuf_ExecuteEx(&cbuf_server); Cbuf_AddTextEx(&cbuf_server, str); old = sv_redirected; if (old != RD_NONE) SV_EndRedirect(); SV_BeginRedirect(RD_MOD); Cbuf_ExecuteEx(&cbuf_server); strlcpy(buf, outputbuf, sizebuff); SV_EndRedirect(); if (old != RD_NONE) SV_BeginRedirect(old); } /* ================= PF2_redirectcmd void redirectcmd (entity to, string str) ================= */ void PF2_redirectcmd(int entnum, char *str) { extern redirect_t sv_redirected; if (sv_redirected) { Cbuf_AddTextEx(&cbuf_server, str); Cbuf_ExecuteEx(&cbuf_server); return; } if (entnum < 1 || entnum > MAX_CLIENTS) { PR2_RunError("Parm 0 not a client"); } SV_BeginRedirect((redirect_t)(RD_MOD + entnum)); Cbuf_AddTextEx(&cbuf_server, str); Cbuf_ExecuteEx(&cbuf_server); SV_EndRedirect(); } /* ================= PF2_FindRadius Returns a chain of entities that have origins within a spherical area gedict_t *findradius( gedict_t * start, vec3_t org, float rad ); ================= */ intptr_t PF2_FindRadius(int e, float *org, float rad) { int j; edict_t *ed; vec3_t eorg; for ( e++; e < sv.num_edicts; e++ ) { ed = EDICT_NUM( e ); if (ed->e.free) continue; if (ed->v->solid == SOLID_NOT) continue; for (j=0 ; j<3 ; j++) eorg[j] = org[j] - (ed->v->origin[j] + (ed->v->mins[j] + ed->v->maxs[j])*0.5); if (VectorLength(eorg) > rad) continue; return VM_Ptr2VM((byte *)ed->v); } return 0; } /* =============== PF2_walkmove float(float yaw, float dist) walkmove =============== */ int PF2_walkmove(edict_t *ent, float yaw, float dist) { vec3_t move; int oldself; int ret; if (!((int) ent->v->flags & (FL_ONGROUND | FL_FLY | FL_SWIM))) { return 0; } yaw = yaw * M_PI * 2 / 360; move[0] = cos(yaw) * dist; move[1] = sin(yaw) * dist; move[2] = 0; // save program state, because SV_movestep may call other progs // oldf = pr_xfunction; oldself = pr_global_struct->self; ret = SV_movestep(ent, move, true); // restore program state // pr_xfunction = oldf; pr_global_struct->self = oldself; return ret; } /* =============== PF2_MoveToGoal float(float dist) PF2_MoveToGoal =============== */ void PF2_MoveToGoal(float dist) { edict_t *ent, *goal; // dfunction_t *oldf; int oldself; ent = PROG_TO_EDICT(pr_global_struct->self); goal = PROG_TO_EDICT(ent->v->goalentity); if ( !( (int)ent->v->flags & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) { return; } // if the next step hits the enemy, return immediately if ( PROG_TO_EDICT(ent->v->enemy) != sv.edicts && SV_CloseEnough (ent, goal, dist) ) return; // save program state, because SV_movestep may call other progs // oldf = pr_xfunction; oldself = pr_global_struct->self; // bump around... if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->v->ideal_yaw, dist)) { SV_NewChaseDir (ent, goal, dist); } // restore program state // pr_xfunction = oldf; pr_global_struct->self = oldself; } /* =============== PF2_droptofloor void(entnum) droptofloor =============== */ int PF2_droptofloor(edict_t *ent) { vec3_t end; trace_t trace; VectorCopy(ent->v->origin, end); end[2] -= 256; trace = SV_Trace(ent->v->origin, ent->v->mins, ent->v->maxs, end, false, ent); if (trace.fraction == 1 || trace.allsolid) { return 0; } else { VectorCopy(trace.endpos, ent->v->origin); SV_LinkEdict(ent, false); ent->v->flags = (int) ent->v->flags | FL_ONGROUND; ent->v->groundentity = EDICT_TO_PROG(trace.e.ent); return 1; } } /* =============== PF2_lightstyle void(int style, string value) lightstyle =============== */ void PF2_lightstyle(int style, char *val) { client_t *client; int j; // change the string in sv sv.lightstyles[style] = val; // send message to all clients on this server if (sv.state != ss_active) return; for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) if (client->state == cs_spawned) { ClientReliableWrite_Begin(client, svc_lightstyle, strlen(val) + 3); ClientReliableWrite_Char(client, style); ClientReliableWrite_String(client, val); } if (sv.mvdrecording) { if (MVDWrite_Begin(dem_all, 0, strlen(val) + 3)) { MVD_MSG_WriteByte(svc_lightstyle); MVD_MSG_WriteChar(style); MVD_MSG_WriteString(val); } } } /* ============= PF2_pointcontents ============= */ int PF2_pointcontents(float x, float y, float z) { vec3_t origin; origin[0] = x; origin[1] = y; origin[2] = z; return SV_PointContents(origin); } /* ============= PF2_nextent entity nextent(entity) ============= */ intptr_t PF2_nextent(int i) { edict_t *ent; while (1) { i++; if (i >= sv.num_edicts) { return 0; } ent = EDICT_NUM(i); if (!ent->e.free) { return i; } } } /* ============= PF2_nextclient fast walk over spawned clients entity nextclient(entity) ============= */ intptr_t PF2_nextclient(int i) { edict_t *ent; while (1) { i++; if (i < 1 || i > MAX_CLIENTS) { return 0; } ent = EDICT_NUM(i); if (!ent->e.free) // actually that always true for clients edicts { if (svs.clients[i - 1].state == cs_spawned) // client in game { return VM_Ptr2VM((byte *)ent->v); } } } } /* ============= PF2_find entity find(start,fieldoff,str) ============= */ intptr_t PF2_Find(int e, int fofs, char *str) { char *t; edict_t *ed; if(!str) PR2_RunError ("PF2_Find: bad search string"); for (e++ ; e < sv.num_edicts ; e++) { ed = EDICT_NUM(e); if (ed->e.free) continue; if (!(intptr_t *)((byte *)ed->v + fofs)) continue; t = VM_ArgPtr(*(intptr_t *)((char *)ed->v + fofs)); if (!t) continue; if (!strcmp(t,str)) { return VM_Ptr2VM((byte *)ed->v); } } return 0; } /* ============= PF2_aim ?????? Pick a vector for the player to shoot along vector aim(entity, missilespeed) ============= */ /* ============== PF2_changeyaw ??? This was a major timewaster in progs, so it was converted to C ============== */ /* =============================================================================== MESSAGE WRITING =============================================================================== */ #define MSG_BROADCAST 0 // unreliable to all #define MSG_ONE 1 // reliable to one (msg_entity) #define MSG_ALL 2 // reliable to all #define MSG_INIT 3 // write to the init string #define MSG_MULTICAST 4 // for multicast() sizebuf_t *WriteDest2(int dest) { // int entnum; // int dest; // edict_t *ent; //dest = G_FLOAT(OFS_PARM0); switch (dest) { case MSG_BROADCAST: return &sv.datagram; case MSG_ONE: SV_Error("Shouldn't be at MSG_ONE"); #if 0 ent = PROG_TO_EDICT(pr_global_struct->msg_entity); entnum = NUM_FOR_EDICT(ent); if (entnum < 1 || entnum > MAX_CLIENTS) PR2_RunError("WriteDest: not a client"); return &svs.clients[entnum - 1].netchan.message; #endif case MSG_ALL: return &sv.reliable_datagram; case MSG_INIT: if (sv.state != ss_loading) PR2_RunError("PF_Write_*: MSG_INIT can only be written in spawn " "functions"); return &sv.signon; case MSG_MULTICAST: return &sv.multicast; default: PR2_RunError ("WriteDest: bad destination"); break; } return NULL; } static client_t *Write_GetClient(void) { int entnum; edict_t *ent; ent = PROG_TO_EDICT(pr_global_struct->msg_entity); entnum = NUM_FOR_EDICT(ent); if (entnum < 1 || entnum > MAX_CLIENTS) PR2_RunError("WriteDest: not a client"); return &svs.clients[entnum - 1]; } void PF2_WriteByte(int to, int data) { if (to == MSG_ONE) { client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, 1); ClientReliableWrite_Byte(cl, data); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 1)) { MVD_MSG_WriteByte(data); } } } else MSG_WriteByte(WriteDest2(to), data); } void PF2_WriteChar(int to, int data) { if (to == MSG_ONE) { client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, 1); ClientReliableWrite_Char(cl, data); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 1)) { MVD_MSG_WriteByte(data); } } } else MSG_WriteChar(WriteDest2(to), data); } void PF2_WriteShort(int to, int data) { if (to == MSG_ONE) { client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, 2); ClientReliableWrite_Short(cl, data); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 2)) { MVD_MSG_WriteShort(data); } } } else MSG_WriteShort(WriteDest2(to), data); } void PF2_WriteLong(int to, int data) { if (to == MSG_ONE) { client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, 4); ClientReliableWrite_Long(cl, data); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 4)) { MVD_MSG_WriteLong(data); } } } else MSG_WriteLong(WriteDest2(to), data); } void PF2_WriteAngle(int to, float data) { if (to == MSG_ONE) { #ifdef FTE_PEXT_FLOATCOORDS int size = msg_anglesize; #else int size = 1; #endif client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, size); ClientReliableWrite_Angle(cl, data); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, size)) { MVD_MSG_WriteAngle(data); } } } else MSG_WriteAngle(WriteDest2(to), data); } void PF2_WriteCoord(int to, float data) { if (to == MSG_ONE) { #ifdef FTE_PEXT_FLOATCOORDS int size = msg_coordsize; #else int size = 2; #endif client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, size); ClientReliableWrite_Coord(cl, data); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, size)) { MVD_MSG_WriteCoord(data); } } } else MSG_WriteCoord(WriteDest2(to), data); } void PF2_WriteString(int to, char *data) { if (to == MSG_ONE) { client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, 1 + strlen(data)); ClientReliableWrite_String(cl, data); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 1 + strlen(data))) { MVD_MSG_WriteString(data); } } } else MSG_WriteString(WriteDest2(to), data); } void PF2_WriteEntity(int to, int data) { if (to == MSG_ONE) { client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, 2); ClientReliableWrite_Short(cl,data );//G_EDICTNUM(OFS_PARM1) if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 2)) { MVD_MSG_WriteShort(data); } } } else MSG_WriteShort(WriteDest2(to), data); } //============================================================================= int SV_ModelIndex(char *name); /* ================== PF2_makestatic ================== */ void PF2_makestatic(edict_t *ent) { entity_state_t *s; if (sv.static_entity_count >= sizeof(sv.static_entities) / sizeof(sv.static_entities[0])) { ED_Free (ent); return; } s = &sv.static_entities[sv.static_entity_count]; memset(s, 0, sizeof(sv.static_entities[0])); s->number = sv.static_entity_count + 1; s->modelindex = SV_ModelIndex(PR_GetEntityString(ent->v->model)); if (!s->modelindex) { ED_Free (ent); return; } s->frame = ent->v->frame; s->colormap = ent->v->colormap; s->skinnum = ent->v->skin; VectorCopy(ent->v->origin, s->origin); VectorCopy(ent->v->angles, s->angles); ++sv.static_entity_count; // throw the entity away now ED_Free(ent); } //============================================================================= /* ============== PF2_setspawnparms ============== */ void PF2_setspawnparms(int entnum) { int i; client_t *client; if (entnum < 1 || entnum > MAX_CLIENTS) PR2_RunError("Entity is not a client"); // copy spawn parms out of the client_t client = svs.clients + (entnum - 1); for (i = 0; i < NUM_SPAWN_PARMS; i++) (&pr_global_struct->parm1)[i] = client->spawn_parms[i]; } /* ============== PF2_changelevel ============== */ void PF2_changelevel(char *s, char *entfile) { static int last_spawncount; char expanded[MAX_QPATH]; if (gamedata.APIversion < 15) entfile = ""; // check to make sure the level exists. // this is work around for bellow check about two changelevels, // which lock server in one map if we trying switch to map which does't exist snprintf(expanded, MAX_QPATH, "maps/%s.bsp", s); if (!FS_FLocateFile(expanded, FSLFRT_IFFOUND, NULL)) { Sys_Printf ("Can't find %s\n", expanded); return; } // make sure we don't issue two changelevels // this check is evil and cause lock on one map, if /map command fail for some reason if (svs.spawncount == last_spawncount) return; last_spawncount = svs.spawncount; if (entfile && *entfile) { Cbuf_AddTextEx(&cbuf_server, va("map %s %s\n", s, entfile)); } else { Cbuf_AddTextEx(&cbuf_server, va("map %s\n", s)); } } /* ============== PF2_logfrag logfrag (killer, killee) ============== */ void PF2_logfrag(int e1, int e2) { char *s; // -> scream time_t t; struct tm *tblock; if (e1 < 1 || e1 > MAX_CLIENTS || e2 < 1 || e2 > MAX_CLIENTS) return; // -> scream t = time (NULL); tblock = localtime (&t); //bliP: date check -> if (!tblock) s = va("%s\n", "#bad date#"); else if ((int)frag_log_type.value) // need for old-style frag log file s = va("\\frag\\%s\\%s\\%s\\%s\\%d-%d-%d %d:%d:%d\\\n", svs.clients[e1-1].name, svs.clients[e2-1].name, svs.clients[e1-1].team, svs.clients[e2-1].team, tblock->tm_year + 1900, tblock->tm_mon + 1, tblock->tm_mday, tblock->tm_hour, tblock->tm_min, tblock->tm_sec); else s = va("\\%s\\%s\\\n",svs.clients[e1-1].name, svs.clients[e2-1].name); // <- SZ_Print(&svs.log[svs.logsequence & 1], s); SV_Write_Log(FRAG_LOG, 1, s); } /* ============== PF2_getinfokey string(entity e, string key) infokey ============== */ void PF2_infokey(int e1, char *key, char *valbuff, int sizebuff) //(int e1, char *key, char *valbuff, int sizebuff) { static char ov[256]; char *value; value = ov; if (e1 == 0) { if (key && key[0] == '\\') { // so we can check is server support such "hacks" via infokey(world, "\\realip") f.e. key++; value = "no"; if ( !strcmp(key, "date_str") || !strcmp(key, "ip") || !strncmp(key, "realip", 7) || !strncmp(key, "download", 9) || !strcmp(key, "ping") || !strcmp(key, "*userid") || !strncmp(key, "login", 6) || !strcmp(key, "*VIP") || !strcmp(key, "*state") || !strcmp(key, "netname") || !strcmp(key, "mapname") || !strcmp(key, "modelname") || !strcmp(key, "version") || !strcmp(key, "servername") ) value = "yes"; } else if (!strcmp(key, "date_str")) { // qqshka - qvm does't have any time builtin support, so add this date_t date; SV_TimeOfDay(&date, "%a %b %d, %H:%M:%S %Y"); snprintf(ov, sizeof(ov), "%s", date.str); } else if (!strcmp(key, "mapname")) { value = sv.mapname; } else if (!strcmp(key, "modelname")) { value = sv.modelname; } else if (!strcmp(key, "version")) { value = VersionStringFull(); } else if (!strcmp(key, "servername")) { value = SERVER_NAME; } else if ((value = Info_ValueForKey(svs.info, key)) == NULL || !*value) value = Info_Get(&_localinfo_, key); } else if (e1 > 0 && e1 <= MAX_CLIENTS) { client_t *cl = &svs.clients[e1-1]; if (!strcmp(key, "ip")) strlcpy(ov, NET_BaseAdrToString(cl->netchan.remote_address), sizeof(ov)); else if (!strncmp(key, "realip", 7)) strlcpy(ov, NET_BaseAdrToString (cl->realip), sizeof(ov)); else if (!strncmp(key, "download", 9)) snprintf(ov, sizeof(ov), "%d", cl->file_percent ? cl->file_percent : -1); //bliP: file percent else if (!strcmp(key, "ping")) snprintf(ov, sizeof(ov), "%d", (int)SV_CalcPing(cl)); else if (!strcmp(key, "*userid")) snprintf(ov, sizeof(ov), "%d", cl->userid); else if (!strncmp(key, "login", 6)) value = cl->login; else if (!strcmp(key, "*VIP")) // qqshka: also located in userinfo, but this is more safe/secure way, imo snprintf(ov, sizeof(ov), "%d", cl->vip); else if (!strcmp(key, "netname")) value = cl->name; else if (!strcmp(key, "*state")) { switch (cl->state) { case cs_free: value = "free"; break; case cs_zombie: value = "zombie"; break; case cs_preconnected: value = "preconnected"; break; case cs_connected: value = "connected"; break; case cs_spawned: value = "spawned"; break; default: value = "unknown"; break; } } else value = Info_Get(&cl->_userinfo_ctx_, key); } else value = ""; if ((int) strlen(value) > sizebuff) Con_DPrintf("PR2_infokey: buffer size too small\n"); strlcpy(valbuff, value, sizebuff); // RETURN_STRING(value); } /* ============== PF2_multicast void(vector where, float set) multicast ============== */ void PF2_multicast(float x, float y, float z, int to) //(vec3_t o, int to) { vec3_t o; o[0] = x; o[1] = y; o[2] = z; SV_Multicast(o, to); } /* ============== PF2_disable_updates void(entiny whom, float time) disable_updates ============== */ void PF2_disable_updates(int entnum, float time1) //(int entnum, float time) { client_t *client; // entnum = G_EDICTNUM(OFS_PARM0); // time1 = G_FLOAT(OFS_PARM1); if (entnum < 1 || entnum > MAX_CLIENTS) { Con_Printf("tried to disable_updates to a non-client\n"); return; } client = &svs.clients[entnum - 1]; client->disable_updates_stop = realtime + time1; } #define MAX_PR2_FILES 8 typedef struct { char name[256]; vfsfile_t *handle; fsMode_t accessmode; } pr2_fopen_files_t; pr2_fopen_files_t pr2_fopen_files[MAX_PR2_FILES]; int pr2_num_open_files = 0; char* cmodes[]={"rb","r","wb","w","ab","a"}; // FIXME: read from paks int PF2_FS_OpenFile(char *name, fileHandle_t *handle, fsMode_t fmode) { int i, ret = -1; if(pr2_num_open_files >= MAX_PR2_FILES) { return -1; } *handle = 0; for (i = 0; i < MAX_PR2_FILES; i++) if (!pr2_fopen_files[i].handle) break; if (i == MAX_PR2_FILES) //too many already open { return -1; } if (FS_UnsafeFilename(name)) { // someone tried to be clever. return -1; } strlcpy(pr2_fopen_files[i].name, name, sizeof(pr2_fopen_files[i].name)); pr2_fopen_files[i].accessmode = fmode; switch(fmode) { case FS_READ_BIN: case FS_READ_TXT: #ifndef SERVERONLY pr2_fopen_files[i].handle = FS_OpenVFS(name, cmodes[fmode], FS_ANY); #else pr2_fopen_files[i].handle = FS_OpenVFS(name, cmodes[fmode], FS_GAME); #endif if(!pr2_fopen_files[i].handle) { return -1; } Con_DPrintf( "PF2_FS_OpenFile %s\n", name ); ret = VFS_GETLEN(pr2_fopen_files[i].handle); break; case FS_WRITE_BIN: case FS_WRITE_TXT: case FS_APPEND_BIN: case FS_APPEND_TXT: // well, perhaps we we should create path... // FS_CreatePathRelative(name, FS_GAME_OS); pr2_fopen_files[i].handle = FS_OpenVFS(name, cmodes[fmode], FS_GAME_OS); if ( !pr2_fopen_files[i].handle ) { return -1; } Con_DPrintf( "PF2_FS_OpenFile %s\n", name ); ret = VFS_TELL(pr2_fopen_files[i].handle); break; default: return -1; } *handle = i+1; pr2_num_open_files++; return ret; } void PF2_FS_CloseFile(fileHandle_t fnum) { fnum--; if (fnum < 0 || fnum >= MAX_PR2_FILES) return; //out of range if(!pr2_num_open_files) return; if(!(pr2_fopen_files[fnum].handle)) return; VFS_CLOSE(pr2_fopen_files[fnum].handle); pr2_fopen_files[fnum].handle = NULL; pr2_num_open_files--; } int seek_origin[]={SEEK_CUR,SEEK_END,SEEK_SET}; intptr_t PF2_FS_SeekFile(fileHandle_t fnum, intptr_t offset, fsOrigin_t type) { fnum--; if (fnum < 0 || fnum >= MAX_PR2_FILES) return 0;//out of range if(!pr2_num_open_files) return 0; if(!(pr2_fopen_files[fnum].handle)) return 0; if(type < 0 || type >= sizeof(seek_origin) / sizeof(seek_origin[0])) return 0; return VFS_SEEK(pr2_fopen_files[fnum].handle, offset, seek_origin[type]); } intptr_t PF2_FS_TellFile(fileHandle_t fnum) { fnum--; if (fnum < 0 || fnum >= MAX_PR2_FILES) return 0;//out of range if(!pr2_num_open_files) return 0; if(!(pr2_fopen_files[fnum].handle)) return 0; return VFS_TELL(pr2_fopen_files[fnum].handle); } intptr_t PF2_FS_WriteFile(char *dest, intptr_t quantity, fileHandle_t fnum) { fnum--; if (fnum < 0 || fnum >= MAX_PR2_FILES) return 0;//out of range if(!pr2_num_open_files) return 0; if(!(pr2_fopen_files[fnum].handle)) return 0; return VFS_WRITE(pr2_fopen_files[fnum].handle, dest, quantity); } intptr_t PF2_FS_ReadFile(char *dest, intptr_t quantity, fileHandle_t fnum) { fnum--; if (fnum < 0 || fnum >= MAX_PR2_FILES) return 0;//out of range if(!pr2_num_open_files) return 0; if(!(pr2_fopen_files[fnum].handle)) return 0; return VFS_READ(pr2_fopen_files[fnum].handle, dest, quantity, NULL); } void PR2_FS_Restart(void) { int i; if(pr2_num_open_files) { for (i = 0; i < MAX_PR2_FILES; i++) { if(pr2_fopen_files[i].handle) { VFS_CLOSE(pr2_fopen_files[i].handle); pr2_num_open_files--; pr2_fopen_files[i].handle = NULL; } } } if(pr2_num_open_files) Sys_Error("PR2_fcloseall: pr2_num_open_files != 0"); pr2_num_open_files = 0; memset(pr2_fopen_files,0,sizeof(pr2_fopen_files)); } static int GetFileList_Compare (const void *p1, const void *p2) { return strcmp (*((char**)p1), *((char**)p2)); } #define FILELIST_GAMEDIR_ONLY (1<<0) // if set then search in gamedir only #define FILELIST_WITH_PATH (1<<1) // include path to file #define FILELIST_WITH_EXT (1<<2) // include extension of file intptr_t PF2_FS_GetFileList(char *path, char *ext, char *listbuff, intptr_t buffsize, intptr_t flags) { // extern searchpath_t *com_searchpaths; // evil, because this must be used in fs.c only... char *gpath = NULL; char *list[MAX_DIRFILES]; const int list_cnt = sizeof(list) / sizeof(list[0]); dir_t dir; // searchpath_t *search; char netpath[MAX_OSPATH], *fullname; char *dirptr; int numfiles = 0; int i, j; if (gamedata.APIversion < 15) flags = 0; memset(list, 0, sizeof(list)); dirptr = listbuff; *dirptr = 0; if (strstr( path, ".." ) || strstr( path, "::" )) return 0; // do not allow relative paths // search through the path, one element at a time for (i = 0, gpath = NULL; i < list_cnt && ( gpath = FS_NextPath( gpath ) ); ) { // if FILELIST_GAMEDIR_ONLY set then search in gamedir only if ((flags & FILELIST_GAMEDIR_ONLY) && strcmp(gpath, fs_gamedir)) continue; snprintf (netpath, sizeof (netpath), "%s/%s", gpath, path); // reg exp search... dir = Sys_listdir(netpath, ext, SORT_NO); for (j = 0; i < list_cnt && dir.files[j].name[0]; j++, i++) { if (flags & FILELIST_WITH_PATH) { // with path snprintf (netpath, sizeof (netpath), "%s/%s", gpath, dir.files[j].name); fullname = netpath; // skip "./" prefix if (!strncmp(fullname, "./", sizeof("./") - 1)) fullname += 2; } else { // just name fullname = dir.files[j].name; } // skip file extension if (!(flags & FILELIST_WITH_EXT)) { #ifndef SERVERONLY COM_StripExtension(fullname, fullname, sizeof(fullname)); #else COM_StripExtension(fullname); #endif } list[i] = Q_strdup(fullname); // a bit below we will free it } } // sort it, this will help exclude duplicates a bit below qsort (list, i, sizeof(list[0]), GetFileList_Compare); // copy for (i = 0; i < list_cnt && list[i]; i++) { size_t namelen = strlen(list[i]) + 1; if(dirptr + namelen > listbuff + buffsize) break; if (!*list[i]) continue; // hrm, empty // simple way to exclude duplicates, since we sorted it above! if (i && !strcmp(list[i-1], list[i])) continue; // Con_Printf("%4d %s\n", i, list[i]); strlcpy(dirptr, list[i], namelen); dirptr += namelen; numfiles++; } // free allocated mem for (i = 0; i < list_cnt; i++) Q_free(list[i]); return numfiles; } /* int trap_Map_Extension( const char* ext_name, int mapto) return: 0 success maping -1 not found -2 cannot map */ intptr_t PF2_Map_Extension(char *name, int mapto) { if (mapto < _G__LASTAPI) { return -2; } return -1; } /////////Bot Functions extern cvar_t maxclients, maxspectators; int PF2_Add_Bot(char *name, int bottomcolor, int topcolor, char *skin) { client_t *cl, *newcl = NULL; int edictnum; int clients, spectators, i; extern char *shortinfotbl[]; char *s; edict_t *ent; eval_t *val; int old_self; char info[MAX_EXT_INFO_STRING]; // count up the clients and spectators clients = 0; spectators = 0; for ( i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++ ) { if ( cl->state == cs_free ) continue; if ( cl->spectator ) spectators++; else clients++; } // if at server limits, refuse connection if ((int)maxclients.value > MAX_CLIENTS ) Cvar_SetValue( &maxclients, MAX_CLIENTS ); if ((int)maxspectators.value > MAX_CLIENTS ) Cvar_SetValue( &maxspectators, MAX_CLIENTS ); if ((int)maxspectators.value + maxclients.value > MAX_CLIENTS ) Cvar_SetValue( &maxspectators, MAX_CLIENTS - (int)maxclients.value ); if ( clients >= ( int ) maxclients.value ) { return 0; } for ( i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++ ) { if ( cl->state == cs_free ) { newcl = cl; break; } } if ( !newcl ) { return 0; } memset(newcl, 0, sizeof(*newcl)); edictnum = (newcl - svs.clients) + 1; ent = EDICT_NUM(edictnum); ED_ClearEdict(ent); memset(&newcl->_userinfo_ctx_, 0, sizeof(newcl->_userinfo_ctx_)); memset(&newcl->_userinfoshort_ctx_, 0, sizeof(newcl->_userinfoshort_ctx_)); snprintf( info, sizeof( info ), "\\name\\%s\\topcolor\\%d\\bottomcolor\\%d\\emodel\\6967\\pmodel\\13845\\skin\\%s\\*bot\\1", name, topcolor, bottomcolor, skin ); newcl->_userinfo_ctx_.max = MAX_CLIENT_INFOS; newcl->_userinfoshort_ctx_.max = MAX_CLIENT_INFOS; Info_Convert(&newcl->_userinfo_ctx_, info); newcl->state = cs_spawned; newcl->userid = SV_GenerateUserID(); newcl->datagram.allowoverflow = true; newcl->datagram.data = newcl->datagram_buf; newcl->datagram.maxsize = sizeof( newcl->datagram_buf ); newcl->spectator = 0; newcl->isBot = 1; SV_SetClientConnectionTime(newcl); strlcpy(newcl->name, name, sizeof(newcl->name)); newcl->entgravity = 1.0; val = PR2_GetEdictFieldValue( ent, "gravity" ); // FIXME: do it similar to maxspeed if ( val ) val->_float = 1.0; sv_client->maxspeed = sv_maxspeed.value; if (fofs_maxspeed) EdictFieldFloat(ent, fofs_maxspeed) = sv_maxspeed.value; newcl->edict = ent; ent->v->colormap = edictnum; val = PR2_GetEdictFieldValue(ent, "isBot"); // FIXME: do it similar to maxspeed if( val ) val->_int = 1; // restore client name. PR_SetEntityString(ent, ent->v->netname, newcl->name); memset( newcl->stats, 0, sizeof( newcl->stats ) ); SZ_InitEx (&newcl->netchan.message, newcl->netchan.message_buf, (int)sizeof(newcl->netchan.message_buf), true); SZ_Clear( &newcl->netchan.message ); newcl->netchan.drop_count = 0; newcl->netchan.incoming_sequence = 1; // copy the most important userinfo into userinfoshort // { SV_ExtractFromUserinfo( newcl, true ); for ( i = 0; shortinfotbl[i] != NULL; i++ ) { s = Info_Get( &newcl->_userinfo_ctx_, shortinfotbl[i] ); Info_SetStar( &newcl->_userinfoshort_ctx_, shortinfotbl[i], s ); } // move star keys to infoshort Info_CopyStar( &newcl->_userinfo_ctx_, &newcl->_userinfoshort_ctx_ ); // } newcl->disable_updates_stop = -1.0; // Vladis SV_FullClientUpdate( newcl, &sv.reliable_datagram ); old_self = pr_global_struct->self; pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(newcl->edict); PR2_GameClientConnect(0); PR2_GamePutClientInServer(0); pr_global_struct->self = old_self; return edictnum; } void RemoveBot(client_t *cl) { if( !cl->isBot ) return; pr_global_struct->self = EDICT_TO_PROG(cl->edict); if ( sv_vm ) PR2_GameClientDisconnect(0); cl->old_frags = 0; cl->edict->v->frags = 0.0; cl->name[0] = 0; cl->state = cs_free; Info_RemoveAll(&cl->_userinfo_ctx_); Info_RemoveAll(&cl->_userinfoshort_ctx_); SV_FullClientUpdate( cl, &sv.reliable_datagram ); cl->isBot = 0; } void PF2_Remove_Bot(int entnum) { client_t *cl; int old_self; if ( entnum < 1 || entnum > MAX_CLIENTS ) { Con_Printf( "tried to remove a non-botclient %d \n", entnum ); return; } cl = &svs.clients[entnum - 1]; if ( !cl->isBot ) { Con_Printf( "tried to remove a non-botclient %d \n", entnum ); return; } old_self = pr_global_struct->self; //save self pr_global_struct->self = entnum; RemoveBot(cl); pr_global_struct->self = old_self; } // FIXME: Why PR2_UserInfoChanged is not called here? Like for normal players. // Why we need this special handling in the first place? void PF2_SetBotUserInfo(int entnum, char *key, char *value, int flags) { client_t *cl; int i; extern char *shortinfotbl[]; if (gamedata.APIversion < 15) flags = 0; if (strstr(key, "&c") || strstr(key, "&r") || strstr(value, "&c") || strstr(value, "&r")) return; if ( entnum < 1 || entnum > MAX_CLIENTS ) { Con_Printf( "tried to change userinfo a non-botclient %d \n", entnum ); return; } cl = &svs.clients[entnum - 1]; if ( !cl->isBot ) { Con_Printf( "tried to change userinfo a non-botclient %d \n", entnum ); return; } if ( flags & SETUSERINFO_STAR ) Info_SetStar( &cl->_userinfo_ctx_, key, value ); else Info_Set( &cl->_userinfo_ctx_, key, value ); SV_ExtractFromUserinfo( cl, !strcmp( key, "name" ) ); for ( i = 0; shortinfotbl[i] != NULL; i++ ) { if ( key[0] == '_' || !strcmp( key, shortinfotbl[i] ) ) { char *nuw = Info_Get( &cl->_userinfo_ctx_, key ); Info_Set( &cl->_userinfoshort_ctx_, key, nuw ); i = cl - svs.clients; MSG_WriteByte( &sv.reliable_datagram, svc_setinfo ); MSG_WriteByte( &sv.reliable_datagram, i ); MSG_WriteString( &sv.reliable_datagram, key ); MSG_WriteString( &sv.reliable_datagram, nuw ); break; } } } void PF2_SetBotCMD(int entnum, int msec, float a1, float a2, float a3, int forwardmove, int sidemove, int upmove, int buttons, int impulse) { client_t *cl; if ( entnum < 1 || entnum > MAX_CLIENTS ) { Con_Printf( "tried to set cmd a non-botclient %d \n", entnum ); return; } cl = &svs.clients[entnum - 1]; if ( !cl->isBot ) { Con_Printf( "tried to set cmd a non-botclient %d \n", entnum ); return; } cl->botcmd.msec = msec; cl->botcmd.angles[0] = a1; cl->botcmd.angles[1] = a2; cl->botcmd.angles[2] = a3; cl->botcmd.forwardmove = forwardmove; cl->botcmd.sidemove = sidemove; cl->botcmd.upmove = upmove; cl->botcmd.buttons = buttons; cl->botcmd.impulse = impulse; if (cl->edict->v->fixangle) { VectorCopy(cl->edict->v->angles, cl->botcmd.angles); cl->botcmd.angles[PITCH] *= -3; cl->edict->v->fixangle = 0; } } //========================================= // some time support in QVM //========================================= /* ============== PF2_QVMstrftime ============== */ int PF2_QVMstrftime(char *valbuff, int sizebuff, char *fmt, int offset) { struct tm *newtime; time_t long_time; int ret; if (sizebuff <= 0 || !valbuff) { Con_DPrintf("PF2_QVMstrftime: wrong buffer\n"); return 0; } time(&long_time); long_time += offset; newtime = localtime(&long_time); if (!newtime) { valbuff[0] = 0; // or may be better set to "#bad date#" ? return 0; } ret = strftime(valbuff, sizebuff-1, fmt, newtime); if (!ret) { valbuff[0] = 0; // or may be better set to "#bad date#" ? Con_DPrintf("PF2_QVMstrftime: buffer size too small\n"); return 0; } return ret; } // a la the ZQ_PAUSE QC extension void PF2_setpause(int pause) { if (pause != (sv.paused & 1)) SV_TogglePause (NULL, 1); } void PF2_SetUserInfo(int entnum, char *k, char *v, int flags) { client_t *cl; char key[MAX_KEY_STRING]; char value[MAX_KEY_STRING]; char s[MAX_KEY_STRING * 4]; int i; extern char *shortinfotbl[]; if (strstr(k, "&c") || strstr(k, "&r") || strstr(v, "&c") || strstr(v, "&r")) return; if ( entnum < 1 || entnum > MAX_CLIENTS ) { Con_Printf( "tried to change userinfo a non-client %d \n", entnum ); return; } cl = &svs.clients[entnum - 1]; // well, our API is weird if ( cl->isBot ) { PF2_SetBotUserInfo(entnum, k, v, flags); return; } // tokenize snprintf( s, sizeof(s), "PF2_SetUserInfo \"%-.*s\" \"%-.*s\"", (int)sizeof(key), k, (int)sizeof(value), v ); Cmd_TokenizeString( s ); // well, PR2_UserInfoChanged() may call PF2_SetUserInfo() again, so we better save thouse strlcpy( key, Cmd_Argv(1), sizeof(key) ); strlcpy( value, Cmd_Argv(2), sizeof(value) ); if( sv_vm ) { pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(cl->edict); if (PR2_UserInfoChanged(0)) return; } if ( flags & SETUSERINFO_STAR ) Info_SetStar( &cl->_userinfo_ctx_, key, value ); else Info_Set( &cl->_userinfo_ctx_, key, value ); SV_ExtractFromUserinfo( cl, !strcmp( key, "name" ) ); PR2_UserInfoChanged(1); for ( i = 0; shortinfotbl[i] != NULL; i++ ) { if ( !strcmp( key, shortinfotbl[i] ) ) { char *nuw = Info_Get( &cl->_userinfo_ctx_, key ); // well, here we do not have if ( flags & SETUSERINFO_STAR ) because shortinfotbl[] does't have any star key Info_Set( &cl->_userinfoshort_ctx_, key, nuw ); i = cl - svs.clients; MSG_WriteByte( &sv.reliable_datagram, svc_setinfo ); MSG_WriteByte( &sv.reliable_datagram, i ); MSG_WriteString( &sv.reliable_datagram, key ); MSG_WriteString( &sv.reliable_datagram, nuw ); break; } } } void PF2_VisibleTo(int viewer, int first, int len, byte *visible) { int e, last = first + len; edict_t *ent; edict_t *viewer_ent = EDICT_NUM(viewer); vec3_t org; byte *pvs; if (last > sv.num_edicts) last = sv.num_edicts; VectorAdd(viewer_ent->v->origin, viewer_ent->v->view_ofs, org); pvs = CM_FatPVS(org); for (e = first, ent = EDICT_NUM(e); e < last; e++, ent = NEXT_EDICT(ent)) { int i; if (ent->e.num_leafs < 0 || ent->e.free || (e >= 1 && e <= MAX_CLIENTS && svs.clients[e - 1].state != cs_spawned)) { continue; // Ignore free edicts or not active client. } for (i = 0; i < ent->e.num_leafs; i++) { if (pvs[ent->e.leafnums[i] >> 3] & (1 << (ent->e.leafnums[i]&7))) { visible[e - first] = true; // seems to be visible break; } } } } //=========================================================================== // SysCalls //=========================================================================== #define VMV(x) _vmf(args[x]), _vmf(args[(x) + 1]), _vmf(args[(x) + 2]) #define VME(x) EDICT_NUM(args[x]) intptr_t PR2_GameSystemCalls(intptr_t *args) { switch (args[0]) { case G_GETAPIVERSION: return GAME_API_VERSION; case G_DPRINT: Con_DPrintf("%s", (const char *)VMA(1)); return 0; case G_ERROR: PR2_RunError(VMA(1)); return 0; case G_GetEntityToken: VM_CheckBounds(sv_vm, args[1], args[2]); pr2_ent_data_ptr = COM_Parse(pr2_ent_data_ptr); strlcpy(VMA(1), com_token, args[2]); return pr2_ent_data_ptr != NULL; case G_SPAWN_ENT: return NUM_FOR_EDICT(ED_Alloc()); case G_REMOVE_ENT: ED_Free(VME(1)); return 0; case G_PRECACHE_SOUND: PF2_precache_sound(VMA(1)); return 0; case G_PRECACHE_MODEL: PF2_precache_model(VMA(1)); return 0; case G_LIGHTSTYLE: PF2_lightstyle(args[1], VMA(2)); return 0; case G_SETORIGIN: PF2_setorigin(VME(1), VMV(2)); return 0; case G_SETSIZE: PF2_setsize(VME(1), VMV(2), VMV(5)); return 0; case G_SETMODEL: PF2_setmodel(VME(1), VMA(2)); return 0; case G_BPRINT: { int flags = args[3]; if (gamedata.APIversion < 15) flags = 0; SV_BroadcastPrintfEx(args[1], flags, "%s", VMA(2)); } return 0; case G_SPRINT: PF2_sprint(args[1], args[2], VMA(3), args[4]); return 0; case G_CENTERPRINT: PF2_centerprint(args[1], VMA(2)); return 0; case G_AMBIENTSOUND: PF2_ambientsound(VMV(1), VMA(4), VMF(5), VMF(6)); return 0; case G_SOUND: /* ================= PF2_sound Each entity can have eight independant sound sources, like voice, weapon, feet, etc. Channel 0 is an auto-allocate channel, the others override anything already running on that entity/channel pair. An attenuation of 0 will play full volume everywhere in the level. Larger attenuations will drop off. void sound( gedict_t * ed, int channel, char *samp, float vol, float att ) ================= */ SV_StartSound(VME(1), args[2], VMA(3), VMF(4) * 255, VMF(5)); return 0; case G_TRACELINE: PF2_traceline(VMV(1), VMV(4), args[7], args[8]); return 0; case G_CHECKCLIENT: return PF2_checkclient(); case G_STUFFCMD: PF2_stuffcmd(args[1], VMA(2), args[3]); return 0; case G_LOCALCMD: /* ================= Sends text over to the server's execution buffer localcmd (string) ================= */ Cbuf_AddText(VMA(1)); return 0; case G_CVAR: return PASSFLOAT(Cvar_Value(VMA(1))); case G_CVAR_SET: Cvar_SetByName(VMA(1), VMA(2)); return 0; case G_FINDRADIUS: return PF2_FindRadius(NUM_FOR_GAME_EDICT(VMA(1)), (float *)VMA(2), VMF(3)); case G_WALKMOVE: return PF2_walkmove(VME(1), VMF(2), VMF(3)); case G_DROPTOFLOOR: return PF2_droptofloor(VME(1)); case G_CHECKBOTTOM: return SV_CheckBottom(VME(1)); case G_POINTCONTENTS: return PF2_pointcontents(VMV(1)); case G_NEXTENT: return PF2_nextent(args[1]); case G_AIM: return 0; case G_MAKESTATIC: PF2_makestatic(VME(1)); return 0; case G_SETSPAWNPARAMS: PF2_setspawnparms(args[1]); return 0; case G_CHANGELEVEL: PF2_changelevel(VMA(1), VMA(2)); return 0; case G_LOGFRAG: PF2_logfrag(args[1], args[2]); return 0; case G_GETINFOKEY: VM_CheckBounds(sv_vm, args[3], args[4]); PF2_infokey(args[1], VMA(2), VMA(3), args[4]); return 0; case G_MULTICAST: PF2_multicast(VMV(1), args[4]); return 0; case G_DISABLEUPDATES: PF2_disable_updates(args[1], VMF(2)); return 0; case G_WRITEBYTE: PF2_WriteByte(args[1], args[2]); return 0; case G_WRITECHAR: PF2_WriteChar(args[1], args[2]); return 0; case G_WRITESHORT: PF2_WriteShort(args[1], args[2]); return 0; case G_WRITELONG: PF2_WriteLong(args[1], args[2]); return 0; case G_WRITEANGLE: PF2_WriteAngle(args[1], VMF(2)); return 0; case G_WRITECOORD: PF2_WriteCoord(args[1], VMF(2)); return 0; case G_WRITESTRING: PF2_WriteString(args[1], VMA(2)); return 0; case G_WRITEENTITY: PF2_WriteEntity(args[1], args[2]); return 0; case G_FLUSHSIGNON: SV_FlushSignon(); return 0; case g_memset: VM_CheckBounds(sv_vm, args[1], args[3]); memset(VMA(1), args[2], args[3]); return args[1]; case g_memcpy: VM_CheckBounds2(sv_vm, args[1], args[2], args[3]); memcpy(VMA(1), VMA(2), args[3]); return args[1]; case g_strncpy: VM_CheckBounds2(sv_vm, args[1], args[2], args[3]); strncpy(VMA(1), VMA(2), args[3]); return args[1]; case g_sin: return PASSFLOAT(sin(VMF(1))); case g_cos: return PASSFLOAT(cos(VMF(1))); case g_atan2: return PASSFLOAT(atan2(VMF(1), VMF(2))); case g_sqrt: return PASSFLOAT(sqrt(VMF(1))); case g_floor: return PASSFLOAT(floor(VMF(1))); case g_ceil: return PASSFLOAT(ceil(VMF(1))); case g_acos: return PASSFLOAT(acos(VMF(1))); case G_CMD_ARGC: return Cmd_Argc(); case G_CMD_ARGV: VM_CheckBounds(sv_vm, args[2], args[3]); strlcpy(VMA(2), Cmd_Argv(args[1]), args[3]); return 0; case G_TraceCapsule: PF2_TraceCapsule(VMV(1), VMV(4), args[7], VME(8), VMV(9), VMV(12)); return 0; case G_FSOpenFile: return PF2_FS_OpenFile(VMA(1), (fileHandle_t *)VMA(2), (fsMode_t)args[3]); case G_FSCloseFile: PF2_FS_CloseFile((fileHandle_t)args[1]); return 0; case G_FSReadFile: VM_CheckBounds(sv_vm, args[1], args[2]); return PF2_FS_ReadFile(VMA(1), args[2], (fileHandle_t)args[3]); case G_FSWriteFile: VM_CheckBounds(sv_vm, args[1], args[2]); return PF2_FS_WriteFile(VMA(1), args[2], (fileHandle_t)args[3]); case G_FSSeekFile: return PF2_FS_SeekFile((fileHandle_t)args[1], args[2], (fsOrigin_t)args[3]); case G_FSTellFile: return PF2_FS_TellFile((fileHandle_t)args[1]); case G_FSGetFileList: VM_CheckBounds(sv_vm, args[3], args[4]); return PF2_FS_GetFileList(VMA(1), VMA(2), VMA(3), args[4], args[5]); case G_CVAR_SET_FLOAT: Cvar_SetValueByName(VMA(1), VMF(2)); return 0; case G_CVAR_STRING: VM_CheckBounds(sv_vm, args[2], args[3]); strlcpy(VMA(2), Cvar_String(VMA(1)), args[3]); return 0; case G_Map_Extension: return PF2_Map_Extension(VMA(1), args[2]); case G_strcmp: return strcmp(VMA(1), VMA(2)); case G_strncmp: return strncmp(VMA(1), VMA(2), args[3]); case G_stricmp: return strcasecmp(VMA(1), VMA(2)); case G_strnicmp: return strncasecmp(VMA(1), VMA(2), args[3]); case G_Find: return PF2_Find(NUM_FOR_GAME_EDICT(VMA(1)), args[2], VMA(3)); case G_executecmd: PF2_executecmd(); return 0; case G_conprint: Sys_Printf("%s", VMA(1)); return 0; case G_readcmd: VM_CheckBounds(sv_vm, args[2], args[3]); PF2_readcmd(VMA(1), VMA(2), args[3]); return 0; case G_redirectcmd: PF2_redirectcmd(NUM_FOR_GAME_EDICT(VMA(1)), VMA(2)); return 0; case G_Add_Bot: return PF2_Add_Bot(VMA(1), args[2], args[3], VMA(4)); case G_Remove_Bot: PF2_Remove_Bot(args[1]); return 0; case G_SetBotUserInfo: PF2_SetBotUserInfo(args[1], VMA(2), VMA(3), args[4]); return 0; case G_SetBotCMD: PF2_SetBotCMD(args[1], args[2], VMV(3), args[6], args[7], args[8], args[9], args[10]); return 0; case G_QVMstrftime: VM_CheckBounds(sv_vm, args[1], args[2]); return PF2_QVMstrftime(VMA(1), args[2], VMA(3), args[4]); case G_CMD_ARGS: VM_CheckBounds(sv_vm, args[1], args[2]); strlcpy(VMA(1), Cmd_Args(), args[2]); return 0; case G_CMD_TOKENIZE: Cmd_TokenizeString(VMA(1)); return 0; case g_strlcpy: VM_CheckBounds(sv_vm, args[1], args[3]); return strlcpy(VMA(1), VMA(2), args[3]); case g_strlcat: VM_CheckBounds(sv_vm, args[1], args[3]); return strlcat(VMA(1), VMA(2), args[3]); case G_MAKEVECTORS: AngleVectors(VMA(1), pr_global_struct->v_forward, pr_global_struct->v_right, pr_global_struct->v_up); return 0; case G_NEXTCLIENT: return PF2_nextclient(NUM_FOR_GAME_EDICT(VMA(1))); case G_PRECACHE_VWEP_MODEL: return PF2_precache_vwep_model(VMA(1)); case G_SETPAUSE: PF2_setpause(args[1]); return 0; case G_SETUSERINFO: PF2_SetUserInfo(args[1], VMA(2), VMA(3), args[4]); return 0; case G_MOVETOGOAL: PF2_MoveToGoal(VMF(1)); return 0; case G_VISIBLETO: VM_CheckBounds(sv_vm, args[4], args[3]); memset(VMA(4), 0, args[3]); // Ensure same memory state on each run. PF2_VisibleTo(args[1], args[2], args[3], VMA(4)); return 0; default: SV_Error("Bad game system trap: %ld", (long int)args[0]); } return 0; } #endif /* USE_PR2 */ #endif // !CLIENTONLY mvdsv-0.35/src/pr2_edict.c000066400000000000000000000035431427146041000154120ustar00rootroot00000000000000/* * QW262 * Copyright (C) 2004 [sd] angel * * 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 * * * */ #ifndef CLIENTONLY #ifdef USE_PR2 #include "qwsvdef.h" field_t *fields; eval_t *PR2_GetEdictFieldValue(edict_t *ed, char *field) { char *s; field_t *f; if (!sv_vm) return PR1_GetEdictFieldValue(ed, field); for (f = fields; (s = PR2_GetString(f->name)) && *s; f++) if (!strcasecmp(PR2_GetString(f->name), field)) return (eval_t *)((char *)ed->v + f->ofs); return NULL; } int ED2_FindFieldOffset (char *field) { char *s; field_t *f; if (!sv_vm) return ED1_FindFieldOffset(field); for (f = fields; (s = PR2_GetString(f->name)) && *s; f++) if (!strcasecmp(PR2_GetString(f->name), field)) return f->ofs; return 0; } /* ============= ED2_PrintEdict_f For debugging, prints a single edicy ============= */ void ED2_PrintEdict_f (void) { extern void ED_PrintEdict_f (void); if(!sv_vm) ED_PrintEdict_f(); } /* ============= ED2_PrintEdicts For debugging, prints all the entities in the current server ============= */ void ED2_PrintEdicts (void) { extern void ED_PrintEdicts (void); if(!sv_vm) ED_PrintEdicts(); } #endif /* USE_PR2 */ #endif // !CLIENTONLY mvdsv-0.35/src/pr2_exec.c000066400000000000000000000372231427146041000152500ustar00rootroot00000000000000/* * QW262 * Copyright (C) 2004 [sd] angel * * 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 * * * */ #ifndef CLIENTONLY #ifdef USE_PR2 #include "qwsvdef.h" #include "vm_local.h" gameData_t gamedata; extern field_t *fields; // 0 = pr1 (qwprogs.dat etc), 1 = native (.so/.dll), 2 = q3vm (.qvm), 3 = q3vm (.qvm) with JIT cvar_t sv_progtype = { "sv_progtype","0" }; // 0 = standard, 1 = pr2 mods set string_t fields as byte offsets to location of actual strings cvar_t sv_pr2references = {"sv_pr2references", "0"}; void ED2_PrintEdicts (void); void PR2_Profile_f (void); void ED2_PrintEdict_f (void); void ED_Count (void); void VM_VmInfo_f( void ); void PR2_Init(void) { int p; int usedll; Cvar_Register(&sv_progtype); Cvar_Register(&sv_progsname); Cvar_Register(&sv_pr2references); Cvar_Register(&vm_rtChecks); #ifdef WITH_NQPROGS Cvar_Register(&sv_forcenqprogs); #endif p = SV_CommandLineProgTypeArgument(); if (p && p < COM_Argc()) { usedll = Q_atoi(COM_Argv(p + 1)); if (usedll > VMI_COMPILED || usedll < VMI_NONE) usedll = VMI_NONE; Cvar_SetValue(&sv_progtype,usedll); } Cmd_AddCommand ("edict", ED2_PrintEdict_f); Cmd_AddCommand ("edicts", ED2_PrintEdicts); Cmd_AddCommand ("edictcount", ED_Count); Cmd_AddCommand ("profile", PR2_Profile_f); Cmd_AddCommand ("mod", PR2_GameConsoleCommand); Cmd_AddCommand ("vminfo", VM_VmInfo_f); memset(pr_newstrtbl, 0, sizeof(pr_newstrtbl)); } void PR2_Profile_f(void) { if(!sv_vm) { PR_Profile_f(); return; } } //=========================================================================== // PR2_GetString: only called to get direct addresses now //=========================================================================== char *PR2_GetString(intptr_t num) { if(!sv_vm) return PR1_GetString(num); switch (sv_vm->type) { case VMI_NONE: return PR1_GetString(num); case VMI_NATIVE: if (num) { return (char *)num; } else { return ""; } case VMI_BYTECODE: case VMI_COMPILED: if (num <= 0) return ""; return VM_ExplicitArgPtr(sv_vm, num); } return NULL; } intptr_t PR2_EntityStringLocation(string_t offset, int max_size) { if (offset > 0 && offset < pr_edict_size * sv.max_edicts - max_size) { return ((intptr_t)sv.game_edicts + offset); } return 0; } intptr_t PR2_GlobalStringLocation(string_t offset) { // FIXME: the mod has allocated this memory, don't have max size if (offset > 0) { return ((intptr_t)pr_global_struct + offset); } return 0; } char *PR2_GetEntityString(string_t num) { if(!sv_vm) return PR1_GetString(num); switch (sv_vm->type) { case VMI_NONE: return PR1_GetString(num); case VMI_NATIVE: if (num) { if (sv_vm->pr2_references) { char** location = (char**)PR2_EntityStringLocation(num, sizeof(char*)); if (location && *location) { return *location; } } #ifndef idx64 else { return (char *) (num); } #endif } return ""; case VMI_BYTECODE: case VMI_COMPILED: if (num <= 0) return ""; if (sv_vm->pr2_references) { num = *(string_t*)PR2_EntityStringLocation(num, sizeof(string_t)); } return VM_ExplicitArgPtr(sv_vm, num); } return NULL; } //=========================================================================== // PR2_SetString // !!!!IMPOTANT!!!! // Server change string pointers in mod memory only in trapcall(strings passed from mod, and placed in mod memory). // Never pass pointers outside of the mod memory to mod, this does not work in QVM in 64 bit server. //=========================================================================== void PR2_SetEntityString(edict_t* ed, string_t* target, char* s) { if (!sv_vm) { PR1_SetString(target, s); return; } } void PR2_SetGlobalString(string_t* target, char* s) { if (!sv_vm) { PR1_SetString(target, s); return; } } /* ================= PR2_LoadEnts ================= */ extern const char *pr2_ent_data_ptr; void PR2_LoadEnts(char *data) { if (sv_vm) { pr2_ent_data_ptr = data; //Init parse VM_Call(sv_vm, 0, GAME_LOADENTS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); } else { PR1_LoadEnts(data); } } //=========================================================================== // GameStartFrame //=========================================================================== void PR2_GameStartFrame(qbool isBotFrame) { if (isBotFrame && (!sv_vm || sv_vm->type == VMI_NONE || gamedata.APIversion < 15)) { return; } if (sv_vm) VM_Call(sv_vm, 2, GAME_START_FRAME, (int) (sv.time * 1000), (int)isBotFrame, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else PR1_GameStartFrame(); } //=========================================================================== // GameClientConnect //=========================================================================== void PR2_GameClientConnect(int spec) { if (sv_vm) VM_Call(sv_vm, 1, GAME_CLIENT_CONNECT, spec, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else PR1_GameClientConnect(spec); } //=========================================================================== // GamePutClientInServer //=========================================================================== void PR2_GamePutClientInServer(int spec) { if (sv_vm) VM_Call(sv_vm, 1, GAME_PUT_CLIENT_IN_SERVER, spec, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else PR1_GamePutClientInServer(spec); } //=========================================================================== // GameClientDisconnect //=========================================================================== void PR2_GameClientDisconnect(int spec) { if (sv_vm) VM_Call(sv_vm, 1, GAME_CLIENT_DISCONNECT, spec, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else PR1_GameClientDisconnect(spec); } //=========================================================================== // GameClientPreThink //=========================================================================== void PR2_GameClientPreThink(int spec) { if (sv_vm) VM_Call(sv_vm, 1, GAME_CLIENT_PRETHINK, spec, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else PR1_GameClientPreThink(spec); } //=========================================================================== // GameClientPostThink //=========================================================================== void PR2_GameClientPostThink(int spec) { if (sv_vm) VM_Call(sv_vm, 1, GAME_CLIENT_POSTTHINK, spec, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else PR1_GameClientPostThink(spec); } //=========================================================================== // ClientCmd return false on unknown command //=========================================================================== qbool PR2_ClientCmd(void) { if (sv_vm) return VM_Call(sv_vm, 0, GAME_CLIENT_COMMAND, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else return PR1_ClientCmd(); } //=========================================================================== // ClientKill //=========================================================================== void PR2_ClientKill(void) { if (sv_vm) PR2_ClientCmd(); // PR2 have some universal way for command execution unlike QC based mods. else PR1_ClientKill(); } //=========================================================================== // ClientSay return false if say unhandled by mod //=========================================================================== qbool PR2_ClientSay(int isTeamSay, char *message) { // // message - used for QC based mods only. // PR2 mods get it from Cmd_Args() and such. // if (sv_vm) return VM_Call(sv_vm, 1, GAME_CLIENT_SAY, isTeamSay, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else return PR1_ClientSay(isTeamSay, message); } //=========================================================================== // GameSetNewParms //=========================================================================== void PR2_GameSetNewParms(void) { if (sv_vm) VM_Call(sv_vm, 0, GAME_SETNEWPARMS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else PR1_GameSetNewParms(); } //=========================================================================== // GameSetNewParms //=========================================================================== void PR2_GameSetChangeParms(void) { if (sv_vm) VM_Call(sv_vm, 0, GAME_SETCHANGEPARMS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else { PR1_GameSetChangeParms(); } } //=========================================================================== // EdictTouch //=========================================================================== void PR2_EdictTouch(func_t f) { if (sv_vm) VM_Call(sv_vm, 0, GAME_EDICT_TOUCH, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else PR1_EdictTouch(f); } //=========================================================================== // EdictThink //=========================================================================== void PR2_EdictThink(func_t f) { if (sv_vm) VM_Call(sv_vm, 0, GAME_EDICT_THINK, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else PR1_EdictThink(f); } //=========================================================================== // EdictBlocked //=========================================================================== void PR2_EdictBlocked(func_t f) { if (sv_vm) VM_Call(sv_vm, 0, GAME_EDICT_BLOCKED, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else PR1_EdictBlocked(f); } //=========================================================================== // UserInfoChanged //=========================================================================== qbool PR2_UserInfoChanged(int after) { if (sv_vm) return VM_Call(sv_vm, 1, GAME_CLIENT_USERINFO_CHANGED, after, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else return PR1_UserInfoChanged(after); } //=========================================================================== // GameShutDown //=========================================================================== void PR2_GameShutDown(void) { if (sv_vm) VM_Call(sv_vm, 0, GAME_SHUTDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else PR1_GameShutDown(); } //=========================================================================== // UnLoadProgs //=========================================================================== void PR2_UnLoadProgs(void) { if (sv_vm) { VM_Free( sv_vm ); sv_vm = NULL; } else { PR1_UnLoadProgs(); } } //=========================================================================== // LoadProgs //=========================================================================== void PR2_LoadProgs(void) { sv_vm = VM_Create(VM_GAME, sv_progsname.string, PR2_GameSystemCalls, sv_progtype.value ); if ( sv_vm ) { ; // nothing. } else { PR1_LoadProgs (); } } //=========================================================================== // GameConsoleCommand //=========================================================================== void PR2_GameConsoleCommand(void) { int old_other, old_self; client_t *cl; int i; if( sv_vm ) { old_self = pr_global_struct->self; old_other = pr_global_struct->other; pr_global_struct->other = 0; //sv_cmd = SV_CMD_CONSOLE; pr_global_struct->self = 0; for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (!cl->state) continue; if ( cl->isBot ) continue; if (NET_CompareAdr(cl->netchan.remote_address, net_from)) { pr_global_struct->self = EDICT_TO_PROG(cl->edict); break; } } VM_Call(sv_vm, 0, GAME_CONSOLE_COMMAND, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); pr_global_struct->self = old_self; pr_global_struct->other = old_other; } } //=========================================================================== // PausedTic //=========================================================================== void PR2_PausedTic(float duration) { if (sv_vm) VM_Call(sv_vm, 1, GAME_PAUSED_TIC, (int)(duration*1000), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); else PR1_PausedTic(duration); } void PR2_ClearEdict(edict_t* e) { if (sv_vm && sv_vm->pr2_references && (sv_vm->type == VMI_NATIVE || sv_vm->type == VMI_BYTECODE || sv_vm->type == VMI_COMPILED)) { int old_self = pr_global_struct->self; pr_global_struct->self = EDICT_TO_PROG(e); VM_Call(sv_vm, 0, GAME_CLEAR_EDICT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); pr_global_struct->self = old_self; } } //=========================================================================== // InitProgs //=========================================================================== #define GAME_API_VERSION_MIN 16 void LoadGameData(intptr_t gamedata_ptr) { #ifdef idx64 gameData_vm_t* gamedata_vm; if (sv_vm->type == VMI_BYTECODE || sv_vm->type == VMI_COMPILED) { gamedata_vm = (gameData_vm_t *)PR2_GetString(gamedata_ptr); gamedata.ents = (intptr_t)gamedata_vm->ents_p; gamedata.global = (intptr_t)gamedata_vm->global_p; gamedata.fields = (intptr_t)gamedata_vm->fields_p; gamedata.APIversion = gamedata_vm->APIversion; gamedata.sizeofent = gamedata_vm->sizeofent; gamedata.maxentities = gamedata_vm->maxentities; return; } #endif gamedata = *(gameData_t *)PR2_GetString(gamedata_ptr); } void LoadFields(void) { #ifdef idx64 if (sv_vm->type == VMI_BYTECODE || sv_vm->type == VMI_COMPILED) { field_vm_t *fieldvm_p; field_t *f; int num = 0; fieldvm_p = (field_vm_t*)PR2_GetString((intptr_t)gamedata.fields); while (fieldvm_p[num].name) { num++; } f = fields = (field_t *)Hunk_Alloc(sizeof(field_t) * (num + 1)); while (fieldvm_p->name){ f->name = (stringptr_t)fieldvm_p->name; f->ofs = fieldvm_p->ofs; f->type = (fieldtype_t)fieldvm_p->type; f++; fieldvm_p++; } f->name = 0; return; } #endif fields = (field_t*)PR2_GetString((intptr_t)gamedata.fields); } extern void PR2_FS_Restart(void); void PR2_InitProg(void) { extern cvar_t sv_pr2references; intptr_t gamedata_ptr; Cvar_SetValue(&sv_pr2references, 0.0f); if (!sv_vm) { PR1_InitProg(); return; } PR2_FS_Restart(); gamedata.APIversion = 0; gamedata_ptr = (intptr_t) VM_Call(sv_vm, 2, GAME_INIT, (int)(sv.time * 1000), (int)(Sys_DoubleTime() * 100000), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (!gamedata_ptr) { SV_Error("PR2_InitProg: gamedata == NULL"); } LoadGameData(gamedata_ptr); if (gamedata.APIversion < GAME_API_VERSION_MIN || gamedata.APIversion > GAME_API_VERSION) { if (GAME_API_VERSION_MIN == GAME_API_VERSION) { SV_Error("PR2_InitProg: Incorrect API version (%i should be %i)", gamedata.APIversion, GAME_API_VERSION); } else { SV_Error("PR2_InitProg: Incorrect API version (%i should be between %i and %i)", gamedata.APIversion, GAME_API_VERSION_MIN, GAME_API_VERSION); } } sv_vm->pr2_references = gamedata.APIversion >= 15 && (int)sv_pr2references.value; #ifdef idx64 if (sv_vm->type == VMI_NATIVE && (!sv_vm->pr2_references || gamedata.APIversion < 15)) SV_Error("PR2_InitProg: Native prog must support sv_pr2references for 64bit mode (mod API version (%i should be 15+))", gamedata.APIversion); #endif pr_edict_size = gamedata.sizeofent; Con_DPrintf("edict size %d\n", pr_edict_size); sv.game_edicts = (entvars_t *)(PR2_GetString((intptr_t)gamedata.ents)); pr_global_struct = (globalvars_t*)PR2_GetString((intptr_t)gamedata.global); pr_globals = (float *)pr_global_struct; LoadFields(); sv.max_edicts = MAX_EDICTS; if (gamedata.APIversion >= 14) { sv.max_edicts = min(sv.max_edicts, gamedata.maxentities); } else { sv.max_edicts = min(sv.max_edicts, 512); } } #endif /* USE_PR2 */ #endif // !CLIENTONLY mvdsv-0.35/src/pr_cmds.c000066400000000000000000001647671427146041000152060ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef CLIENTONLY #include "qwsvdef.h" static tokenizecontext_t pr1_tokencontext; #define RETURN_EDICT(e) (((int *)pr_globals)[OFS_RETURN] = EDICT_TO_PROG(e)) #define RETURN_STRING(s) (PR1_SetString(&((int *)pr_globals)[OFS_RETURN], s)) /* =============================================================================== BUILT-IN FUNCTIONS =============================================================================== */ char *PF_VarString (int first) { int i; static char out[2048]; out[0] = 0; for (i=first ; is_name) ,s); ed = PROG_TO_EDICT(pr_global_struct->self); ED_Print (ed); SV_Error ("Program error"); } /* ================= PF_objerror Dumps out self, then an error message. The program is aborted and self is removed, but the level can continue. objerror(value) ================= */ void PF_objerror (void) { char *s; edict_t *ed; s = PF_VarString(0); Con_Printf ("======OBJECT ERROR in %s:\n%s\n", PR1_GetString(pr_xfunction->s_name),s); ed = PROG_TO_EDICT(pr_global_struct->self); ED_Print (ed); ED_Free (ed); SV_Error ("Program error"); } /* ============== PF_makevectors Writes new values for v_forward, v_up, and v_right based on angles makevectors(vector) ============== */ void PF_makevectors (void) { AngleVectors (G_VECTOR(OFS_PARM0), PR_GLOBAL(v_forward), PR_GLOBAL(v_right), PR_GLOBAL(v_up)); } /* ================= PF_setorigin This is the only valid way to move an object without using the physics of the world (setting velocity and waiting). Directly changing origin will not set internal links correctly, so clipping would be messed up. This should be called when an object is spawned, and then only if it is teleported. setorigin (entity, origin) ================= */ void PF_setorigin (void) { edict_t *e; float *org; e = G_EDICT(OFS_PARM0); org = G_VECTOR(OFS_PARM1); VectorCopy (org, e->v->origin); SV_AntilagReset (e); SV_LinkEdict (e, false); } /* ================= PF_setsize the size box is rotated by the current angle setsize (entity, minvector, maxvector) ================= */ void PF_setsize (void) { edict_t *e; float *min, *max; e = G_EDICT(OFS_PARM0); min = G_VECTOR(OFS_PARM1); max = G_VECTOR(OFS_PARM2); VectorCopy (min, e->v->mins); VectorCopy (max, e->v->maxs); VectorSubtract (max, min, e->v->size); SV_LinkEdict (e, false); } /* ================= PF_setmodel setmodel(entity, model) Also sets size, mins, and maxs for inline bmodels ================= */ static void PF_setmodel (void) { int i; edict_t *e; char *m, **check; cmodel_t *mod; e = G_EDICT(OFS_PARM0); m = G_STRING(OFS_PARM1); // check to see if model was properly precached for (i = 0, check = sv.model_precache; i < MAX_MODELS && *check ; i++, check++) if (!strcmp(*check, m)) goto ok; PR_RunError ("PF_setmodel: no precache: %s\n", m); ok: e->v->model = G_INT(OFS_PARM1); e->v->modelindex = i; // if it is an inline model, get the size information for it if (m[0] == '*') { mod = CM_InlineModel (m); VectorCopy (mod->mins, e->v->mins); VectorCopy (mod->maxs, e->v->maxs); VectorSubtract (mod->maxs, mod->mins, e->v->size); SV_LinkEdict (e, false); } else if (pr_nqprogs) { // hacks to make NQ progs happy if (!strcmp(PR1_GetString(e->v->model), "maps/b_explob.bsp")) { VectorClear (e->v->mins); VectorSet (e->v->maxs, 32, 32, 64); } else { // FTE does this, so we do, too; I'm not sure if it makes a difference VectorSet (e->v->mins, -16, -16, -16); VectorSet (e->v->maxs, 16, 16, 16); } VectorSubtract (e->v->maxs, e->v->mins, e->v->size); SV_LinkEdict (e, false); } } /* ================= PF_bprint broadcast print to everyone on server bprint(value) ================= */ void PF_bprint (void) { char *s; int level; if (pr_nqprogs) { level = PRINT_HIGH; s = PF_VarString(0); } else { level = G_FLOAT(OFS_PARM0); s = PF_VarString(1); } SV_BroadcastPrintf (level, "%s", s); } #define SPECPRINT_CENTERPRINT 0x1 #define SPECPRINT_SPRINT 0x2 #define SPECPRINT_STUFFCMD 0x4 /* ================= PF_sprint single print to a specific client sprint(clientent, value) ================= */ void PF_sprint (void) { char *s; client_t *client, *cl; int entnum; int level; int i; entnum = G_EDICTNUM(OFS_PARM0); if (pr_nqprogs) { level = PRINT_HIGH; s = PF_VarString(1); } else { level = G_FLOAT(OFS_PARM1); s = PF_VarString(2); } if (entnum < 1 || entnum > MAX_CLIENTS) { Con_Printf ("tried to sprint to a non-client\n"); return; } client = &svs.clients[entnum-1]; SV_ClientPrintf (client, level, "%s", s); //bliP: spectator print -> if ((int)sv_specprint.value & SPECPRINT_SPRINT) { for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (!cl->state || !cl->spectator) continue; if ((cl->spec_track == entnum) && (cl->spec_print & SPECPRINT_SPRINT)) SV_ClientPrintf (cl, level, "%s", s); } } //<- } /* ================= PF_centerprint single print to a specific client centerprint(clientent, value) ================= */ void PF_centerprint (void) { char *s; int entnum; client_t *cl, *spec; int i; entnum = G_EDICTNUM(OFS_PARM0); s = PF_VarString(1); if (entnum < 1 || entnum > MAX_CLIENTS) { Con_Printf ("tried to sprint to a non-client\n"); return; } cl = &svs.clients[entnum-1]; ClientReliableWrite_Begin (cl, svc_centerprint, 2 + strlen(s)); ClientReliableWrite_String (cl, s); if (sv.mvdrecording) { if (MVDWrite_Begin (dem_single, entnum - 1, 2 + strlen(s))) { MVD_MSG_WriteByte (svc_centerprint); MVD_MSG_WriteString (s); } } //bliP: spectator print -> if ((int)sv_specprint.value & SPECPRINT_CENTERPRINT) { for (i = 0, spec = svs.clients; i < MAX_CLIENTS; i++, spec++) { if (!cl->state || !spec->spectator) continue; if ((spec->spec_track == entnum) && (cl->spec_print & SPECPRINT_CENTERPRINT)) { ClientReliableWrite_Begin (spec, svc_centerprint, 2 + strlen(s)); ClientReliableWrite_String (spec, s); } } } //<- } /* ================= PF_normalize vector normalize(vector) ================= */ void PF_normalize (void) { float *value1; vec3_t newvalue; float nuw; value1 = G_VECTOR(OFS_PARM0); nuw = value1[0] * value1[0] + value1[1] * value1[1] + value1[2]*value1[2]; nuw = sqrt(nuw); if (nuw == 0) newvalue[0] = newvalue[1] = newvalue[2] = 0; else { nuw = 1/nuw; newvalue[0] = value1[0] * nuw; newvalue[1] = value1[1] * nuw; newvalue[2] = value1[2] * nuw; } VectorCopy (newvalue, G_VECTOR(OFS_RETURN)); } /* ================= PF_vlen scalar vlen(vector) ================= */ void PF_vlen (void) { float *value1; float nuw; value1 = G_VECTOR(OFS_PARM0); nuw = value1[0] * value1[0] + value1[1] * value1[1] + value1[2]*value1[2]; nuw = sqrt(nuw); G_FLOAT(OFS_RETURN) = nuw; } /* ================= PF_vectoyaw float vectoyaw(vector) ================= */ void PF_vectoyaw (void) { float *value1; float yaw; value1 = G_VECTOR(OFS_PARM0); if (value1[1] == 0 && value1[0] == 0) yaw = 0; else { yaw = /*(int)*/ (atan2(value1[1], value1[0]) * 180 / M_PI); if (yaw < 0) yaw += 360; } G_FLOAT(OFS_RETURN) = yaw; } /* ================= PF_vectoangles vector vectoangles(vector) ================= */ void PF_vectoangles (void) { float *value1; float forward; float yaw, pitch; value1 = G_VECTOR(OFS_PARM0); if (value1[1] == 0 && value1[0] == 0) { yaw = 0; if (value1[2] > 0) pitch = 90; else pitch = 270; } else { yaw = /*(int)*/ (atan2(value1[1], value1[0]) * 180 / M_PI); if (yaw < 0) yaw += 360; forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); pitch = /*(int)*/ (atan2(value1[2], forward) * 180 / M_PI); if (pitch < 0) pitch += 360; } G_FLOAT(OFS_RETURN+0) = pitch; G_FLOAT(OFS_RETURN+1) = yaw; G_FLOAT(OFS_RETURN+2) = 0; } /* ================= PF_Random Returns a number from 0<= num < 1 random() ================= */ void PF_random (void) { float num; num = (rand ()&0x7fff) / ((float)0x7fff); G_FLOAT(OFS_RETURN) = num; } /* ================= PF_particle particle(origin, dir, color, count [,replacement_te [,replacement_count]]) = #48 For NQ progs (or as a QW extension) ================= */ static void PF_particle (void) { float *org, *dir; float color, count; int replacement_te; int replacement_count = 0 /* suppress compiler warning */; org = G_VECTOR(OFS_PARM0); dir = G_VECTOR(OFS_PARM1); color = G_FLOAT(OFS_PARM2); count = G_FLOAT(OFS_PARM3); // Progs should provide a tempentity code and particle count for the case // when a client doesn't support svc_particle if (pr_argc >= 5) { replacement_te = G_FLOAT(OFS_PARM4); replacement_count = (pr_argc >= 6) ? G_FLOAT(OFS_PARM5) : 1; } else { // To aid porting of NQ mods, if the extra arguments are not provided, try // to figure out what progs want by inspecting color and count if (count == 255) { replacement_te = TE_EXPLOSION; // count is not used } else if (color == 73) { replacement_te = TE_BLOOD; replacement_count = 1; // FIXME: use count / ? } else if (color == 225) { replacement_te = TE_LIGHTNINGBLOOD; // count is not used } else { replacement_te = 0; // don't send anything } } SV_StartParticle (org, dir, color, count, replacement_te, replacement_count); } /* ================= PF_ambientsound ================= */ void PF_ambientsound (void) { char **check; char *samp; float *pos; float vol, attenuation; int i, soundnum; pos = G_VECTOR (OFS_PARM0); samp = G_STRING(OFS_PARM1); vol = G_FLOAT(OFS_PARM2); attenuation = G_FLOAT(OFS_PARM3); // check to see if samp was properly precached for (soundnum=0, check = sv.sound_precache ; *check ; check++, soundnum++) if (!strcmp(*check,samp)) break; if (!*check) { Con_Printf ("no precache: %s\n", samp); return; } // add an svc_spawnambient command to the level signon packet MSG_WriteByte (&sv.signon,svc_spawnstaticsound); for (i=0 ; i<3 ; i++) MSG_WriteCoord(&sv.signon, pos[i]); MSG_WriteByte (&sv.signon, soundnum); MSG_WriteByte (&sv.signon, vol*255); MSG_WriteByte (&sv.signon, attenuation*64); } /* ================= PF_sound Each entity can have eight independant sound sources, like voice, weapon, feet, etc. Channel 0 is an auto-allocate channel, the others override anything already running on that entity/channel pair. An attenuation of 0 will play full volume everywhere in the level. Larger attenuations will drop off. ================= */ void PF_sound (void) { char *sample; int channel; edict_t *entity; int volume; float attenuation; entity = G_EDICT(OFS_PARM0); channel = G_FLOAT(OFS_PARM1); sample = G_STRING(OFS_PARM2); volume = G_FLOAT(OFS_PARM3) * 255; attenuation = G_FLOAT(OFS_PARM4); SV_StartSound (entity, channel, sample, volume, attenuation); } /* ================= PF_break break() ================= */ void PF_break (void) { Con_Printf ("break statement\n"); *(int *)-4 = 0; // dump to debugger // PR_RunError ("break statement"); } /* ================= PF_traceline Used for use tracing and shot targeting Traces are blocked by bbox and exact bsp entityes, and also slide box entities if the tryents flag is set. traceline (vector1, vector2, tryents) ================= */ void PF_traceline (void) { float *v1, *v2; trace_t trace; int nomonsters; edict_t *ent; v1 = G_VECTOR(OFS_PARM0); v2 = G_VECTOR(OFS_PARM1); nomonsters = G_FLOAT(OFS_PARM2); ent = G_EDICT(OFS_PARM3); if (sv_antilag.value == 2) nomonsters |= MOVE_LAGGED; trace = SV_Trace (v1, vec3_origin, vec3_origin, v2, nomonsters, ent); PR_GLOBAL(trace_allsolid) = trace.allsolid; PR_GLOBAL(trace_startsolid) = trace.startsolid; PR_GLOBAL(trace_fraction) = trace.fraction; PR_GLOBAL(trace_inwater) = trace.inwater; PR_GLOBAL(trace_inopen) = trace.inopen; VectorCopy (trace.endpos, PR_GLOBAL(trace_endpos)); VectorCopy (trace.plane.normal, PR_GLOBAL(trace_plane_normal)); PR_GLOBAL(trace_plane_dist) = trace.plane.dist; if (trace.e.ent) PR_GLOBAL(trace_ent) = EDICT_TO_PROG(trace.e.ent); else PR_GLOBAL(trace_ent) = EDICT_TO_PROG(sv.edicts); } /* ================= PF_checkpos Returns true if the given entity can move to the given position from it's current position by walking or rolling. FIXME: make work... scalar checkpos (entity, vector) ================= */ void PF_checkpos (void) {} //============================================================================ // Unlike Quake's Mod_LeafPVS, CM_LeafPVS returns a pointer to static data // uncompressed at load time, so it's safe to store for future use static byte *checkpvs; int PF_newcheckclient (int check) { int i; edict_t *ent; vec3_t org; // cycle to the next one if (check < 1) check = 1; if (check > MAX_CLIENTS) check = MAX_CLIENTS; if (check == MAX_CLIENTS) i = 1; else i = check + 1; for ( ; ; i++) { if (i == MAX_CLIENTS+1) i = 1; ent = EDICT_NUM(i); if (i == check) break; // didn't find anything else if (ent->e.free) continue; if (ent->v->health <= 0) continue; if ((int)ent->v->flags & FL_NOTARGET) continue; // anything that is a client, or has a client as an enemy break; } // get the PVS for the entity VectorAdd (ent->v->origin, ent->v->view_ofs, org); checkpvs = CM_LeafPVS (CM_PointInLeaf(org)); return i; } /* ================= PF_checkclient Returns a client (or object that has a client enemy) that would be a valid target. If there are more than one valid options, they are cycled each frame If (self.origin + self.viewofs) is not in the PVS of the current target, it is not returned at all. entity checkclient() = #17 ================= */ #define MAX_CHECK 16 static void PF_checkclient (void) { edict_t *ent, *self; int l; vec3_t vieworg; // find a new check if on a new frame if (sv.time - sv.lastchecktime >= 0.1) { sv.lastcheck = PF_newcheckclient (sv.lastcheck); sv.lastchecktime = sv.time; } // return check if it might be visible ent = EDICT_NUM(sv.lastcheck); if (ent->e.free || ent->v->health <= 0) { RETURN_EDICT(sv.edicts); return; } // if current entity can't possibly see the check entity, return 0 self = PROG_TO_EDICT(pr_global_struct->self); VectorAdd (self->v->origin, self->v->view_ofs, vieworg); l = CM_Leafnum(CM_PointInLeaf(vieworg)) - 1; if ( (l<0) || !(checkpvs[l>>3] & (1<<(l&7)) ) ) { RETURN_EDICT(sv.edicts); return; } // might be able to see it RETURN_EDICT(ent); } //============================================================================ /* ================= PF_stuffcmd Sends text over to the client's execution buffer stuffcmd (clientent, value) ================= */ void PF_stuffcmd (void) { int entnum; char *str; client_t *cl, *spec; char *buf; int j; entnum = G_EDICTNUM(OFS_PARM0); if (entnum < 1 || entnum > MAX_CLIENTS) PR_RunError ("Parm 0 not a client"); str = G_STRING(OFS_PARM1); cl = &svs.clients[entnum-1]; if (!strncmp(str, "disconnect\n", MAX_STUFFTEXT)) { // so long and thanks for all the fish cl->drop = true; return; } buf = cl->stufftext_buf; if (strlen(buf) + strlen(str) >= MAX_STUFFTEXT) PR_RunError ("stufftext buffer overflow"); strlcat (buf, str, MAX_STUFFTEXT); if( strchr( buf, '\n' ) ) { ClientReliableWrite_Begin (cl, svc_stufftext, 2+strlen(buf)); ClientReliableWrite_String (cl, buf); if (sv.mvdrecording) { if (MVDWrite_Begin ( dem_single, cl - svs.clients, 2+strlen(buf))) { MVD_MSG_WriteByte (svc_stufftext); MVD_MSG_WriteString (buf); } } //bliP: spectator print -> if ((int)sv_specprint.value & SPECPRINT_STUFFCMD) { for (j = 0, spec = svs.clients; j < MAX_CLIENTS; j++, spec++) { if (!cl->state || !spec->spectator) continue; if ((spec->spec_track == entnum) && (cl->spec_print & SPECPRINT_STUFFCMD)) { ClientReliableWrite_Begin (spec, svc_stufftext, 2+strlen(buf)); ClientReliableWrite_String (spec, buf); } } } //<- buf[0] = 0; } } /* ================= PF_localcmd Sends text over to the client's execution buffer localcmd (string) ================= */ void PF_localcmd (void) { char *str; str = G_STRING(OFS_PARM0); if (pr_nqprogs && !strcmp(str, "restart\n")) { Cbuf_AddText (va("map %s\n", sv.mapname)); return; } Cbuf_AddText (str); } #define MAX_PR_STRING_SIZE 2048 #define MAX_PR_STRINGS 64 int pr_string_index = 0; char pr_string_buf[MAX_PR_STRINGS][MAX_PR_STRING_SIZE]; char *pr_string_temp = pr_string_buf[0]; void PF_SetTempString(void) { pr_string_temp = pr_string_buf[(++pr_string_index) & (MAX_PR_STRINGS - 1)]; } /* ================= PF_tokenize float tokenize(string) ================= */ void PF_tokenize (void) { const char *str; str = G_STRING(OFS_PARM0); Cmd_TokenizeStringEx(&pr1_tokencontext, str); G_FLOAT(OFS_RETURN) = Cmd_ArgcEx(&pr1_tokencontext); } /* ================= PF_argc returns number of tokens (must be executed after PF_Tokanize!) float argc(void) ================= */ void PF_argc (void) { G_FLOAT(OFS_RETURN) = (float) Cmd_ArgcEx(&pr1_tokencontext); } /* ================= PF_argv returns token requested by user (must be executed after PF_Tokanize!) string argc(float) ================= */ void PF_argv (void) { int num; num = (int) G_FLOAT(OFS_PARM0); // if (num < 0 ) num = 0; // if (num > Cmd_Argc()-1) num = Cmd_Argc()-1; if (num < 0 || num >= Cmd_ArgcEx(&pr1_tokencontext)) { RETURN_STRING(""); } else { snprintf(pr_string_temp, MAX_PR_STRING_SIZE, "%s", Cmd_ArgvEx(&pr1_tokencontext, num)); RETURN_STRING(pr_string_temp); PF_SetTempString(); } } /* ================= PF_substr string substr(string str, float start, float len) ================= */ void PF_substr (void) { char *s; int start, len, l; s = G_STRING(OFS_PARM0); start = (int) G_FLOAT(OFS_PARM1); len = (int) G_FLOAT(OFS_PARM2); l = strlen(s); if (start >= l || !len || !*s) { PR_SetTmpString(&G_INT(OFS_RETURN), ""); return; } s += start; l -= start; if (len > l + 1) len = l + 1; strlcpy(pr_string_temp, s, len + 1); PR1_SetString(&G_INT(OFS_RETURN), pr_string_temp); PF_SetTempString(); } /* ================= PF_strcat string strcat(string str1, string str2) ================= */ void PF_strcat (void) { strlcpy(pr_string_temp, PF_VarString(0), MAX_PR_STRING_SIZE); PR1_SetString(&G_INT(OFS_RETURN), pr_string_temp); PF_SetTempString(); } /* ================= PF_strlen float strlen(string str) ================= */ void PF_strlen (void) { G_FLOAT(OFS_RETURN) = (float) strlen(G_STRING(OFS_PARM0)); } /* ================= PF_strzone ZQ_QC_STRINGS string newstr (string str [, float size]) The 'size' parameter is not QSG but an MVDSV extension ================= */ void PF_strzone (void) { char *s; int i, size; s = G_STRING(OFS_PARM0); for (i = 0; i < MAX_PRSTR; i++) { if (!pr_newstrtbl[i] || pr_newstrtbl[i] == pr_strings) break; } if (i == MAX_PRSTR) PR_RunError("strzone: out of string memory"); size = strlen(s) + 1; if (pr_argc == 2 && (int) G_FLOAT(OFS_PARM1) > size) size = (int) G_FLOAT(OFS_PARM1); pr_newstrtbl[i] = (char *) Q_malloc(size); strlcpy(pr_newstrtbl[i], s, size); G_INT(OFS_RETURN) = -(i+MAX_PRSTR); } /* ================= PF_strunzone ZQ_QC_STRINGS void strunzone (string str) ================= */ void PF_strunzone (void) { int num; num = G_INT(OFS_PARM0); if (num > - MAX_PRSTR) PR_RunError("strunzone: not a dynamic string"); if (num <= -(MAX_PRSTR * 2)) PR_RunError ("strunzone: bad string"); num = - (num + MAX_PRSTR); if (pr_newstrtbl[num] == pr_strings) return; // allow multiple strunzone on the same string (like free in C) Q_free(pr_newstrtbl[num]); pr_newstrtbl[num] = pr_strings; } void PF_clear_strtbl(void) { int i; for (i = 0; i < MAX_PRSTR; i++) { if (pr_newstrtbl[i] && pr_newstrtbl[i] != pr_strings) { Q_free(pr_newstrtbl[i]); pr_newstrtbl[i] = NULL; } } } //FTE_CALLTIMEOFDAY void PF_calltimeofday (void) { extern func_t ED_FindFunctionOffset (char *name); func_t f; if ((f = ED_FindFunctionOffset ("timeofday"))) { date_t date; SV_TimeOfDay(&date, "%a %b %d, %H:%M:%S %Y"); G_FLOAT(OFS_PARM0) = (float)date.sec; G_FLOAT(OFS_PARM1) = (float)date.min; G_FLOAT(OFS_PARM2) = (float)date.hour; G_FLOAT(OFS_PARM3) = (float)date.day; G_FLOAT(OFS_PARM4) = (float)date.mon; G_FLOAT(OFS_PARM5) = (float)date.year; PR_SetTmpString(&G_INT(OFS_PARM6), date.str); PR_ExecuteProgram(f); } } /* ================= PF_cvar float cvar (string) ================= */ void PF_cvar (void) { char *str; str = G_STRING(OFS_PARM0); if (!strcasecmp(str, "pr_checkextension")) { // we do support PF_checkextension G_FLOAT(OFS_RETURN) = 1.0; return; } if (pr_nqprogs && !pr_globals[35]/* deathmatch */ && (!strcmp(str, "timelimit") || !strcmp(str, "samelevel")) ) { // workaround for NQ progs bug: timelimit and samelevel are checked in SP/coop G_FLOAT(OFS_RETURN) = 0.0; return; } G_FLOAT(OFS_RETURN) = Cvar_Value (str); } /* ================= PF_cvar_set float cvar (string) ================= */ void PF_cvar_set (void) { char *var_name, *val; cvar_t *var; var_name = G_STRING(OFS_PARM0); val = G_STRING(OFS_PARM1); var = Cvar_Find(var_name); if (!var) { Con_Printf ("PF_cvar_set: variable %s not found\n", var_name); return; } Cvar_Set (var, val); } /* ================= PF_findradius Returns a chain of entities that have origins within a spherical area findradius (origin, radius) ================= */ static void PF_findradius (void) { int i, j, numtouch; edict_t *touchlist[MAX_EDICTS], *ent, *chain; float rad, rad_2, *org; vec3_t mins, maxs, eorg; org = G_VECTOR(OFS_PARM0); rad = G_FLOAT(OFS_PARM1); rad_2 = rad * rad; for (i = 0; i < 3; i++) { mins[i] = org[i] - rad - 1; // enlarge the bbox a bit maxs[i] = org[i] + rad + 1; } numtouch = SV_AreaEdicts (mins, maxs, touchlist, sv.max_edicts, AREA_SOLID); numtouch += SV_AreaEdicts (mins, maxs, &touchlist[numtouch], sv.max_edicts - numtouch, AREA_TRIGGERS); chain = (edict_t *)sv.edicts; // touch linked edicts for (i = 0; i < numtouch; i++) { ent = touchlist[i]; if (ent->v->solid == SOLID_NOT) continue; // FIXME? for (j = 0; j < 3; j++) eorg[j] = org[j] - (ent->v->origin[j] + (ent->v->mins[j] + ent->v->maxs[j]) * 0.5); if (DotProduct(eorg, eorg) > rad_2) continue; ent->v->chain = EDICT_TO_PROG(chain); chain = ent; } RETURN_EDICT(chain); } /* ========= PF_dprint ========= */ void PF_dprint (void) { Con_Printf ("%s",PF_VarString(0)); } void PF_ftos (void) { float v; v = G_FLOAT(OFS_PARM0); if (v == (int)v) snprintf (pr_string_temp, MAX_PR_STRING_SIZE, "%d",(int)v); else snprintf (pr_string_temp, MAX_PR_STRING_SIZE, "%5.1f",v); PR1_SetString(&G_INT(OFS_RETURN), pr_string_temp); PF_SetTempString(); } void PF_fabs (void) { float v; v = G_FLOAT(OFS_PARM0); G_FLOAT(OFS_RETURN) = fabs(v); } void PF_vtos (void) { snprintf (pr_string_temp, MAX_PR_STRING_SIZE, "'%5.1f %5.1f %5.1f'", G_VECTOR(OFS_PARM0)[0], G_VECTOR(OFS_PARM0)[1], G_VECTOR(OFS_PARM0)[2]); PR1_SetString(&G_INT(OFS_RETURN), pr_string_temp); PF_SetTempString(); } void PF_Spawn (void) { edict_t *ed; ed = ED_Alloc(); RETURN_EDICT(ed); } void PF_Remove (void) { edict_t *ed; ed = G_EDICT(OFS_PARM0); ED_Free (ed); } // entity (entity start, .string field, string match) find = #5; void PF_Find (void) { int e; int f; char *s, *t; edict_t *ed; e = G_EDICTNUM(OFS_PARM0); f = G_INT(OFS_PARM1); s = G_STRING(OFS_PARM2); if (!s) PR_RunError ("PF_Find: bad search string"); for (e++ ; e < sv.num_edicts ; e++) { ed = EDICT_NUM(e); if (ed->e.free) continue; t = E_STRING(ed,f); if (!t) continue; if (!strcmp(t,s)) { RETURN_EDICT(ed); return; } } RETURN_EDICT(sv.edicts); } void PR_CheckEmptyString (char *s) { if (s[0] <= ' ') PR_RunError ("Bad string"); } void PF_precache_file (void) { // precache_file is only used to copy files with qcc, it does nothing G_INT(OFS_RETURN) = G_INT(OFS_PARM0); } void PF_precache_sound (void) { char *s; int i; if (sv.state != ss_loading) PR_RunError ("PF_Precache_*: Precache can only be done in spawn functions"); s = G_STRING(OFS_PARM0); G_INT(OFS_RETURN) = G_INT(OFS_PARM0); PR_CheckEmptyString (s); for (i=0 ; iself); yaw = G_FLOAT(OFS_PARM0); dist = G_FLOAT(OFS_PARM1); if ( !( (int)ent->v->flags & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) { G_FLOAT(OFS_RETURN) = 0; return; } yaw = yaw*M_PI*2 / 360; move[0] = cos(yaw)*dist; move[1] = sin(yaw)*dist; move[2] = 0; // save program state, because SV_movestep may call other progs oldf = pr_xfunction; oldself = pr_global_struct->self; G_FLOAT(OFS_RETURN) = SV_movestep(ent, move, true); // restore program state pr_xfunction = oldf; pr_global_struct->self = oldself; } /* =============== PF_droptofloor void() droptofloor =============== */ void PF_droptofloor (void) { edict_t *ent; vec3_t end; trace_t trace; ent = PROG_TO_EDICT(pr_global_struct->self); VectorCopy (ent->v->origin, end); end[2] -= 256; trace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, end, false, ent); if (trace.fraction == 1 || trace.allsolid) G_FLOAT(OFS_RETURN) = 0; else { VectorCopy (trace.endpos, ent->v->origin); SV_LinkEdict (ent, false); ent->v->flags = (int)ent->v->flags | FL_ONGROUND; ent->v->groundentity = EDICT_TO_PROG(trace.e.ent); G_FLOAT(OFS_RETURN) = 1; } } /* =============== PF_lightstyle void(float style, string value) lightstyle =============== */ void PF_lightstyle (void) { int style; char *val; client_t *client; int j; style = G_FLOAT(OFS_PARM0); val = G_STRING(OFS_PARM1); // change the string in sv sv.lightstyles[style] = val; // send message to all clients on this server if (sv.state != ss_active) return; for (j=0, client = svs.clients ; jstate == cs_spawned ) { ClientReliableWrite_Begin (client, svc_lightstyle, strlen(val)+3); ClientReliableWrite_Char (client, style); ClientReliableWrite_String (client, val); } if (sv.mvdrecording) { if (MVDWrite_Begin( dem_all, 0, strlen(val)+3)) { MVD_MSG_WriteByte(svc_lightstyle); MVD_MSG_WriteChar(style); MVD_MSG_WriteString(val); } } } void PF_rint (void) { float f; f = G_FLOAT(OFS_PARM0); if (f > 0) G_FLOAT(OFS_RETURN) = (int)(f + 0.5); else G_FLOAT(OFS_RETURN) = (int)(f - 0.5); } void PF_floor (void) { G_FLOAT(OFS_RETURN) = floor(G_FLOAT(OFS_PARM0)); } void PF_ceil (void) { G_FLOAT(OFS_RETURN) = ceil(G_FLOAT(OFS_PARM0)); } /* ============= PF_checkbottom ============= */ void PF_checkbottom (void) { edict_t *ent; ent = G_EDICT(OFS_PARM0); G_FLOAT(OFS_RETURN) = SV_CheckBottom (ent); } /* ============= PF_pointcontents ============= */ void PF_pointcontents (void) { float *v; v = G_VECTOR(OFS_PARM0); G_FLOAT(OFS_RETURN) = SV_PointContents (v); } /* ============= PF_nextent entity nextent(entity) ============= */ void PF_nextent (void) { int i; edict_t *ent; i = G_EDICTNUM(OFS_PARM0); while (1) { i++; if (i == sv.num_edicts) { RETURN_EDICT(sv.edicts); return; } ent = EDICT_NUM(i); if (!ent->e.free) { RETURN_EDICT(ent); return; } } } /* ============= PF_aim Pick a vector for the player to shoot along vector aim(entity, missilespeed) ============= */ void PF_aim (void) { VectorCopy (PR_GLOBAL(v_forward), G_VECTOR(OFS_RETURN)); } /* ============== PF_changeyaw This was a major timewaster in progs, so it was converted to C ============== */ void PF_changeyaw (void) { edict_t *ent; float ideal, current, move, speed; ent = PROG_TO_EDICT(pr_global_struct->self); current = anglemod( ent->v->angles[1] ); ideal = ent->v->ideal_yaw; speed = ent->v->yaw_speed; if (current == ideal) return; move = ideal - current; if (ideal > current) { if (move >= 180) move = move - 360; } else { if (move <= -180) move = move + 360; } if (move > 0) { if (move > speed) move = speed; } else { if (move < -speed) move = -speed; } ent->v->angles[1] = anglemod (current + move); } /* =============================================================================== MESSAGE WRITING =============================================================================== */ #define MSG_BROADCAST 0 // unreliable to all #define MSG_ONE 1 // reliable to one (msg_entity) #define MSG_ALL 2 // reliable to all #define MSG_INIT 3 // write to the init string #define MSG_MULTICAST 4 // for multicast() sizebuf_t *WriteDest (void) { int dest; // int entnum; // edict_t *ent; dest = G_FLOAT(OFS_PARM0); switch (dest) { case MSG_BROADCAST: return &sv.datagram; case MSG_ONE: SV_Error("Shouldn't be at MSG_ONE"); #if 0 ent = PROG_TO_EDICT(PR_GLOBAL(msg_entity)); entnum = NUM_FOR_EDICT(ent); if (entnum < 1 || entnum > MAX_CLIENTS) PR_RunError ("WriteDest: not a client"); return &svs.clients[entnum-1].netchan.message; #endif case MSG_ALL: return &sv.reliable_datagram; case MSG_INIT: if (sv.state != ss_loading) PR_RunError ("PF_Write_*: MSG_INIT can only be written in spawn functions"); return &sv.signon; case MSG_MULTICAST: return &sv.multicast; default: PR_RunError ("WriteDest: bad destination"); break; } return NULL; } static client_t *Write_GetClient(void) { int entnum; edict_t *ent; ent = PROG_TO_EDICT(PR_GLOBAL(msg_entity)); entnum = NUM_FOR_EDICT(ent); if (entnum < 1 || entnum > MAX_CLIENTS) PR_RunError ("WriteDest: not a client"); return &svs.clients[entnum-1]; } #ifdef WITH_NQPROGS static byte nqp_buf_data[1024] /* must be large enough for svc_finale text */; static sizebuf_t nqp_buf; static qbool nqp_ignore_this_frame; static int nqp_expect; void NQP_Reset (void) { nqp_ignore_this_frame = false; nqp_expect = 0; SZ_Init (&nqp_buf, nqp_buf_data, sizeof(nqp_buf_data)); } static void NQP_Flush (int count) { // FIXME, we make no distinction reliable or not assert (count <= nqp_buf.cursize); SZ_Write (&sv.reliable_datagram, nqp_buf_data, count); memmove (nqp_buf_data, nqp_buf_data + count, nqp_buf.cursize - count); nqp_buf.cursize -= count; } static void NQP_Skip (int count) { assert (count <= nqp_buf.cursize); memmove (nqp_buf_data, nqp_buf_data + count, nqp_buf.cursize - count); nqp_buf.cursize -= count; } static void NQP_Process (void) { int cmd; if (nqp_ignore_this_frame) { SZ_Clear (&nqp_buf); return; } while (1) { if (nqp_expect) { if (nqp_buf.cursize >= nqp_expect) { NQP_Flush (nqp_expect); nqp_expect = 0; } else break; } if (!nqp_buf.cursize) break; nqp_expect = 0; cmd = nqp_buf_data[0]; if (cmd == svc_killedmonster || cmd == svc_foundsecret || cmd == svc_sellscreen) nqp_expect = 1; else if (cmd == svc_updatestat) { NQP_Skip (1); MSG_WriteByte (&sv.reliable_datagram, svc_updatestatlong); nqp_expect = 5; } else if (cmd == svc_print) { byte *p = (byte *)memchr (nqp_buf_data + 1, 0, nqp_buf.cursize - 1); if (!p) goto waitformore; MSG_WriteByte(&sv.reliable_datagram, svc_print); MSG_WriteByte(&sv.reliable_datagram, PRINT_HIGH); MSG_WriteString(&sv.reliable_datagram, (char *)(nqp_buf_data + 1)); NQP_Skip(p - nqp_buf_data + 1); } else if (cmd == nq_svc_setview) { if (nqp_buf.cursize < 3) goto waitformore; NQP_Skip (3); // TODO: make an extension for this } else if (cmd == svc_updatefrags) nqp_expect = 4; else if (cmd == nq_svc_updatecolors) { if (nqp_buf.cursize < 3) goto waitformore; MSG_WriteByte (&sv.reliable_datagram, svc_setinfo); MSG_WriteByte (&sv.reliable_datagram, nqp_buf_data[1]); MSG_WriteString (&sv.reliable_datagram, "topcolor"); MSG_WriteString (&sv.reliable_datagram, va("%i", min(nqp_buf_data[2] & 15, 13))); MSG_WriteByte (&sv.reliable_datagram, svc_setinfo); MSG_WriteByte (&sv.reliable_datagram, nqp_buf_data[1]); MSG_WriteString (&sv.reliable_datagram, "bottomcolor"); MSG_WriteString (&sv.reliable_datagram, va("%i", min((nqp_buf_data[2] >> 4)& 15, 13))); NQP_Skip (3); } else if (cmd == nq_svc_updatename) { int slot; byte *p; if (nqp_buf.cursize < 3) goto waitformore; slot = nqp_buf_data[1]; p = (byte *)memchr (nqp_buf_data + 2, 0, nqp_buf.cursize - 2); if (!p) goto waitformore; MSG_WriteByte (&sv.reliable_datagram, svc_setinfo); MSG_WriteByte (&sv.reliable_datagram, slot); MSG_WriteString (&sv.reliable_datagram, "name"); MSG_WriteString (&sv.reliable_datagram, (char *)nqp_buf_data + 2); MSG_WriteByte (&sv.reliable_datagram, svc_updateping); MSG_WriteByte (&sv.reliable_datagram, slot); MSG_WriteShort (&sv.reliable_datagram, 0); // We expect bots to set their name when they enter the game and never change it MSG_WriteByte (&sv.reliable_datagram, svc_updateentertime); MSG_WriteByte (&sv.reliable_datagram, slot); MSG_WriteFloat (&sv.reliable_datagram, 0); NQP_Skip ((p - nqp_buf_data) + 1); } else if (cmd == svc_cdtrack) { if (nqp_buf.cursize < 3) goto waitformore; NQP_Flush (2); NQP_Skip (1); } else if (cmd == svc_finale) { byte *p = (byte *)memchr (nqp_buf_data + 1, 0, nqp_buf.cursize - 1); if (!p) goto waitformore; nqp_expect = (p - nqp_buf_data) + 1; } else if (cmd == svc_intermission) { int i; NQP_Flush (1); for (i = 0; i < 3; i++) MSG_WriteCoord (&sv.reliable_datagram, svs.clients[0].edict->v->origin[i]); for (i = 0; i < 3; i++) MSG_WriteAngle (&sv.reliable_datagram, svs.clients[0].edict->v->angles[i]); } else if (cmd == nq_svc_cutscene) { byte *p = (byte *)memchr (nqp_buf_data + 1, 0, nqp_buf.cursize - 1); if (!p) goto waitformore; MSG_WriteByte (&sv.reliable_datagram, svc_stufftext); MSG_WriteString (&sv.reliable_datagram, "//cutscene\n"); // ZQ extension NQP_Skip (p - nqp_buf_data + 1); } else if (nqp_buf_data[0] == svc_temp_entity) { if (nqp_buf.cursize < 2) break; switch (nqp_buf_data[1]) { case TE_SPIKE: case TE_SUPERSPIKE: case TE_EXPLOSION: case TE_TAREXPLOSION: case TE_WIZSPIKE: case TE_KNIGHTSPIKE: case TE_LAVASPLASH: case TE_TELEPORT: nqp_expect = 8; break; case TE_GUNSHOT: if (nqp_buf.cursize < 8) goto waitformore; NQP_Flush (2); MSG_WriteByte (&sv.reliable_datagram, 1); NQP_Flush (6); break; case TE_LIGHTNING1: case TE_LIGHTNING2: case TE_LIGHTNING3: nqp_expect = 16; break; case NQ_TE_BEAM: { int entnum; if (nqp_buf.cursize < 16) goto waitformore; MSG_WriteByte (&sv.reliable_datagram, svc_temp_entity); MSG_WriteByte (&sv.reliable_datagram, TE_LIGHTNING1); entnum = nqp_buf_data[2] + nqp_buf_data[3]*256; if ((unsigned int)entnum > 1023) entnum = 0; MSG_WriteShort (&sv.reliable_datagram, (short)(entnum - 1288)); NQP_Skip (4); nqp_expect = 12; break; } case NQ_TE_EXPLOSION2: nqp_expect = 10; break; default: Con_Printf ("WARNING: progs.dat sent an unsupported svc_temp_entity: %i\n", nqp_buf_data[1]); goto ignore; } } else { Con_Printf ("WARNING: progs.dat sent an unsupported svc: %i\n", cmd); ignore: nqp_ignore_this_frame = true; break; } } waitformore:; } #else // !WITH_NQPROGS #define NQP_Process() sizebuf_t nqp_buf; // dummy #endif void PF_WriteByte (void) { if (pr_nqprogs) { if (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT) return; // we don't support this MSG_WriteByte (&nqp_buf, G_FLOAT(OFS_PARM1)); NQP_Process (); return; } if (G_FLOAT(OFS_PARM0) == MSG_ONE) { client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, 1); ClientReliableWrite_Byte(cl, G_FLOAT(OFS_PARM1)); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 1)) { MVD_MSG_WriteByte(G_FLOAT(OFS_PARM1)); } } } else MSG_WriteByte (WriteDest(), G_FLOAT(OFS_PARM1)); } void PF_WriteChar (void) { if (pr_nqprogs) { if (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT) return; // we don't support this MSG_WriteByte (&nqp_buf, G_FLOAT(OFS_PARM1)); NQP_Process (); return; } if (G_FLOAT(OFS_PARM0) == MSG_ONE) { client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, 1); ClientReliableWrite_Char(cl, G_FLOAT(OFS_PARM1)); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 1)) { MVD_MSG_WriteByte(G_FLOAT(OFS_PARM1)); } } } else MSG_WriteChar (WriteDest(), G_FLOAT(OFS_PARM1)); } void PF_WriteShort (void) { if (pr_nqprogs) { if (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT) return; // we don't support this MSG_WriteShort (&nqp_buf, G_FLOAT(OFS_PARM1)); NQP_Process (); return; } if (G_FLOAT(OFS_PARM0) == MSG_ONE) { client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, 2); ClientReliableWrite_Short(cl, G_FLOAT(OFS_PARM1)); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 2)) { MVD_MSG_WriteShort(G_FLOAT(OFS_PARM1)); } } } else MSG_WriteShort (WriteDest(), G_FLOAT(OFS_PARM1)); } void PF_WriteLong (void) { if (pr_nqprogs) { if (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT) return; // we don't support this MSG_WriteLong (&nqp_buf, G_FLOAT(OFS_PARM1)); NQP_Process (); return; } if (G_FLOAT(OFS_PARM0) == MSG_ONE) { client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, 4); ClientReliableWrite_Long(cl, G_FLOAT(OFS_PARM1)); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 4)) { MVD_MSG_WriteLong(G_FLOAT(OFS_PARM1)); } } } else MSG_WriteLong (WriteDest(), G_FLOAT(OFS_PARM1)); } void PF_WriteAngle (void) { if (pr_nqprogs) { if (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT) return; // we don't support this MSG_WriteAngle (&nqp_buf, G_FLOAT(OFS_PARM1)); NQP_Process (); return; } if (G_FLOAT(OFS_PARM0) == MSG_ONE) { #ifdef FTE_PEXT_FLOATCOORDS int size = msg_anglesize; #else int size = 1; #endif client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, size); ClientReliableWrite_Angle(cl, G_FLOAT(OFS_PARM1)); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, size)) { MVD_MSG_WriteAngle(G_FLOAT(OFS_PARM1)); } } } else MSG_WriteAngle (WriteDest(), G_FLOAT(OFS_PARM1)); } void PF_WriteCoord (void) { if (pr_nqprogs) { if (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT) return; // we don't support this MSG_WriteCoord (&nqp_buf, G_FLOAT(OFS_PARM1)); NQP_Process (); return; } if (G_FLOAT(OFS_PARM0) == MSG_ONE) { #ifdef FTE_PEXT_FLOATCOORDS int size = msg_coordsize; #else int size = 2; #endif client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, size); ClientReliableWrite_Coord(cl, G_FLOAT(OFS_PARM1)); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, size)) { MVD_MSG_WriteCoord(G_FLOAT(OFS_PARM1)); } } } else MSG_WriteCoord (WriteDest(), G_FLOAT(OFS_PARM1)); } void PF_WriteString (void) { if (pr_nqprogs) { if (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT) return; // we don't support this MSG_WriteString (&nqp_buf, G_STRING(OFS_PARM1)); NQP_Process (); return; } if (G_FLOAT(OFS_PARM0) == MSG_ONE) { client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, 1+strlen(G_STRING(OFS_PARM1))); ClientReliableWrite_String(cl, G_STRING(OFS_PARM1)); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 1 + strlen(G_STRING(OFS_PARM1)))) { MVD_MSG_WriteString(G_STRING(OFS_PARM1)); } } } else MSG_WriteString (WriteDest(), G_STRING(OFS_PARM1)); } void PF_WriteEntity (void) { if (pr_nqprogs) { if (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT) return; // we don't support this MSG_WriteShort (&nqp_buf, G_EDICTNUM(OFS_PARM1)); NQP_Process (); return; } if (G_FLOAT(OFS_PARM0) == MSG_ONE) { client_t *cl = Write_GetClient(); ClientReliableCheckBlock(cl, 2); ClientReliableWrite_Short(cl, G_EDICTNUM(OFS_PARM1)); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, cl - svs.clients, 2)) { MVD_MSG_WriteShort(G_EDICTNUM(OFS_PARM1)); } } } else MSG_WriteShort (WriteDest(), G_EDICTNUM(OFS_PARM1)); } //============================================================================= int SV_ModelIndex (char *name); void PF_makestatic (void) { entity_state_t* s; edict_t *ent; ent = G_EDICT(OFS_PARM0); if (sv.static_entity_count >= sizeof(sv.static_entities) / sizeof(sv.static_entities[0])) { ED_Free (ent); return; } s = &sv.static_entities[sv.static_entity_count]; memset(s, 0, sizeof(sv.static_entities[0])); s->number = sv.static_entity_count + 1; s->modelindex = SV_ModelIndex(PR_GetEntityString(ent->v->model)); if (!s->modelindex) { ED_Free (ent); return; } s->frame = ent->v->frame; s->colormap = ent->v->colormap; s->skinnum = ent->v->skin; VectorCopy(ent->v->origin, s->origin); VectorCopy(ent->v->angles, s->angles); ++sv.static_entity_count; // throw the entity away now ED_Free (ent); } //============================================================================= /* ============== PF_setspawnparms ============== */ void PF_setspawnparms (void) { edict_t *ent; int i; client_t *client; ent = G_EDICT(OFS_PARM0); i = NUM_FOR_EDICT(ent); if (i < 1 || i > MAX_CLIENTS) PR_RunError ("Entity is not a client"); // copy spawn parms out of the client_t client = svs.clients + (i-1); for (i=0 ; i< NUM_SPAWN_PARMS ; i++) (&PR_GLOBAL(parm1))[i] = client->spawn_parms[i]; } /* ============== PF_changelevel ============== */ void PF_changelevel (void) { char *s; static int last_spawncount; // make sure we don't issue two changelevels if (svs.spawncount == last_spawncount) return; last_spawncount = svs.spawncount; s = G_STRING(OFS_PARM0); Cbuf_AddText (va("map %s\n",s)); } /* ============== PF_logfrag logfrag (killer, killee) ============== */ void PF_logfrag (void) { edict_t *ent1, *ent2; int e1, e2; char *s; // -> scream time_t t; struct tm *tblock; // <- ent1 = G_EDICT(OFS_PARM0); ent2 = G_EDICT(OFS_PARM1); e1 = NUM_FOR_EDICT(ent1); e2 = NUM_FOR_EDICT(ent2); if (e1 < 1 || e1 > MAX_CLIENTS || e2 < 1 || e2 > MAX_CLIENTS) return; // -> scream t = time (NULL); tblock = localtime (&t); //bliP: date check -> if (!tblock) s = va("%s\n", "#bad date#"); else if ((int)frag_log_type.value) // need for old-style frag log file s = va("\\frag\\%s\\%s\\%s\\%s\\%d-%d-%d %d:%d:%d\\\n", svs.clients[e1-1].name, svs.clients[e2-1].name, svs.clients[e1-1].team, svs.clients[e2-1].team, tblock->tm_year + 1900, tblock->tm_mon + 1, tblock->tm_mday, tblock->tm_hour, tblock->tm_min, tblock->tm_sec); else s = va("\\%s\\%s\\\n",svs.clients[e1-1].name, svs.clients[e2-1].name); // <- SZ_Print (&svs.log[svs.logsequence&1], s); SV_Write_Log(FRAG_LOG, 1, s); // SV_Write_Log(MOD_FRAG_LOG, 1, "\n==== PF_logfrag ===={\n"); // SV_Write_Log(MOD_FRAG_LOG, 1, va("%d\n", time(NULL))); // SV_Write_Log(MOD_FRAG_LOG, 1, s); // SV_Write_Log(MOD_FRAG_LOG, 1, "}====================\n"); } /* ============== PF_infokey string(entity e, string key) infokey ============== */ void PF_infokey (void) { edict_t *e; int e1; char *value; char *key; static char ov[256]; client_t *cl; e = G_EDICT(OFS_PARM0); e1 = NUM_FOR_EDICT(e); key = G_STRING(OFS_PARM1); cl = &svs.clients[e1-1]; if (e1 == 0) { if ((value = Info_ValueForKey (svs.info, key)) == NULL || !*value) value = Info_Get(&_localinfo_, key); } else if (e1 <= MAX_CLIENTS) { value = ov; if (!strncmp(key, "ip", 3)) strlcpy(ov, NET_BaseAdrToString (cl->netchan.remote_address), sizeof(ov)); else if (!strncmp(key, "realip", 7)) strlcpy(ov, NET_BaseAdrToString (cl->realip), sizeof(ov)); else if (!strncmp(key, "download", 9)) //snprintf(ov, sizeof(ov), "%d", cl->download != NULL ? (int)(100*cl->downloadcount/cl->downloadsize) : -1); snprintf(ov, sizeof(ov), "%d", cl->file_percent ? cl->file_percent : -1); //bliP: file percent else if (!strncmp(key, "ping", 5)) snprintf(ov, sizeof(ov), "%d", SV_CalcPing (cl)); else if (!strncmp(key, "*userid", 8)) snprintf(ov, sizeof(ov), "%d", svs.clients[e1 - 1].userid); else if (!strncmp(key, "login", 6)) value = cl->login; else value = Info_Get (&cl->_userinfo_ctx_, key); } else value = ""; strlcpy(pr_string_temp, value, MAX_PR_STRING_SIZE); RETURN_STRING(pr_string_temp); PF_SetTempString(); } /* ============== PF_stof float(string s) stof ============== */ void PF_stof (void) { char *s; s = G_STRING(OFS_PARM0); G_FLOAT(OFS_RETURN) = Q_atof(s); } /* ============== PF_multicast void(vector where, float set) multicast ============== */ void PF_multicast (void) { float *o; int to; o = G_VECTOR(OFS_PARM0); to = G_FLOAT(OFS_PARM1); SV_Multicast (o, to); } //DP_QC_SINCOSSQRTPOW //float sin(float x) = #60 void PF_sin (void) { G_FLOAT(OFS_RETURN) = sin(G_FLOAT(OFS_PARM0)); } //DP_QC_SINCOSSQRTPOW //float cos(float x) = #61 void PF_cos (void) { G_FLOAT(OFS_RETURN) = cos(G_FLOAT(OFS_PARM0)); } //DP_QC_SINCOSSQRTPOW //float sqrt(float x) = #62 void PF_sqrt (void) { G_FLOAT(OFS_RETURN) = sqrt(G_FLOAT(OFS_PARM0)); } //DP_QC_SINCOSSQRTPOW //float pow(float x, float y) = #97; static void PF_pow (void) { G_FLOAT(OFS_RETURN) = pow(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1)); } //DP_QC_MINMAXBOUND //float min(float a, float b, ...) = #94 void PF_min (void) { // LordHavoc: 3+ argument enhancement suggested by FrikaC if (pr_argc == 2) G_FLOAT(OFS_RETURN) = min(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1)); else if (pr_argc >= 3) { int i; float f = G_FLOAT(OFS_PARM0); for (i = 1;i < pr_argc;i++) if (G_FLOAT((OFS_PARM0+i*3)) < f) f = G_FLOAT((OFS_PARM0+i*3)); G_FLOAT(OFS_RETURN) = f; } else Sys_Error("min: must supply at least 2 floats"); } //DP_QC_MINMAXBOUND //float max(float a, float b, ...) = #95 void PF_max (void) { // LordHavoc: 3+ argument enhancement suggested by FrikaC if (pr_argc == 2) G_FLOAT(OFS_RETURN) = max(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1)); else if (pr_argc >= 3) { int i; float f = G_FLOAT(OFS_PARM0); for (i = 1;i < pr_argc;i++) if (G_FLOAT((OFS_PARM0+i*3)) > f) f = G_FLOAT((OFS_PARM0+i*3)); G_FLOAT(OFS_RETURN) = f; } else Sys_Error("max: must supply at least 2 floats"); } //DP_QC_MINMAXBOUND //float bound(float min, float value, float max) = #96 void PF_bound (void) { G_FLOAT(OFS_RETURN) = bound(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1), G_FLOAT(OFS_PARM2)); } /* ================= PF_tracebox Like traceline but traces a box of the size specified (NOTE: currently the hull size can only be one of the sizes used in the map for bmodel collisions, entity collisions will pay attention to the exact size specified however, this is a collision code limitation in quake itself, and will be fixed eventually). DP_QC_TRACEBOX void(vector v1, vector mins, vector maxs, vector v2, float nomonsters, entity ignore) tracebox = #90; ================= */ static void PF_tracebox (void) { float *v1, *v2, *mins, *maxs; edict_t *ent; int nomonsters; trace_t trace; v1 = G_VECTOR(OFS_PARM0); mins = G_VECTOR(OFS_PARM1); maxs = G_VECTOR(OFS_PARM2); v2 = G_VECTOR(OFS_PARM3); nomonsters = G_FLOAT(OFS_PARM4); ent = G_EDICT(OFS_PARM5); trace = SV_Trace (v1, mins, maxs, v2, nomonsters, ent); PR_GLOBAL(trace_allsolid) = trace.allsolid; PR_GLOBAL(trace_startsolid) = trace.startsolid; PR_GLOBAL(trace_fraction) = trace.fraction; PR_GLOBAL(trace_inwater) = trace.inwater; PR_GLOBAL(trace_inopen) = trace.inopen; VectorCopy (trace.endpos, PR_GLOBAL(trace_endpos)); VectorCopy (trace.plane.normal, PR_GLOBAL(trace_plane_normal)); PR_GLOBAL(trace_plane_dist) = trace.plane.dist; if (trace.e.ent) PR_GLOBAL(trace_ent) = EDICT_TO_PROG(trace.e.ent); else PR_GLOBAL(trace_ent) = EDICT_TO_PROG(sv.edicts); } /* ================= PF_randomvec DP_QC_RANDOMVEC vector randomvec() = #91 ================= */ static void PF_randomvec (void) { vec3_t temp; do { temp[0] = (rand() & 0x7fff) * (2.0 / 0x7fff) - 1.0; temp[1] = (rand() & 0x7fff) * (2.0 / 0x7fff) - 1.0; temp[2] = (rand() & 0x7fff) * (2.0 / 0x7fff) - 1.0; } while (DotProduct(temp, temp) >= 1); VectorCopy (temp, G_VECTOR(OFS_RETURN)); } /* ================= PF_cvar_string QSG_CVARSTRING DP_QC_CVAR_STRING string cvar_string(string varname) = #103; ================= */ static void PF_cvar_string (void) { char *str; cvar_t *var; str = G_STRING(OFS_PARM0); var = Cvar_Find(str); if (!var) { G_INT(OFS_RETURN) = 0; return; } strlcpy (pr_string_temp, var->string, MAX_PR_STRING_SIZE); RETURN_STRING(pr_string_temp); PF_SetTempString(); } // DP_REGISTERCVAR // float(string name, string value) registercvar = #93; // DarkPlaces implementation has an undocumented feature where you can add cvar flags // as a third parameter; I don't see how that can be useful so it's not implemented void PF_registercvar (void) { char *name, *value; name = G_STRING(OFS_PARM0); value = G_STRING(OFS_PARM0); if (Cvar_Find(name)) { G_INT(OFS_RETURN) = 0; return; } Cvar_Create (name, value, 0); G_INT(OFS_RETURN) = 1; } // ZQ_PAUSE // void(float pause) setpause = #531; void PF_setpause (void) { int pause; pause = G_FLOAT(OFS_PARM0) ? 1 : 0; if (pause != (sv.paused & 1)) SV_TogglePause (NULL, 1); } /* ============== PF_checkextension float checkextension(string extension) = #99; ============== */ static void PF_checkextension (void) { static char *supported_extensions[] = { "DP_CON_SET", // http://wiki.quakesrc.org/index.php/DP_CON_SET "DP_QC_CVAR_STRING", // http://wiki.quakesrc.org/index.php/DP_QC_CVAR_STRING "DP_QC_MINMAXBOUND", // http://wiki.quakesrc.org/index.php/DP_QC_MINMAXBOUND "DP_QC_RANDOMVEC", // http://wiki.quakesrc.org/index.php/DP_QC_RANDOMVEC "DP_QC_SINCOSSQRTPOW", // http://wiki.quakesrc.org/index.php/DP_QC_SINCOSSQRTPOW "DP_QC_TRACEBOX", // http://wiki.quakesrc.org/index.php/DP_QC_TRACEBOX "DP_REGISTERCVAR", // http://wiki.quakesrc.org/index.php/DP_REGISTERCVAR "FTE_CALLTIMEOFDAY", // http://wiki.quakesrc.org/index.php/FTE_CALLTIMEOFDAY "QSG_CVARSTRING", // http://wiki.quakesrc.org/index.php/QSG_CVARSTRING "ZQ_CLIENTCOMMAND", // http://wiki.quakesrc.org/index.php/ZQ_CLIENTCOMMAND "ZQ_ITEMS2", // http://wiki.quakesrc.org/index.php/ZQ_ITEMS2 "ZQ_MOVETYPE_NOCLIP", // http://wiki.quakesrc.org/index.php/ZQ_MOVETYPE_NOCLIP "ZQ_MOVETYPE_FLY", // http://wiki.quakesrc.org/index.php/ZQ_MOVETYPE_FLY "ZQ_MOVETYPE_NONE", // http://wiki.quakesrc.org/index.php/ZQ_MOVETYPE_NONE "ZQ_PAUSE", // http://wiki.quakesrc.org/index.php/ZQ_PAUSE "ZQ_QC_STRINGS", // http://wiki.quakesrc.org/index.php/ZQ_QC_STRINGS "ZQ_QC_TOKENIZE", // http://wiki.quakesrc.org/index.php/ZQ_QC_TOKENIZE "ZQ_VWEP", NULL }; char **pstr, *extension; extension = G_STRING(OFS_PARM0); for (pstr = supported_extensions; *pstr; pstr++) { if (!strcasecmp(*pstr, extension)) { G_FLOAT(OFS_RETURN) = 1.0; // supported return; } } G_FLOAT(OFS_RETURN) = 0.0; // not supported } void PF_Fixme (void) { PR_RunError ("unimplemented bulitin"); } static builtin_t std_builtins[] = { PF_Fixme, //#0 PF_makevectors, // void(entity e) makevectors = #1; PF_setorigin, // void(entity e, vector o) setorigin = #2; PF_setmodel, // void(entity e, string m) setmodel = #3; PF_setsize, // void(entity e, vector min, vector max) setsize = #4; PF_Fixme, // void(entity e, vector min, vector max) setabssize = #5; PF_break, // void() break = #6; PF_random, // float() random = #7; PF_sound, // void(entity e, float chan, string samp) sound = #8; PF_normalize, // vector(vector v) normalize = #9; PF_error, // void(string e) error = #10; PF_objerror, // void(string e) objerror = #11; PF_vlen, // float(vector v) vlen = #12; PF_vectoyaw, // float(vector v) vectoyaw = #13; PF_Spawn, // entity() spawn = #14; PF_Remove, // void(entity e) remove = #15; PF_traceline, // float(vector v1, vector v2, float tryents) traceline = #16; PF_checkclient, // entity() clientlist = #17; PF_Find, // entity(entity start, .string fld, string match) find = #18; PF_precache_sound, // void(string s) precache_sound = #19; PF_precache_model, // void(string s) precache_model = #20; PF_stuffcmd, // void(entity client, string s)stuffcmd = #21; PF_findradius, // entity(vector org, float rad) findradius = #22; PF_bprint, // void(string s) bprint = #23; PF_sprint, // void(entity client, string s) sprint = #24; PF_dprint, // void(string s) dprint = #25; PF_ftos, // void(string s) ftos = #26; PF_vtos, // void(string s) vtos = #27; PF_coredump, PF_traceon, PF_traceoff, //#30 PF_eprint, // void(entity e) debug print an entire entity PF_walkmove, // float(float yaw, float dist) walkmove PF_Fixme, // float(float yaw, float dist) walkmove PF_droptofloor, PF_lightstyle, PF_rint, PF_floor, PF_ceil, PF_Fixme, PF_checkbottom, //#40 PF_pointcontents, PF_Fixme, PF_fabs, PF_aim, PF_cvar, PF_localcmd, PF_nextent, PF_particle, PF_changeyaw, PF_Fixme, //#50 PF_vectoangles, PF_WriteByte, PF_WriteChar, PF_WriteShort, PF_WriteLong, PF_WriteCoord, PF_WriteAngle, PF_WriteString, PF_WriteEntity, //#59 PF_Fixme, PF_Fixme, PF_Fixme, PF_Fixme, PF_Fixme, PF_Fixme, PF_Fixme, SV_MoveToGoal, PF_precache_file, PF_makestatic, PF_changelevel, //#70 PF_Fixme, PF_cvar_set, PF_centerprint, PF_ambientsound, PF_precache_model, PF_precache_sound, // precache_sound2 is different only for qcc PF_precache_file, PF_setspawnparms, PF_logfrag, PF_infokey, //#80 PF_stof, PF_multicast, }; #define num_std_builtins (sizeof(std_builtins)/sizeof(std_builtins[0])) static struct { int num; builtin_t func; } ext_builtins[] = { {60, PF_sin}, //float(float f) sin = #60; {61, PF_cos}, //float(float f) cos = #61; {62, PF_sqrt}, //float(float f) sqrt = #62; {84, PF_tokenize}, // float(string s) tokenize {85, PF_argc}, // float() argc {86, PF_argv}, // string(float n) argv {90, PF_tracebox}, // void (vector v1, vector mins, vector maxs, vector v2, float nomonsters, entity ignore) tracebox {91, PF_randomvec}, // vector() randomvec //// {93, PF_registercvar}, // float(string name, string value) registercvar {94, PF_min}, // float(float a, float b, ...) min {95, PF_max}, // float(float a, float b, ...) max {96, PF_bound}, // float(float min, float value, float max) bound {97, PF_pow}, // float(float x, float y) pow //// {99, PF_checkextension},// float(string name) checkextension //// {103, PF_cvar_string}, // string(string varname) cvar_string //// {114, PF_strlen}, // float(string s) strlen {115, PF_strcat}, // string(string s1, string s2, ...) strcat {116, PF_substr}, // string(string s, float start, float count) substr //{117, PF_stov}, // vector(string s) stov {118, PF_strzone}, // string(string s) strzone {119, PF_strunzone}, // void(string s) strunzone {231, PF_calltimeofday},// void() calltimeofday {448, PF_cvar_string}, // string(string varname) cvar_string {531, PF_setpause}, //void(float pause) setpause {532, PF_precache_vwep_model}, // float(string model) precache_vwep_model = #532; }; #define num_ext_builtins (sizeof(ext_builtins)/sizeof(ext_builtins[0])) builtin_t *pr_builtins = NULL; int pr_numbuiltins = 0; void PR_InitBuiltins (void) { int i; if (pr_builtins) return; // We don't need reinit it. // Free old array. Q_free (pr_builtins); // We have at least iD builtins. pr_numbuiltins = num_std_builtins; // Find highest builtin number to see how much space we actually need. for (i = 0; i < num_ext_builtins; i++) pr_numbuiltins = max(ext_builtins[i].num + 1, pr_numbuiltins); // Allocate builtins array. pr_builtins = (builtin_t *) Q_malloc(pr_numbuiltins * sizeof(builtin_t)); // Init new array to PF_Fixme(). for (i = 0; i < pr_numbuiltins; i++) pr_builtins[i] = PF_Fixme; // Copy iD builtins in new array. memcpy (pr_builtins, std_builtins, num_std_builtins * sizeof(builtin_t)); // Add QSG builtins or, probably, overwrite iD ones. for (i = 0; i < num_ext_builtins; i++) { assert (ext_builtins[i].num >= 0); pr_builtins[ext_builtins[i].num] = ext_builtins[i].func; } } #endif // CLIENTONLYmvdsv-0.35/src/pr_comp.h000066400000000000000000000061421427146041000152010ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef __PR_COMP_H__ #define __PR_COMP_H__ // this file is shared by quake and qcc typedef int func_t; typedef int string_t; typedef intptr_t stringptr_t; typedef enum {ev_void, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_pointer} etype_t; #define OFS_NULL 0 #define OFS_RETURN 1 #define OFS_PARM0 4 // leave 3 ofs for each parm to hold vectors #define OFS_PARM1 7 #define OFS_PARM2 10 #define OFS_PARM3 13 #define OFS_PARM4 16 #define OFS_PARM5 19 #define OFS_PARM6 22 #define OFS_PARM7 25 #define RESERVED_OFS 28 enum { OP_DONE, OP_MUL_F, OP_MUL_V, OP_MUL_FV, OP_MUL_VF, OP_DIV_F, OP_ADD_F, OP_ADD_V, OP_SUB_F, OP_SUB_V, OP_EQ_F, OP_EQ_V, OP_EQ_S, OP_EQ_E, OP_EQ_FNC, OP_NE_F, OP_NE_V, OP_NE_S, OP_NE_E, OP_NE_FNC, OP_LE, OP_GE, OP_LT, OP_GT, OP_LOAD_F, OP_LOAD_V, OP_LOAD_S, OP_LOAD_ENT, OP_LOAD_FLD, OP_LOAD_FNC, OP_ADDRESS, OP_STORE_F, OP_STORE_V, OP_STORE_S, OP_STORE_ENT, OP_STORE_FLD, OP_STORE_FNC, OP_STOREP_F, OP_STOREP_V, OP_STOREP_S, OP_STOREP_ENT, OP_STOREP_FLD, OP_STOREP_FNC, OP_RETURN, OP_NOT_F, OP_NOT_V, OP_NOT_S, OP_NOT_ENT, OP_NOT_FNC, OP_IF, OP_IFNOT, OP_CALL0, OP_CALL1, OP_CALL2, OP_CALL3, OP_CALL4, OP_CALL5, OP_CALL6, OP_CALL7, OP_CALL8, OP_STATE, OP_GOTO, OP_AND, OP_OR, OP_BITAND, OP_BITOR }; typedef struct statement_s { unsigned short op; short a,b,c; } dstatement_t; typedef struct { unsigned short type; // if DEF_SAVEGLOBGAL bit is set // the variable needs to be saved in savegames unsigned short ofs; int s_name; } ddef_t; #define DEF_SAVEGLOBAL (1<<15) #define MAX_PARMS 8 typedef struct { int first_statement; // negative numbers are builtins int parm_start; int locals; // total ints of parms + locals int profile; // runtime int s_name; int s_file; // source file defined in int numparms; byte parm_size[MAX_PARMS]; } dfunction_t; #define PROG_VERSION 6 typedef struct { int version; int crc; // check of header file int ofs_statements; int numstatements; // statement 0 is an error int ofs_globaldefs; int numglobaldefs; int ofs_fielddefs; int numfielddefs; int ofs_functions; int numfunctions; // function 0 is an empty int ofs_strings; int numstrings; // first string is a null string int ofs_globals; int numglobals; int entityfields; } dprograms_t; #endif /* !__PR_COMP_H__ */ mvdsv-0.35/src/pr_edict.c000066400000000000000000000631511427146041000153310ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // sv_edict.c -- entity dictionary #ifndef CLIENTONLY #include "qwsvdef.h" dprograms_t *progs; dfunction_t *pr_functions; char *pr_strings; ddef_t *pr_fielddefs; ddef_t *pr_globaldefs; dstatement_t *pr_statements; globalvars_t *pr_global_struct; float *pr_globals; // same as pr_global_struct int pr_edict_size; // in bytes #define NQ_PROGHEADER_CRC 5927 #ifdef WITH_NQPROGS qbool pr_nqprogs; int pr_fieldoffsetpatch[106]; int pr_globaloffsetpatch[62]; static int pr_globaloffsetpatch_nq[62] = {0,0,0,0,0,666,-4,-4,8,8, 8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8, 8,8}; #endif static int type_size[8] = { 1, // void 1, // string_t 1, // float 3, // vector 1, // entity 1, // field 1, // func_t 1 // pointer (its an int index) }; ddef_t *ED_FieldAtOfs (int ofs); qbool ED_ParseEpair (void *base, ddef_t *key, char *s); #define MAX_FIELD_LEN 64 #define GEFV_CACHESIZE 2 typedef struct { ddef_t *pcache; char field[MAX_FIELD_LEN]; } gefv_cache; static gefv_cache gefvCache[GEFV_CACHESIZE] = {{NULL, ""}, {NULL, ""}}; func_t mod_SpectatorConnect, mod_SpectatorThink, mod_SpectatorDisconnect; func_t GE_ClientCommand, GE_PausedTic, GE_ShouldPause; func_t mod_ConsoleCmd, mod_UserCmd; func_t mod_UserInfo_Changed, mod_localinfoChanged; func_t mod_ChatMessage; cvar_t sv_progsname = {"sv_progsname", "qwprogs"}; #ifdef WITH_NQPROGS cvar_t sv_forcenqprogs = {"sv_forcenqprogs", "0"}; #endif /* ================= ED_ClearEdict Sets everything to NULL ================= */ void ED_ClearEdict (edict_t *e) { memset(e->v, 0, pr_edict_size); e->e.lastruntime = 0; e->e.free = false; PR_ClearEdict(e); } /* ================= ED_Alloc Either finds a free edict, or allocates a new one. Try to avoid reusing an entity that was recently freed, because it can cause the client to think the entity morphed into something else instead of being removed and recreated, which can cause interpolated angles and bad trails. ================= */ edict_t *ED_Alloc (void) { int i; edict_t *e; for (i = MAX_CLIENTS + 1; i < sv.num_edicts; i++) { e = EDICT_NUM(i); // the first couple seconds of server time can involve a lot of // freeing and allocating, so relax the replacement policy if (e->e.free && (e->e.freetime < 2 || sv.time - e->e.freetime > 0.5)) { ED_ClearEdict(e); return e; } } if (i == sv.max_edicts) { Con_Printf ("WARNING: ED_Alloc: no free edicts [%d]\n", sv.max_edicts); i--; // step on whatever is the last edict e = EDICT_NUM(i); SV_UnlinkEdict(e); } else { sv.num_edicts++; e = EDICT_NUM(i); } ED_ClearEdict(e); return e; } /* ================= ED_Free Marks the edict as free FIXME: walk all entities and NULL out references to this entity ================= */ void ED_Free (edict_t *ed) { SV_UnlinkEdict (ed); // unlink from world bsp ed->e.free = true; ed->v->model = 0; ed->v->takedamage = 0; ed->v->modelindex = 0; ed->v->colormap = 0; ed->v->skin = 0; ed->v->frame = 0; ed->v->health = 0; ed->v->classname = 0; VectorClear (ed->v->origin); VectorClear (ed->v->angles); ed->v->nextthink = -1; ed->v->solid = 0; ed->e.freetime = sv.time; } //=========================================================================== /* ============ ED_GlobalAtOfs ============ */ ddef_t *ED_GlobalAtOfs (int ofs) { ddef_t *def; int i; for (i=0 ; inumglobaldefs ; i++) { def = &pr_globaldefs[i]; if (def->ofs == ofs) return def; } return NULL; } /* ============ ED_FieldAtOfs ============ */ ddef_t *ED_FieldAtOfs (int ofs) { ddef_t *def; int i; for (i=0 ; inumfielddefs ; i++) { def = &pr_fielddefs[i]; if (def->ofs == ofs) return def; } return NULL; } /* ============ ED_FindField ============ */ ddef_t *ED_FindField (char *name) { ddef_t *def; int i; for (i=0 ; inumfielddefs ; i++) { def = &pr_fielddefs[i]; if (!strcmp(PR1_GetString(def->s_name),name) ) return def; } return NULL; } /* ============ ED_FindGlobal ============ */ ddef_t *ED_FindGlobal (char *name) { ddef_t *def; int i; for (i=0 ; inumglobaldefs ; i++) { def = &pr_globaldefs[i]; if (!strcmp(PR1_GetString(def->s_name),name) ) return def; } return NULL; } /* ============ ED1_FindFieldOffset ============ */ int ED1_FindFieldOffset (char *field) { ddef_t *d; d = ED_FindField(field); if (!d) return 0; return d->ofs*4; } /* ============ ED_FindFunction ============ */ dfunction_t *ED_FindFunction (char *name) { register dfunction_t *func; register int i; if (!progs) return NULL; for (i=0 ; inumfunctions ; i++) { func = &pr_functions[i]; if (!strcmp(PR1_GetString(func->s_name), name)) return func; } return NULL; } func_t ED_FindFunctionOffset (char *name) { dfunction_t *func; func = ED_FindFunction (name); return func ? (func_t)(func - pr_functions) : 0; } eval_t *PR1_GetEdictFieldValue(edict_t *ed, char *field) { ddef_t *def = NULL; int i; static int rep = 0; for (i=0 ; iv + def->ofs*4); } /* ============ PR_ValueString Returns a string describing *data in a type specific manner ============= */ char *PR_ValueString (etype_t type, eval_t *val) { static char line[256]; ddef_t *def; dfunction_t *f; type = (etype_t) (type & ~DEF_SAVEGLOBAL); switch (type) { case ev_string: snprintf (line, sizeof(line), "%s", PR1_GetString(val->string)); break; case ev_entity: snprintf (line, sizeof(line), "entity %i", NUM_FOR_EDICT(PROG_TO_EDICT(val->edict)) ); break; case ev_function: f = pr_functions + val->function; snprintf (line, sizeof(line), "%s()", PR1_GetString(f->s_name)); break; case ev_field: def = ED_FieldAtOfs ( val->_int ); snprintf (line, sizeof(line), ".%s", PR1_GetString(def->s_name)); break; case ev_void: snprintf (line, sizeof(line), "void"); break; case ev_float: snprintf (line, sizeof(line), "%5.1f", val->_float); break; case ev_vector: snprintf (line, sizeof(line), "'%5.1f %5.1f %5.1f'", val->vector[0], val->vector[1], val->vector[2]); break; case ev_pointer: snprintf (line, sizeof(line), "pointer"); break; default: snprintf (line, sizeof(line), "bad type %i", type); break; } return line; } /* ============ PR_UglyValueString Returns a string describing *data in a type specific manner Easier to parse than PR_ValueString ============= */ char *PR_UglyValueString (etype_t type, eval_t *val) { static char line[256]; ddef_t *def; dfunction_t *f; type = (etype_t) (type & ~DEF_SAVEGLOBAL); switch (type) { case ev_string: snprintf (line, sizeof(line), "%s", PR1_GetString(val->string)); break; case ev_entity: snprintf (line, sizeof(line), "%i", NUM_FOR_EDICT(PROG_TO_EDICT(val->edict))); break; case ev_function: f = pr_functions + val->function; snprintf (line, sizeof(line), "%s", PR1_GetString(f->s_name)); break; case ev_field: def = ED_FieldAtOfs ( val->_int ); snprintf (line, sizeof(line), "%s", PR1_GetString(def->s_name)); break; case ev_void: snprintf (line, sizeof(line), "void"); break; case ev_float: snprintf (line, sizeof(line), "%f", val->_float); break; case ev_vector: snprintf (line, sizeof(line), "%f %f %f", val->vector[0], val->vector[1], val->vector[2]); break; default: snprintf (line, sizeof(line), "bad type %i", type); break; } return line; } /* ============ PR_GlobalString Returns a string with a description and the contents of a global, padded to 20 field width ============ */ char *PR_GlobalString (int ofs) { char *s; int i; ddef_t *def; void *val; static char line[128]; val = (void *)&pr_globals[ofs]; def = ED_GlobalAtOfs(ofs); if (!def) { snprintf (line, sizeof(line), "%i(?""?""?)", ofs); // separate the ?'s to shut up gcc } else { s = PR_ValueString ((etype_t)def->type, (eval_t *) val); snprintf (line, sizeof(line), "%i(%s)%s", ofs, PR1_GetString(def->s_name), s); } i = strlen(line); for ( ; i<20 ; i++) strlcat (line, " ", sizeof(line)); strlcat (line, " ", sizeof(line)); return line; } char *PR_GlobalStringNoContents (int ofs) { int i; ddef_t *def; static char line[128]; def = ED_GlobalAtOfs(ofs); if (!def) snprintf (line, sizeof(line), "%i(?""?""?)", ofs); // separate the ?'s to shut up gcc else snprintf (line, sizeof(line), "%i(%s)", ofs, PR1_GetString(def->s_name)); i = strlen(line); for ( ; i<20 ; i++) strlcat (line, " ", sizeof(line)); strlcat (line, " ", sizeof(line)); return line; } /* ============= ED_Print For debugging ============= */ void ED_Print (edict_t *ed) { int l; ddef_t *d; int *v; int i, j; char *name; int type; if (ed->e.free) { Con_Printf ("FREE\n"); return; } for (i=1 ; inumfielddefs ; i++) { d = &pr_fielddefs[i]; name = PR1_GetString(d->s_name); if (name[strlen(name)-2] == '_') continue; // skip _x, _y, _z vars v = (int *)((char *)ed->v + d->ofs*4); // if the value is still all 0, skip the field type = d->type & ~DEF_SAVEGLOBAL; for (j = 0; j < type_size[type]; j++) { if (v[j]) { break; } } if (j == type_size[type]) { continue; } Con_Printf ("%s",name); l = strlen (name); while (l++ < 15) { Con_Printf(" "); } Con_Printf ("%s\n", PR_ValueString((etype_t)d->type, (eval_t *)v)); } } /* ============= ED_Write For savegames ============= */ void ED_Write (FILE *f, edict_t *ed) { ddef_t *d; int *v; int i, j; char *name; int type; fprintf (f, "{\n"); if (ed->e.free) { fprintf (f, "}\n"); return; } for (i=1 ; inumfielddefs ; i++) { d = &pr_fielddefs[i]; name = PR1_GetString(d->s_name); if (name[strlen(name)-2] == '_') continue; // skip _x, _y, _z vars v = (int *)((char *)ed->v + d->ofs*4); // if the value is still all 0, skip the field type = d->type & ~DEF_SAVEGLOBAL; for (j=0 ; jtype, (eval_t *)v)); } fprintf (f, "}\n"); } void ED_PrintNum (int ent) { ED_Print (EDICT_NUM(ent)); } /* ============= ED_PrintEdicts For debugging, prints all the entities in the current server ============= */ void ED_PrintEdicts (void) { int i; Con_Printf ("%i entities\n", sv.num_edicts); for (i=0 ; i= sv.num_edicts) { Con_Printf ("\nNo such edict: %i\n", i); return; } Con_Printf ("\n EDICT %i:\n",i); ED_PrintNum (i); } /* ============= ED_Count For debugging ============= */ void ED_Count (void) { int i; edict_t *ent; int active, models, solid, step; active = models = solid = step = 0; for (i=0 ; ie.free) continue; active++; if (ent->v->solid) solid++; if (ent->v->model) models++; if (ent->v->movetype == MOVETYPE_STEP) step++; } Con_Printf ("num_edicts:%3i\n", sv.num_edicts); Con_Printf ("active :%3i\n", active); Con_Printf ("view :%3i\n", models); Con_Printf ("touch :%3i\n", solid); Con_Printf ("step :%3i\n", step); } /* ============================================================================== ARCHIVING GLOBALS FIXME: need to tag constants, doesn't really work ============================================================================== */ /* ============= ED_WriteGlobals ============= */ void ED_WriteGlobals (FILE *f) { ddef_t *def; int i; char *name; int type; fprintf (f,"{\n"); for (i=0 ; inumglobaldefs ; i++) { def = &pr_globaldefs[i]; type = def->type; if ( !(def->type & DEF_SAVEGLOBAL) ) continue; type &= ~DEF_SAVEGLOBAL; if (type != ev_string && type != ev_float && type != ev_entity) continue; name = PR1_GetString(def->s_name); fprintf (f,"\"%s\" ", name); fprintf (f,"\"%s\"\n", PR_UglyValueString((etype_t)type, (eval_t *)&pr_globals[def->ofs])); } fprintf (f,"}\n"); } /* ============= ED_ParseGlobals ============= */ void ED_ParseGlobals (const char *data) { char keyname[64]; ddef_t *key; while (1) { // parse key data = COM_Parse (data); if (com_token[0] == '}') break; if (!data) SV_Error ("ED_ParseEntity: EOF without closing brace"); strlcpy (keyname, com_token, sizeof(keyname)); // parse value data = COM_Parse (data); if (!data) SV_Error ("ED_ParseEntity: EOF without closing brace"); if (com_token[0] == '}') SV_Error ("ED_ParseEntity: closing brace without data"); key = ED_FindGlobal (keyname); if (!key) { Con_Printf ("%s is not a global\n", keyname); continue; } if (!ED_ParseEpair ((void *)pr_globals, key, com_token)) SV_Error ("ED_ParseGlobals: parse error"); } } //============================================================================ /* ============= ED_NewString ============= */ char *ED_NewString (char *string) { char *nuw, *new_p; int i,l; l = strlen(string) + 1; nuw = (char *) Hunk_Alloc (l); new_p = nuw; for (i=0 ; i< l ; i++) { if (string[i] == '\\' && i < l-1) { i++; if (string[i] == 'n') *new_p++ = '\n'; else *new_p++ = '\\'; } else *new_p++ = string[i]; } return nuw; } /* ============= ED_ParseEval Can parse either fields or globals returns false if error ============= */ qbool ED_ParseEpair (void *base, ddef_t *key, char *s) { int i; char string[128]; ddef_t *def; char *v, *w; void *d; dfunction_t *func; d = (void *)((int *)base + key->ofs); switch (key->type & ~DEF_SAVEGLOBAL) { case ev_string: PR1_SetString((string_t *)d, ED_NewString (s)); break; case ev_float: *(float *)d = Q_atof (s); break; case ev_vector: strlcpy (string, s, sizeof(string)); v = string; w = string; for (i=0 ; i<3 ; i++) { while (*v && *v != ' ') v++; *v = 0; ((float *)d)[i] = Q_atof (w); w = v = v+1; } break; case ev_entity: *(int *)d = EDICT_TO_PROG(EDICT_NUM(Q_atoi (s))); break; case ev_field: def = ED_FindField (s); if (!def) { Con_Printf ("Can't find field %s\n", s); return false; } *(int *)d = G_INT(def->ofs); break; case ev_function: func = ED_FindFunction (s); if (!func) { Con_Printf ("Can't find function %s\n", s); return false; } *(func_t *)d = func - pr_functions; break; default: break; } return true; } /* ==================== ED_ParseEdict Parses an edict out of the given string, returning the new position ed should be a properly initialized empty edict. Used for initial level load and for savegames. ==================== */ const char *ED_ParseEdict (const char *data, edict_t *ent) { ddef_t *key; qbool anglehack; qbool init; char keyname[256]; init = false; // go through all the dictionary pairs while (1) { // parse key data = COM_Parse (data); if (com_token[0] == '}') break; if (!data) SV_Error ("ED_ParseEntity: EOF without closing brace"); // anglehack is to allow QuakeEd to write single scalar angles // and allow them to be turned into vectors. (FIXME...) if (!strcmp(com_token, "angle")) { strlcpy (com_token, "angles", MAX_COM_TOKEN); anglehack = true; } else anglehack = false; // FIXME: change light to _light to get rid of this hack if (!strcmp(com_token, "light")) strlcpy (com_token, "light_lev", MAX_COM_TOKEN); // hack for single light def strlcpy (keyname, com_token, sizeof(keyname)); // parse value data = COM_Parse (data); if (!data) SV_Error ("ED_ParseEntity: EOF without closing brace"); if (com_token[0] == '}') SV_Error ("ED_ParseEntity: closing brace without data"); init = true; // keynames with a leading underscore are used for utility comments, // and are immediately discarded by quake if (keyname[0] == '_') continue; key = ED_FindField (keyname); if (!key) { Con_Printf ("%s is not a field\n", keyname); continue; } if (anglehack) { char temp[32]; strlcpy (temp, com_token, sizeof(temp)); snprintf (com_token, MAX_COM_TOKEN, "0 %s 0", temp); } if (!ED_ParseEpair ((void *)ent->v, key, com_token)) SV_Error ("ED_ParseEdict: parse error"); } if (!init) ent->e.free = true; return data; } /* ================ ED_LoadFromFile The entities are directly placed in the array, rather than allocated with ED_Alloc, because otherwise an error loading the map would have entity number references out of order. Creates a server's entity / program execution context by parsing textual entity definitions out of an ent file. Used for both fresh maps and savegame loads. A fresh map would also need to call ED_CallSpawnFunctions () to let the objects initialize themselves. ================ */ void ED_LoadFromFile (const char *data) { edict_t *ent; int inhibit; dfunction_t *func; ent = NULL; inhibit = 0; pr_global_struct->time = sv.time; // parse ents while (1) { // parse the opening brace data = COM_Parse (data); if (!data) break; if (com_token[0] != '{') SV_Error ("ED_LoadFromFile: found %s when expecting {",com_token); if (!ent) ent = EDICT_NUM(0); else ent = ED_Alloc (); data = ED_ParseEdict (data, ent); // remove things from different skill levels or deathmatch if ((int)deathmatch.value) { if (((int)ent->v->spawnflags & SPAWNFLAG_NOT_DEATHMATCH)) { ED_Free (ent); inhibit++; continue; } } else if ((current_skill == 0 && ((int)ent->v->spawnflags & SPAWNFLAG_NOT_EASY)) || (current_skill == 1 && ((int)ent->v->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || (current_skill >= 2 && ((int)ent->v->spawnflags & SPAWNFLAG_NOT_HARD)) ) { ED_Free (ent); inhibit++; continue; } // // immediately call spawn function // if (!ent->v->classname) { Con_Printf ("No classname for:\n"); ED_Print (ent); ED_Free (ent); continue; } // look for the spawn function func = ED_FindFunction ( PR1_GetString(ent->v->classname) ); if (!func) { Con_Printf ("No spawn function for:\n"); ED_Print (ent); ED_Free (ent); continue; } pr_global_struct->self = EDICT_TO_PROG(ent); PR_ExecuteProgram (func - pr_functions); SV_FlushSignon(); } Con_DPrintf ("%i entities inhibited\n", inhibit); } extern redirect_t sv_redirected; qbool PR_ConsoleCmd(void) { if (mod_ConsoleCmd) { if (sv_redirected != RD_MOD) { pr_global_struct->time = sv.time; pr_global_struct->self = 0; } PR_ExecuteProgram (mod_ConsoleCmd); return (int) G_FLOAT(OFS_RETURN); } return false; } qbool PR1_ClientCmd(void) { // ZQ_CLIENTCOMMAND extension if (GE_ClientCommand) { static char cmd_copy[128], args_copy[1024] /* Ouch! */; strlcpy (cmd_copy, Cmd_Argv(0), sizeof(cmd_copy)); strlcpy (args_copy, Cmd_Args(), sizeof(args_copy)); PR1_SetString (&((int *)pr_globals)[OFS_PARM0], cmd_copy); PR1_SetString (&((int *)pr_globals)[OFS_PARM1], args_copy); PR_ExecuteProgram (GE_ClientCommand); return G_FLOAT(OFS_RETURN) ? true : false; } if (mod_UserCmd) { static char cmd_copy[128]; strlcpy (cmd_copy, Cmd_Argv(0), sizeof(cmd_copy)); PR1_SetString (&((int *)pr_globals)[OFS_PARM0], cmd_copy); PR_ExecuteProgram (mod_UserCmd); return G_FLOAT(OFS_RETURN) ? true : false; } return false; } /* =============== PR1_LoadProgs =============== */ void PF_clear_strtbl(void); #ifdef WITH_NQPROGS void PR_InitPatchTables (void) { int i; if (pr_nqprogs) { memcpy (pr_globaloffsetpatch, pr_globaloffsetpatch_nq, sizeof(pr_globaloffsetpatch)); for (i = 0; i < 106; i++) { pr_fieldoffsetpatch[i] = (i < 8) ? i : (i < 25) ? i + 1 : (i < 28) ? i + (102 - 25) : (i < 73) ? i - 2 : (i < 74) ? i + (105 - 73) : (i < 105) ? i - 3 : /* (i == 105) */ 8; } for (i=0 ; inumfielddefs ; i++) pr_fielddefs[i].ofs = PR_FIELDOFS(pr_fielddefs[i].ofs); } else { memset (pr_globaloffsetpatch, 0, sizeof(pr_globaloffsetpatch)); for (i = 0; i < 106; i++) pr_fieldoffsetpatch[i] = i; } } #endif void PR1_LoadProgs (void) { int i; char num[32]; int filesize; // flush the non-C variable lookup cache for (i = 0; i < GEFV_CACHESIZE; i++) gefvCache[i].field[0] = 0; // clear pr_newstrtbl PF_clear_strtbl(); progs = NULL; #ifdef WITH_NQPROGS pr_nqprogs = false; // forced load of NQ progs. if (!progs && Cvar_Value("sv_forcenqprogs") && (progs = (dprograms_t *)FS_LoadHunkFile ("progs.dat", &filesize))) pr_nqprogs = true; #endif if (!progs) { char name[MAX_OSPATH]; snprintf(name, sizeof(name), "%s.dat", sv_progsname.string); progs = (dprograms_t *)FS_LoadHunkFile (name, &filesize); } if (!progs) progs = (dprograms_t *)FS_LoadHunkFile ("qwprogs.dat", &filesize); if (!progs) progs = (dprograms_t *)FS_LoadHunkFile ("spprogs.dat", &filesize); #ifdef WITH_NQPROGS // low priority load of NQ progs. if (!progs && (progs = (dprograms_t *)FS_LoadHunkFile ("progs.dat", &filesize))) pr_nqprogs = true; #endif if (!progs) SV_Error ("PR1_LoadProgs: couldn't load progs.dat"); Con_DPrintf ("Programs occupy %iK.\n", filesize/1024); #ifdef WITH_NQPROGS if (pr_nqprogs) Con_DPrintf ("NQ progs.\n"); #endif // add prog crc to the serverinfo snprintf (num, sizeof(num), "%i", CRC_Block ((byte *)progs, filesize)); Info_SetValueForStarKey (svs.info, "*progs", num, MAX_SERVERINFO_STRING); // byte swap the header for (i = 0; i < (int) sizeof(*progs) / 4 ; i++) ((int *)progs)[i] = LittleLong ( ((int *)progs)[i] ); if (progs->version != PROG_VERSION) SV_Error ("qwprogs.dat has wrong version number (%i should be %i)", progs->version, PROG_VERSION); if (progs->crc != (pr_nqprogs ? NQ_PROGHEADER_CRC : PROGHEADER_CRC)) SV_Error ("You must have the qwprogs.dat from QuakeWorld installed"); pr_functions = (dfunction_t *)((byte *)progs + progs->ofs_functions); pr_strings = (char *)progs + progs->ofs_strings; pr_globaldefs = (ddef_t *)((byte *)progs + progs->ofs_globaldefs); pr_fielddefs = (ddef_t *)((byte *)progs + progs->ofs_fielddefs); pr_statements = (dstatement_t *)((byte *)progs + progs->ofs_statements); num_prstr = 0; pr_global_struct = (globalvars_t *)((byte *)progs + progs->ofs_globals); pr_globals = (float *)pr_global_struct; pr_edict_size = progs->entityfields * 4; // byte swap the lumps for (i = 0; i < progs->numstatements; i++) { pr_statements[i].op = LittleShort(pr_statements[i].op); pr_statements[i].a = LittleShort(pr_statements[i].a); pr_statements[i].b = LittleShort(pr_statements[i].b); pr_statements[i].c = LittleShort(pr_statements[i].c); } for (i = 0; i < progs->numfunctions; i++) { pr_functions[i].first_statement = LittleLong (pr_functions[i].first_statement); pr_functions[i].parm_start = LittleLong (pr_functions[i].parm_start); pr_functions[i].s_name = LittleLong (pr_functions[i].s_name); pr_functions[i].s_file = LittleLong (pr_functions[i].s_file); pr_functions[i].numparms = LittleLong (pr_functions[i].numparms); pr_functions[i].locals = LittleLong (pr_functions[i].locals); } for (i = 0; i < progs->numglobaldefs; i++) { pr_globaldefs[i].type = LittleShort (pr_globaldefs[i].type); pr_globaldefs[i].ofs = LittleShort (pr_globaldefs[i].ofs); pr_globaldefs[i].s_name = LittleLong (pr_globaldefs[i].s_name); } for (i = 0; i < progs->numfielddefs; i++) { pr_fielddefs[i].type = LittleShort (pr_fielddefs[i].type); if (pr_fielddefs[i].type & DEF_SAVEGLOBAL) SV_Error ("PR1_LoadProgs: pr_fielddefs[i].type & DEF_SAVEGLOBAL"); pr_fielddefs[i].ofs = LittleShort (pr_fielddefs[i].ofs); pr_fielddefs[i].s_name = LittleLong (pr_fielddefs[i].s_name); } for (i = 0; i < progs->numglobals; i++) ((int *)pr_globals)[i] = LittleLong (((int *)pr_globals)[i]); PR_InitBuiltins(); } void PR1_InitProg(void) { sv.game_edicts = (entvars_t*) Hunk_AllocName (MAX_EDICTS * pr_edict_size, "edicts"); sv.max_edicts = MAX_EDICTS; } /* =============== PR1_Init =============== */ void PR1_Init (void) { Cvar_Register(&sv_progsname); #ifdef WITH_NQPROGS Cvar_Register(&sv_forcenqprogs); #endif Cmd_AddCommand ("edict", ED_PrintEdict_f); Cmd_AddCommand ("edicts", ED_PrintEdicts); Cmd_AddCommand ("edictcount", ED_Count); Cmd_AddCommand ("profile", PR_Profile_f); memset(pr_newstrtbl, 0, sizeof(pr_newstrtbl)); } edict_t *EDICT_NUM(int n) { if (n < 0 || n >= sv.max_edicts) SV_Error ("EDICT_NUM: bad number %i", n); return &sv.edicts[n]; } int NUM_FOR_EDICT(edict_t *e) { int b; b = e->e.entnum; if (b < 0 || b >= sv.num_edicts) SV_Error ("NUM_FOR_EDICT: bad pointer"); return b; } #endif // !CLIENTONLY mvdsv-0.35/src/pr_exec.c000066400000000000000000000413541427146041000151660ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef CLIENTONLY #include "qwsvdef.h" #include typedef struct prstack_s { int s; dfunction_t *f; } prstack_t; #define MAX_STACK_DEPTH 32 prstack_t pr_stack[MAX_STACK_DEPTH]; int pr_depth; #define LOCALSTACK_SIZE 2048 int localstack[LOCALSTACK_SIZE]; int localstack_used; qbool pr_trace; dfunction_t *pr_xfunction; int pr_xstatement; int pr_argc; char *pr_opnames[] = { "DONE", "MUL_F", "MUL_V", "MUL_FV", "MUL_VF", "DIV", "ADD_F", "ADD_V", "SUB_F", "SUB_V", "EQ_F", "EQ_V", "EQ_S", "EQ_E", "EQ_FNC", "NE_F", "NE_V", "NE_S", "NE_E", "NE_FNC", "LE", "GE", "LT", "GT", "INDIRECT", "INDIRECT", "INDIRECT", "INDIRECT", "INDIRECT", "INDIRECT", "ADDRESS", "STORE_F", "STORE_V", "STORE_S", "STORE_ENT", "STORE_FLD", "STORE_FNC", "STOREP_F", "STOREP_V", "STOREP_S", "STOREP_ENT", "STOREP_FLD", "STOREP_FNC", "RETURN", "NOT_F", "NOT_V", "NOT_S", "NOT_ENT", "NOT_FNC", "IF", "IFNOT", "CALL0", "CALL1", "CALL2", "CALL3", "CALL4", "CALL5", "CALL6", "CALL7", "CALL8", "STATE", "GOTO", "AND", "OR", "BITAND", "BITOR" }; char *PR_GlobalString (int ofs); char *PR_GlobalStringNoContents (int ofs); //============================================================================= /* ================= PR_PrintStatement ================= */ void PR_PrintStatement (dstatement_t *s) { int i; if ( (unsigned)s->op < sizeof(pr_opnames)/sizeof(pr_opnames[0])) { Con_Printf ("%s ", pr_opnames[s->op]); i = strlen(pr_opnames[s->op]); for ( ; i<10 ; i++) Con_Printf (" "); } if (s->op == OP_IF || s->op == OP_IFNOT) Con_Printf ("%sbranch %i",PR_GlobalString(s->a),s->b); else if (s->op == OP_GOTO) { Con_Printf ("branch %i",s->a); } else if ( (unsigned)(s->op - OP_STORE_F) < 6) { Con_Printf ("%s",PR_GlobalString(s->a)); Con_Printf ("%s", PR_GlobalStringNoContents(s->b)); } else { if (s->a) Con_Printf ("%s",PR_GlobalString(s->a)); if (s->b) Con_Printf ("%s",PR_GlobalString(s->b)); if (s->c) Con_Printf ("%s", PR_GlobalStringNoContents(s->c)); } Con_Printf ("\n"); } /* ============ PR_StackTrace ============ */ void PR_StackTrace (void) { dfunction_t *f; int i; if (pr_depth == 0) { Con_Printf ("\n"); return; } pr_stack[pr_depth].f = pr_xfunction; for (i=pr_depth ; i>=0 ; i--) { f = pr_stack[i].f; if (!f) Con_Printf ("\n"); else Con_Printf ("%12s : %s\n", PR1_GetString(f->s_file), PR1_GetString(f->s_name)); } } /* ============ PR_Profile_f ============ */ void PR_Profile_f (void) { dfunction_t *f, *best; int max; int num; int i; if (sv.state != ss_active) return; num = 0; do { max = 0; best = NULL; for (i=0 ; inumfunctions ; i++) { f = &pr_functions[i]; if (f->profile > max) { max = f->profile; best = f; } } if (best) { if (num < 10) Con_Printf ("%7i %s\n", best->profile, PR1_GetString(best->s_name)); num++; best->profile = 0; } } while (best); } /* ============ PR_RunError Aborts the currently executing function ============ */ void PR_RunError (char *error, ...) { va_list argptr; char string[1024]; va_start (argptr,error); vsnprintf (string, sizeof(string), error, argptr); va_end (argptr); sv_error = true; PR_PrintStatement (pr_statements + pr_xstatement); PR_StackTrace (); Con_Printf ("%s\n", string); pr_depth = 0; // dump the stack so SV_Error can shutdown functions SV_Error ("Program error"); } /* ==================== PR_EnterFunction Returns the new program statement counter ==================== */ int PR_EnterFunction (dfunction_t *f) { int i, j, c, o; pr_stack[pr_depth].s = pr_xstatement; pr_stack[pr_depth].f = pr_xfunction; pr_depth++; if (pr_depth >= MAX_STACK_DEPTH) PR_RunError ("stack overflow"); // save off any locals that the new function steps on c = f->locals; if (localstack_used + c > LOCALSTACK_SIZE) PR_RunError ("PR_ExecuteProgram: locals stack overflow\n"); for (i=0 ; i < c ; i++) localstack[localstack_used+i] = ((int *)pr_globals)[f->parm_start + i]; localstack_used += c; // copy parameters o = f->parm_start; for (i=0 ; inumparms ; i++) { for (j=0 ; jparm_size[i] ; j++) { ((int *)pr_globals)[o] = ((int *)pr_globals)[OFS_PARM0+i*3+j]; o++; } } pr_xfunction = f; return f->first_statement - 1; // offset the s++ } /* ==================== PR_LeaveFunction ==================== */ int PR_LeaveFunction (void) { int i, c; if (pr_depth <= 0) SV_Error ("prog stack underflow"); // restore locals from the stack c = pr_xfunction->locals; localstack_used -= c; if (localstack_used < 0) PR_RunError ("PR_ExecuteProgram: locals stack underflow\n"); for (i=0 ; i < c ; i++) ((int *)pr_globals)[pr_xfunction->parm_start + i] = localstack[localstack_used+i]; // up stack pr_depth--; pr_xfunction = pr_stack[pr_depth].f; return pr_stack[pr_depth].s; } /* ============================================================================ PR_ExecuteProgram The interpretation main loop ============================================================================ */ void PR_ExecuteProgram (func_t fnum) { eval_t *a = NULL, *b = NULL, *c = NULL; int s; dstatement_t *st = NULL; dfunction_t *f, *newf; int runaway; int i; edict_t *ed; int exitdepth; eval_t *ptr; if (!fnum || fnum >= progs->numfunctions) { if (pr_global_struct->self) ED_Print (PROG_TO_EDICT(pr_global_struct->self)); SV_Error ("PR_ExecuteProgram: NULL function"); } f = &pr_functions[fnum]; runaway = 100000; pr_trace = false; // make a stack frame exitdepth = pr_depth; s = PR_EnterFunction (f); while (1) { s++; // next statement st = &pr_statements[s]; a = (eval_t *)&pr_globals[st->a]; b = (eval_t *)&pr_globals[st->b]; c = (eval_t *)&pr_globals[st->c]; if (--runaway == 0) PR_RunError ("runaway loop error"); pr_xfunction->profile++; pr_xstatement = s; if (pr_trace) PR_PrintStatement (st); switch (st->op) { case OP_ADD_F: c->_float = a->_float + b->_float; break; case OP_ADD_V: c->vector[0] = a->vector[0] + b->vector[0]; c->vector[1] = a->vector[1] + b->vector[1]; c->vector[2] = a->vector[2] + b->vector[2]; break; case OP_SUB_F: c->_float = a->_float - b->_float; break; case OP_SUB_V: c->vector[0] = a->vector[0] - b->vector[0]; c->vector[1] = a->vector[1] - b->vector[1]; c->vector[2] = a->vector[2] - b->vector[2]; break; case OP_MUL_F: c->_float = a->_float * b->_float; break; case OP_MUL_V: c->_float = a->vector[0]*b->vector[0] + a->vector[1]*b->vector[1] + a->vector[2]*b->vector[2]; break; case OP_MUL_FV: c->vector[0] = a->_float * b->vector[0]; c->vector[1] = a->_float * b->vector[1]; c->vector[2] = a->_float * b->vector[2]; break; case OP_MUL_VF: c->vector[0] = b->_float * a->vector[0]; c->vector[1] = b->_float * a->vector[1]; c->vector[2] = b->_float * a->vector[2]; break; case OP_DIV_F: c->_float = a->_float / b->_float; break; case OP_BITAND: c->_float = (int)a->_float & (int)b->_float; break; case OP_BITOR: c->_float = (int)a->_float | (int)b->_float; break; case OP_GE: c->_float = a->_float >= b->_float; break; case OP_LE: c->_float = a->_float <= b->_float; break; case OP_GT: c->_float = a->_float > b->_float; break; case OP_LT: c->_float = a->_float < b->_float; break; case OP_AND: c->_float = a->_float && b->_float; break; case OP_OR: c->_float = a->_float || b->_float; break; case OP_NOT_F: c->_float = !a->_float; break; case OP_NOT_V: c->_float = !a->vector[0] && !a->vector[1] && !a->vector[2]; break; case OP_NOT_S: c->_float = !a->string || !*PR1_GetString(a->string); break; case OP_NOT_FNC: c->_float = !a->function; break; case OP_NOT_ENT: c->_float = (PROG_TO_EDICT(a->edict) == sv.edicts); break; case OP_EQ_F: c->_float = a->_float == b->_float; break; case OP_EQ_V: c->_float = (a->vector[0] == b->vector[0]) && (a->vector[1] == b->vector[1]) && (a->vector[2] == b->vector[2]); break; case OP_EQ_S: c->_float = !strcmp(PR1_GetString(a->string), PR1_GetString(b->string)); break; case OP_EQ_E: c->_float = a->_int == b->_int; break; case OP_EQ_FNC: c->_float = a->function == b->function; break; case OP_NE_F: c->_float = a->_float != b->_float; break; case OP_NE_V: c->_float = (a->vector[0] != b->vector[0]) || (a->vector[1] != b->vector[1]) || (a->vector[2] != b->vector[2]); break; case OP_NE_S: c->_float = strcmp(PR1_GetString(a->string), PR1_GetString(b->string)); break; case OP_NE_E: c->_float = a->_int != b->_int; break; case OP_NE_FNC: c->_float = a->function != b->function; break; //================== case OP_STORE_F: case OP_STORE_ENT: case OP_STORE_FLD: // integers case OP_STORE_S: case OP_STORE_FNC: // pointers b->_int = a->_int; break; case OP_STORE_V: b->vector[0] = a->vector[0]; b->vector[1] = a->vector[1]; b->vector[2] = a->vector[2]; break; case OP_STOREP_F: case OP_STOREP_ENT: case OP_STOREP_FLD: // integers case OP_STOREP_S: case OP_STOREP_FNC: // pointers ptr = (eval_t *)((byte *)sv.game_edicts + b->_int); ptr->_int = a->_int; break; case OP_STOREP_V: ptr = (eval_t *)((byte *)sv.game_edicts + b->_int); ptr->vector[0] = a->vector[0]; ptr->vector[1] = a->vector[1]; ptr->vector[2] = a->vector[2]; break; case OP_ADDRESS: ed = PROG_TO_EDICT(a->edict); #ifdef PARANOID NUM_FOR_EDICT(ed); // make sure it's in range #endif if (ed == (edict_t *)sv.edicts && sv.state == ss_active) PR_RunError ("assignment to world entity"); c->_int = (byte *)((int *)ed->v + PR_FIELDOFS(b->_int)) - (byte *)sv.game_edicts; break; case OP_LOAD_F: case OP_LOAD_FLD: case OP_LOAD_ENT: case OP_LOAD_S: case OP_LOAD_FNC: ed = PROG_TO_EDICT(a->edict); #ifdef PARANOID NUM_FOR_EDICT(ed); // make sure it's in range #endif //need for checking 'cmd mmode player N', if N >= 0x10000000 =(signed)=> negative if (b->_int >= 0) { a = (eval_t *)((int *)ed->v + PR_FIELDOFS(b->_int)); c->_int = a->_int; } else c->_int = 0; break; case OP_LOAD_V: ed = PROG_TO_EDICT(a->edict); #ifdef PARANOID NUM_FOR_EDICT(ed); // make sure it's in range #endif a = (eval_t *)((int *)ed->v + PR_FIELDOFS(b->_int)); c->vector[0] = a->vector[0]; c->vector[1] = a->vector[1]; c->vector[2] = a->vector[2]; break; //================== case OP_IFNOT: if (!a->_int) s += st->b - 1; // offset the s++ break; case OP_IF: if (a->_int) s += st->b - 1; // offset the s++ break; case OP_GOTO: s += st->a - 1; // offset the s++ break; case OP_CALL0: case OP_CALL1: case OP_CALL2: case OP_CALL3: case OP_CALL4: case OP_CALL5: case OP_CALL6: case OP_CALL7: case OP_CALL8: pr_argc = st->op - OP_CALL0; if (!a->function) PR_RunError ("NULL function"); newf = &pr_functions[a->function]; if (newf->first_statement < 0) { // negative statements are built in functions i = -newf->first_statement; if (i >= pr_numbuiltins) PR_RunError ("Bad builtin call number"); pr_builtins[i] (); break; } s = PR_EnterFunction (newf); break; case OP_DONE: case OP_RETURN: pr_globals[OFS_RETURN] = pr_globals[st->a]; pr_globals[OFS_RETURN+1] = pr_globals[st->a+1]; pr_globals[OFS_RETURN+2] = pr_globals[st->a+2]; s = PR_LeaveFunction (); if (pr_depth == exitdepth) return; // all done break; case OP_STATE: ed = PROG_TO_EDICT(pr_global_struct->self); ed->v->nextthink = pr_global_struct->time + 0.1; if (a->_float != ed->v->frame) { ed->v->frame = a->_float; } ed->v->think = b->function; break; default: PR_RunError ("Bad opcode %i", st->op); } } } //============================================================================= char *pr_newstrtbl[MAX_PRSTR]; char *pr_strtbl[MAX_PRSTR]; int num_prstr; char *PR1_GetString(int num) { if (num < 0) { //Con_DPrintf("GET:%d == %s\n", num, pr_strtbl[-num]); num = -num; if (num >= 2 * MAX_PRSTR) { Con_Printf("PR1_GetString: num = %d\n", num);// May be will be better to generate PR_RunError? return NULL; } if (num >= MAX_PRSTR) return pr_newstrtbl[num - MAX_PRSTR]; return pr_strtbl[num]; } return pr_strings + num; } void PR1_SetString(string_t* address, char* s) { int i; if (!address) { return; } if (!s || !s[0]) { *address = 0; return; } if (s - pr_strings < 0 || s - pr_strings > INT_MAX) { for (i = 0; i < num_prstr; i++) { if (pr_strtbl[i] == s) { *address = -i; return; } } if (num_prstr + 1 >= MAX_PRSTR) { Sys_Error("MAX_PRSTR"); } pr_strtbl[++num_prstr] = s; //Con_DPrintf("SET:%d == %s\n", -num_prstr, s); *address = -num_prstr; } else { *address = (int)(s - pr_strings); } } /* ============== PR_SetTmpString temp strings are used for qc function parameters many calls to function could cause strtbl overflow ============== */ void PR_SetTmpString(string_t* target, const char *s) { static int index1; static char tmp[8][2048]; index1 = (index1 + 1) & 7; strlcpy(tmp[index1], s, sizeof(tmp[index1])); PR1_SetString(target, tmp[index1]); } //============================================================================= void PR1_GameClientDisconnect(int spec) { if (spec) { if (mod_SpectatorDisconnect) PR_ExecuteProgram(mod_SpectatorDisconnect); } else { PR_ExecuteProgram(PR_GLOBAL(ClientDisconnect)); } } //============================================================================= void PR1_GameClientConnect(int spec) { if (spec) { if (mod_SpectatorConnect) PR_ExecuteProgram(mod_SpectatorConnect); } else { PR_ExecuteProgram(PR_GLOBAL(ClientConnect)); } } //============================================================================= void PR1_GamePutClientInServer(int spec) { if (spec) { // none... } else { PR_ExecuteProgram(PR_GLOBAL(PutClientInServer)); } } //============================================================================= void PR1_GameClientPreThink(int spec) { if (spec) { // none... } else { PR_ExecuteProgram(PR_GLOBAL(PlayerPreThink)); } } //============================================================================= void PR1_GameClientPostThink(int spec) { if (spec) { if (mod_SpectatorThink) PR_ExecuteProgram(mod_SpectatorThink); } else { PR_ExecuteProgram(PR_GLOBAL(PlayerPostThink)); } } //============================================================================= qbool PR1_ClientSay(int isTeamSay, char *message) { qbool ret = false; if (mod_ChatMessage) { int j; // remove surrounding " if any. if (message[0] == '"' && (j = (int)strlen(message)) > 2 && message[j-1] == '"') { message++; // skip opening ". message[max(0,(int)strlen(message)-1)] = 0; // truncate closing ". } PR_SetTmpString(&G_INT(OFS_PARM0), message); G_FLOAT(OFS_PARM1) = (float)isTeamSay; PR_ExecuteProgram(mod_ChatMessage); ret = !!G_FLOAT(OFS_RETURN); } return ret; } //============================================================================= void PR1_PausedTic(float duration) { if (GE_PausedTic) { G_FLOAT(OFS_PARM0) = duration; PR_ExecuteProgram (GE_PausedTic); } } //============================================================================= void PR1_UnLoadProgs(void) { if (progs) { // FIXME: There should be done alot of variables reseting... #ifdef WITH_NQPROGS pr_nqprogs = false; #endif progs = NULL; } } #endif // !CLIENTONLY mvdsv-0.35/src/progdefs.h000066400000000000000000000062101427146041000153470ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef __PROGDEFS_H__ #define __PROGDEFS_H__ /* file generated by qcc, do not modify */ typedef struct { int pad[28]; int self; int other; int world; float time; float frametime; int newmis; float force_retouch; string_t mapname; float serverflags; float total_secrets; float total_monsters; float found_secrets; float killed_monsters; float parm1; float parm2; float parm3; float parm4; float parm5; float parm6; float parm7; float parm8; float parm9; float parm10; float parm11; float parm12; float parm13; float parm14; float parm15; float parm16; vec3_t v_forward; vec3_t v_up; vec3_t v_right; float trace_allsolid; float trace_startsolid; float trace_fraction; vec3_t trace_endpos; vec3_t trace_plane_normal; float trace_plane_dist; int trace_ent; float trace_inopen; float trace_inwater; int msg_entity; func_t main; func_t StartFrame; func_t PlayerPreThink; func_t PlayerPostThink; func_t ClientKill; func_t ClientConnect; func_t PutClientInServer; func_t ClientDisconnect; func_t SetNewParms; func_t SetChangeParms; } globalvars_t; typedef struct { float modelindex; vec3_t absmin; vec3_t absmax; float ltime; float lastruntime; float movetype; float solid; vec3_t origin; vec3_t oldorigin; vec3_t velocity; vec3_t angles; vec3_t avelocity; string_t classname; string_t model; float frame; float skin; float effects; vec3_t mins; vec3_t maxs; vec3_t size; func_t touch; func_t use; func_t think; func_t blocked; float nextthink; int groundentity; float health; float frags; float weapon; string_t weaponmodel; float weaponframe; float currentammo; float ammo_shells; float ammo_nails; float ammo_rockets; float ammo_cells; float items; float takedamage; int chain; float deadflag; vec3_t view_ofs; float button0; float button1; float button2; float impulse; float fixangle; vec3_t v_angle; string_t netname; int enemy; float flags; float colormap; float team; float max_health; float teleport_time; float armortype; float armorvalue; float waterlevel; float watertype; float ideal_yaw; float yaw_speed; int aiment; int goalentity; float spawnflags; string_t target; string_t targetname; float dmg_take; float dmg_save; int dmg_inflictor; int owner; vec3_t movedir; string_t message; float sounds; string_t noise; string_t noise1; string_t noise2; string_t noise3; } entvars_t; #define PROGHEADER_CRC 54730 #endif /* !__PROGDEFS_H__ */ mvdsv-0.35/src/progs.h000066400000000000000000000203131427146041000146700ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef __PROGS_H__ #define __PROGS_H__ #include "pr_comp.h" // defs shared with qcc #include "progdefs.h" // generated by program cdefs typedef union eval_s { string_t string; float _float; float vector[3]; func_t function; int _int; int edict; } eval_t; struct edict_s; // forward referecnce for link_t typedef struct link_s { struct edict_s *ed; struct link_s *prev, *next; } link_t; #define EDICT_FROM_AREA(l) ((l)->ed) #define MAX_ENT_LEAFS 16 typedef struct sv_edict_s { qbool free; link_t area; // linked to a division node or leaf int entnum; int num_leafs; short leafnums[MAX_ENT_LEAFS]; entity_state_t baseline; float freetime; // sv.time when the object was freed double lastruntime; // sv.time when SV_RunEntity was last called for this edict (Tonik) } sv_edict_t; typedef struct edict_s { sv_edict_t e; // server side part of the edict_t entvars_t *v; // C exported fields from progs } edict_t; //============================================================================ extern dprograms_t *progs; extern dfunction_t *pr_functions; extern char *pr_strings; extern ddef_t *pr_globaldefs; extern ddef_t *pr_fielddefs; extern dstatement_t *pr_statements; extern globalvars_t *pr_global_struct; extern float *pr_globals; // same as pr_global_struct extern int pr_edict_size; // in bytes extern cvar_t sv_progsname; #ifdef WITH_NQPROGS extern cvar_t sv_forcenqprogs; #endif //============================================================================ #ifdef WITH_NQPROGS extern qbool pr_nqprogs; extern int pr_fieldoffsetpatch[106]; extern int pr_globaloffsetpatch[62]; #define PR_FIELDOFS(i) ((unsigned int)(i) > 105 ? (i) : pr_fieldoffsetpatch[i]) #define PR_GLOBAL(field) (((globalvars_t *)((byte *)pr_global_struct + \ pr_globaloffsetpatch[((int *)&((globalvars_t *)0)->field - (int *)0) - 28]))->field) void NQP_Reset (void); #else // !WITH_NQPROGS #define pr_nqprogs 0 #define PR_FIELDOFS(i) (i) #define PR_GLOBAL(field) pr_global_struct->field #define NQP_Reset() #endif //============================================================================ void PR_Init (void); void PR_ExecuteProgram (func_t fnum); void PR_InitPatchTables (void); // NQ progs support void PR_Profile_f (void); void ED_ClearEdict (edict_t *e); edict_t *ED_Alloc (void); void ED_Free (edict_t *ed); char *ED_NewString (char *string); // returns a copy of the string allocated from the server's string heap void ED_Print (edict_t *ed); void ED_Write (FILE *f, edict_t *ed); const char *ED_ParseEdict (const char *data, edict_t *ent); void ED_WriteGlobals (FILE *f); void ED_ParseGlobals (const char *data); void ED_LoadFromFile (const char *data); edict_t *EDICT_NUM(int n); int NUM_FOR_EDICT(edict_t *e); #define NEXT_EDICT(e) ((edict_t *)((byte *)(e) + sizeof(edict_t))) #define EDICT_TO_PROG(e) ((byte *)(e)->v - (byte *)sv.game_edicts) #define PROG_TO_EDICT(e) (&sv.edicts[(e)/pr_edict_size]) //============================================================================ #define G_FLOAT(o) (pr_globals[o]) #define G_INT(o) (*(int *)&pr_globals[o]) #define G_EDICT(o) (&sv.edicts[(*(int *)&pr_globals[o])/pr_edict_size]) #define G_EDICTNUM(o) NUM_FOR_EDICT(G_EDICT(o)) #define G_VECTOR(o) (&pr_globals[o]) #define G_STRING(o) (PR1_GetString(*(string_t *)&pr_globals[o])) #define G_FUNCTION(o) (*(func_t *)&pr_globals[o]) #define E_FLOAT(e,o) (((float*)e->v)[o]) #define E_INT(e,o) (*(int *)&((float*)e->v)[o]) #define E_VECTOR(e,o) (&((float*)e->v)[o]) #define E_STRING(e,o) (PR1_GetString(*(string_t *)&((float*)e->v)[PR_FIELDOFS(o)])) typedef void (*builtin_t) (void); extern builtin_t *pr_builtins; extern int pr_numbuiltins; extern int pr_argc; extern qbool pr_trace; extern dfunction_t *pr_xfunction; extern int pr_xstatement; extern func_t mod_ConsoleCmd, mod_UserCmd; extern func_t mod_UserInfo_Changed, mod_localinfoChanged; extern func_t mod_ChatMessage; extern func_t mod_SpectatorConnect, mod_SpectatorDisconnect, mod_SpectatorThink; extern func_t GE_ClientCommand, GE_PausedTic, GE_ShouldPause; extern int fofs_items2; // ZQ_ITEMS2 extension extern int fofs_vw_index; // ZQ_VWEP extern int fofs_movement; extern int fofs_gravity, fofs_maxspeed; extern int fofs_hideentity; extern int fofs_trackent; extern int fofs_visibility; extern int fofs_hide_players; extern int fofs_teleported; #define EdictFieldFloat(ed, fieldoffset) ((eval_t *)((byte *)(ed)->v + (fieldoffset)))->_float #define EdictFieldVector(ed, fieldoffset) ((eval_t *)((byte *)(ed)->v + (fieldoffset)))->vector void PR_RunError (char *error, ...); void ED_PrintEdicts (void); void ED_PrintNum (int ent); eval_t *PR1_GetEdictFieldValue(edict_t *ed, char *field); int ED1_FindFieldOffset (char *field); // // PR Strings stuff // #define MAX_PRSTR 1024 extern char *pr_strtbl[MAX_PRSTR]; extern char *pr_newstrtbl[MAX_PRSTR]; extern int num_prstr; char *PR1_GetString(int num); void PR1_SetString(string_t* address, char* s); void PR_SetTmpString(string_t* address, const char *s); void PR1_LoadProgs (void); void PR1_InitProg(void); void PR1_Init(void); #define PR1_GameShutDown() // PR1 does not really have it. void PR1_UnLoadProgs(void); void PR1_GameClientDisconnect(int spec); void PR1_GameClientConnect(int spec); void PR1_GamePutClientInServer(int spec); void PR1_GameClientPreThink(int spec); void PR1_GameClientPostThink(int spec); qbool PR1_ClientSay(int isTeamSay, char *message); void PR1_PausedTic(float duration); qbool PR1_ClientCmd(void); #define PR1_GameSetChangeParms() PR_ExecuteProgram(PR_GLOBAL(SetChangeParms)) #define PR1_GameSetNewParms() PR_ExecuteProgram(PR_GLOBAL(SetNewParms)) #define PR1_GameStartFrame() PR_ExecuteProgram (PR_GLOBAL(StartFrame)) #define PR1_ClientKill() PR_ExecuteProgram (PR_GLOBAL(ClientKill)) #define PR1_UserInfoChanged(after) (0) // PR1 does not really have it, // we have mod_UserInfo_Changed but it is slightly different. #define PR1_LoadEnts ED_LoadFromFile #define PR1_EdictThink PR_ExecuteProgram #define PR1_EdictTouch PR_ExecuteProgram #define PR1_EdictBlocked PR_ExecuteProgram #ifndef USE_PR2 #define PR_LoadProgs PR1_LoadProgs #define PR_InitProg PR1_InitProg #define PR_GameShutDown PR1_GameShutDown #define PR_UnLoadProgs PR1_UnLoadProgs #define PR_Init PR1_Init //#define PR_GetString PR1_GetString //#define PR_SetString PR1_SetString #define PR_GetEntityString PR1_GetString #define PR_SetEntityString(ent, target, value) PR1_SetString(&target, value) #define PR_SetGlobalString(target, value) PR1_SetString(&target, value) #define ED_FindFieldOffset ED1_FindFieldOffset #define PR_GetEdictFieldValue PR1_GetEdictFieldValue #define PR_GameClientDisconnect PR1_GameClientDisconnect #define PR_GameClientConnect PR1_GameClientConnect #define PR_GamePutClientInServer PR1_GamePutClientInServer #define PR_GameClientPreThink PR1_GameClientPreThink #define PR_GameClientPostThink PR1_GameClientPostThink #define PR_ClientSay PR1_ClientSay #define PR_PausedTic PR1_PausedTic #define PR_ClientCmd PR1_ClientCmd #define PR_GameSetChangeParms PR1_GameSetChangeParms #define PR_GameSetNewParms PR1_GameSetNewParms #define PR_GameStartFrame(isBotFrame) { if (!isBotFrame) { PR1_GameStartFrame(); } } #define PR_ClientKill PR1_ClientKill #define PR_UserInfoChanged PR1_UserInfoChanged #define PR_LoadEnts PR1_LoadEnts #define PR_EdictThink PR1_EdictThink #define PR_EdictTouch PR1_EdictTouch #define PR_EdictBlocked PR1_EdictBlocked #define PR_ClearEdict(ent) #endif // pr_cmds.c void PR_InitBuiltins (void); #endif /* !__PROGS_H__ */ mvdsv-0.35/src/protocol.h000066400000000000000000000534401427146041000154060ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // protocol.h -- communications protocols #ifndef __PROTOCOL_H__ #define __PROTOCOL_H__ #define PROTOCOL_VERSION 28 #define PROTOCOL_VERSION_FTE (('F'<<0) + ('T'<<8) + ('E'<<16) + ('X' << 24)) //fte extensions. #define PROTOCOL_VERSION_FTE2 (('F'<<0) + ('T'<<8) + ('E'<<16) + ('2' << 24)) //fte extensions. #define PROTOCOL_VERSION_MVD1 (('M'<<0) + ('V'<<8) + ('D'<<16) + ('1' << 24)) //mvdsv extensions // qqshka: FTE_PEXT_ACCURATETIMINGS - not actually used in ezquake. // I added it to ezquake in hope what someone made some rockets(enitities) smoothing code... // But it not happens, so better turn it off. #define FTE_PEXT_TRANS 0x00000008 // .alpha support //#define FTE_PEXT_ACCURATETIMINGS 0x00000040 #define FTE_PEXT_HLBSP 0x00000200 //stops fte servers from complaining #define FTE_PEXT_MODELDBL 0x00001000 #define FTE_PEXT_ENTITYDBL 0x00002000 //max of 1024 ents instead of 512 #define FTE_PEXT_ENTITYDBL2 0x00004000 //max of 1024 ents instead of 512 #define FTE_PEXT_FLOATCOORDS 0x00008000 //supports floating point origins. #define FTE_PEXT_SPAWNSTATIC2 0x00400000 //Sends an entity delta instead of a baseline. #define FTE_PEXT_256PACKETENTITIES 0x01000000 //Client can recieve 256 packet entities. #define FTE_PEXT_CHUNKEDDOWNLOADS 0x20000000 //alternate file download method. Hopefully it'll give quadroupled download speed, especially on higher pings. //=============================================== #define PORT_CLIENT 27001 #define PORT_MASTER 27000 #define PORT_SERVER 27500 #define PORT_QUAKETV 27900 //=============================================== // fte protocol extensions. #define PROTOCOL_VERSION_FTE (('F'<<0) + ('T'<<8) + ('E'<<16) + ('X' << 24)) #define PROTOCOL_VERSION_FTE2 (('F'<<0) + ('T'<<8) + ('E'<<16) + ('2' << 24)) #define PROTOCOL_VERSION_MVD1 (('M'<<0) + ('V'<<8) + ('D'<<16) + ('1' << 24)) // // Not all of that really supported. // Some supported by client only. // #ifdef PROTOCOL_VERSION_FTE #define FTE_PEXT_TRANS 0x00000008 // .alpha support #define FTE_PEXT_ACCURATETIMINGS 0x00000040 #define FTE_PEXT_HLBSP 0x00000200 //stops fte servers from complaining #define FTE_PEXT_MODELDBL 0x00001000 #define FTE_PEXT_ENTITYDBL 0x00002000 //max of 1024 ents instead of 512 #define FTE_PEXT_ENTITYDBL2 0x00004000 //max of 1024 ents instead of 512 #define FTE_PEXT_FLOATCOORDS 0x00008000 //supports floating point origins. #define FTE_PEXT_SPAWNSTATIC2 0x00400000 //Sends an entity delta instead of a baseline. #define FTE_PEXT_256PACKETENTITIES 0x01000000 //Client can recieve 256 packet entities. #define FTE_PEXT_CHUNKEDDOWNLOADS 0x20000000 //alternate file download method. Hopefully it'll give quadroupled download speed, especially on higher pings. #endif // PROTOCOL_VERSION_FTE #ifdef PROTOCOL_VERSION_FTE2 #define FTE_PEXT2_VOICECHAT 0x00000002 #endif // PROTOCOL_VERSION_FTE2 #ifdef PROTOCOL_VERSION_MVD1 #define MVD_PEXT1_FLOATCOORDS (1 << 0) // FTE_PEXT_FLOATCOORDS but for entity/player coords only #define MVD_PEXT1_HIGHLAGTELEPORT (1 << 1) // Adjust movement direction for frames following teleport #define MVD_PEXT1_SERVERSIDEWEAPON (1 << 2) // Server-side weapon selection #define MVD_PEXT1_DEBUG_WEAPON (1 << 3) // Send weapon-choice explanation to server for logging #define MVD_PEXT1_DEBUG_ANTILAG (1 << 4) // Send predicted positions to server (compare to antilagged positions) #define MVD_PEXT1_HIDDEN_MESSAGES (1 << 5) // dem_multiple(0) packets are in format ( + )* #define MVD_PEXT1_SERVERSIDEWEAPON2 (1 << 6) // Server-side weapon selection supports clc_mvd_weapon_full_impulse #if defined(MVD_PEXT1_DEBUG_ANTILAG) || defined(MVD_PEXT1_DEBUG_WEAPON) #define MVD_PEXT1_DEBUG #define MVD_PEXT1_ANTILAG_CLIENTPOS 128 // flag set on the playernum if the client positions are also included #define clc_mvd_debug 201 #define clc_mvd_debug_type_antilag 1 #define clc_mvd_debug_type_weapon 2 #endif #define MVD_PEXT1_INCLUDEINMVD (MVD_PEXT1_HIDDEN_MESSAGES) #endif // PROTOCOL_VERSION_MVD1 //============================================== // // ZQuake protocol extensions (*z_ext serverinfo key) // #define Z_EXT_PM_TYPE (1<<0) // basic PM_TYPE functionality (reliable jump_held) #define Z_EXT_PM_TYPE_NEW (1<<1) // adds PM_FLY, PM_SPECTATOR #define Z_EXT_VIEWHEIGHT (1<<2) // STAT_VIEWHEIGHT #define Z_EXT_SERVERTIME (1<<3) // STAT_TIME #define Z_EXT_PITCHLIMITS (1<<4) // serverinfo maxpitch & minpitch #define Z_EXT_JOIN_OBSERVE (1<<5) // server: "join" and "observe" commands are supported // client: on-the-fly spectator <-> player switching supported #define Z_EXT_PF_ONGROUND (1<<6) // server: PF_ONGROUND is valid for all svc_playerinfo #define Z_EXT_VWEP (1<<7) // ZQ_VWEP extension #define Z_EXT_PF_SOLID (1<<8) // what our client supports #define CLIENT_EXTENSIONS ( \ Z_EXT_PM_TYPE | \ Z_EXT_PM_TYPE_NEW | \ Z_EXT_VIEWHEIGHT | \ Z_EXT_SERVERTIME | \ Z_EXT_PITCHLIMITS | \ Z_EXT_JOIN_OBSERVE | \ Z_EXT_PF_ONGROUND | \ Z_EXT_VWEP | \ Z_EXT_PF_SOLID \ ) // what our server supports #define SERVER_EXTENSIONS ( \ Z_EXT_PM_TYPE | \ Z_EXT_PM_TYPE_NEW | \ Z_EXT_VIEWHEIGHT | \ Z_EXT_SERVERTIME | \ Z_EXT_PITCHLIMITS | \ Z_EXT_JOIN_OBSERVE | \ Z_EXT_PF_ONGROUND | \ Z_EXT_VWEP | \ Z_EXT_PF_SOLID \ ) //========================================= // out of band message id bytes // M = master, S = server, C = client, A = any // the second character will always be \n if the message isn't a single // byte long (?? not true anymore?) #define S2C_CHALLENGE 'c' #define S2C_CONNECTION 'j' #define A2A_PING 'k' // respond with an A2A_ACK #define A2A_ACK 'l' // general acknowledgement without info #define A2A_NACK 'm' // [+ comment] general failure #define A2A_ECHO 'e' // for echoing #define A2C_PRINT 'n' // print a message on client #define S2M_HEARTBEAT 'a' // + serverinfo + userlist + fraglist #define A2C_CLIENT_COMMAND 'B' // + command line #define S2M_SHUTDOWN 'C' //================== // note that there are some defs.qc that mirror to these numbers // also related to svc_strings[] in cl_parse //================== // // server to client #define svc_bad 0 #define svc_nop 1 #define svc_disconnect 2 #define svc_updatestat 3 // [byte] [byte] #define nq_svc_version 4 // [long] server version #define nq_svc_setview 5 // [short] entity number #define svc_sound 6 // #define nq_svc_time 7 // [float] server time #define svc_print 8 // [byte] id [string] null terminated string #define svc_stufftext 9 // [string] stuffed into client's console buffer // the string should be \n terminated #define svc_setangle 10 // [angle3] set the view angle to this absolute value #define svc_serverdata 11 // [long] protocol ... #define svc_lightstyle 12 // [byte] [string] #define nq_svc_updatename 13 // [byte] [string] #define svc_updatefrags 14 // [byte] [short] #define nq_svc_clientdata 15 // #define svc_stopsound 16 // #define nq_svc_updatecolors 17 // [byte] [byte] [byte] #define nq_svc_particle 18 // [vec3] #define svc_damage 19 #define svc_spawnstatic 20 #define svc_fte_spawnstatic2 21 // @!@!@! #define svc_spawnbaseline 22 #define svc_temp_entity 23 // variable #define svc_setpause 24 // [byte] on / off #define nq_svc_signonnum 25 // [byte] used for the signon sequence #define svc_centerprint 26 // [string] to put in center of the screen #define svc_killedmonster 27 #define svc_foundsecret 28 #define svc_spawnstaticsound 29 // [coord3] [byte] samp [byte] vol [byte] aten #define svc_intermission 30 // [vec3_t] origin [vec3_t] angle #define svc_finale 31 // [string] text #define svc_cdtrack 32 // [byte] track #define svc_sellscreen 33 #define nq_svc_cutscene 34 // same as svc_smallkick #define svc_smallkick 34 // set client punchangle to 2 #define svc_bigkick 35 // set client punchangle to 4 #define svc_updateping 36 // [byte] [short] #define svc_updateentertime 37 // [byte] [float] #define svc_updatestatlong 38 // [byte] [long] #define svc_muzzleflash 39 // [short] entity #define svc_updateuserinfo 40 // [byte] slot [long] uid // [string] userinfo #define svc_download 41 // [short] size [size bytes] #define svc_playerinfo 42 // variable #define svc_nails 43 // [byte] num [48 bits] xyzpy 12 12 12 4 8 #define svc_chokecount 44 // [byte] packets choked #define svc_modellist 45 // [strings] #define svc_soundlist 46 // [strings] #define svc_packetentities 47 // [...] #define svc_deltapacketentities 48 // [...] #define svc_maxspeed 49 // maxspeed change, for prediction #define svc_entgravity 50 // gravity change, for prediction #define svc_setinfo 51 // setinfo on a client #define svc_serverinfo 52 // serverinfo #define svc_updatepl 53 // [byte] [byte] #define svc_nails2 54 // [byte] num [52 bits] nxyzpy 8 12 12 12 4 8 // mvdsv extended svcs (for mvd playback) #ifdef FTE_PEXT_MODELDBL #define svc_fte_modellistshort 60 // [strings] #endif #define svc_fte_spawnbaseline2 66 #define svc_qizmovoice 83 #ifdef FTE_PEXT2_VOICECHAT #define svc_fte_voicechat 84 // FTE voice chat. #endif //============================================== // // client to server // #define clc_bad 0 #define clc_nop 1 //define clc_doublemove 2 #define clc_move 3 // [[usercmd_t] #define clc_stringcmd 4 // [string] message #define clc_delta 5 // [byte] sequence number, requests delta compression of message #define clc_tmove 6 // teleport request, spectator only #define clc_upload 7 // #ifdef FTE_PEXT2_VOICECHAT #define clc_voicechat 83 // FTE voice chat. #endif #ifdef MVD_PEXT1_SERVERSIDEWEAPON #define clc_mvd_weapon 200 // server-side weapon selection // selection options #define clc_mvd_weapon_mode_presel 1 // preselect (don't send impulses until -attack/+attack) #define clc_mvd_weapon_mode_iffiring 2 // don't wait for -attack before pre-selecting weapon #define clc_mvd_weapon_forget_ranking 4 // forget priority list after initial selection (requires extra byte for age) // hide options #define clc_mvd_weapon_hide_axe 8 // on subsequent -attack, hide weapon and switch to axe #define clc_mvd_weapon_hide_sg 16 // on subsequent -attack, hide weapon and switch to sg #define clc_mvd_weapon_reset_on_death 32 // on death, go back to 2 1 #define clc_mvd_weapon_switching 64 // if not set, disable all server-side weapon switching #define clc_mvd_weapon_full_impulse 128 // if set, each weapon set as a byte, rather than packing two into one //byte MSG_EncodeMVDSVWeaponFlags(int deathmatch, int weaponmode, int weaponhide, qbool weaponhide_axe, qbool forgetorder, qbool forgetondeath); //void MSG_DecodeMVDSVWeaponFlags(int flags, int* weaponmode, int* weaponhide, qbool* forgetorder, int* sequence); #endif //============================================== // playerinfo flags from server // playerinfo always sends: playernum, flags, origin[] and framenumber #define PF_MSEC (1 << 0) #define PF_COMMAND (1 << 1) #define PF_VELOCITY1 (1 << 2) #define PF_VELOCITY2 (1 << 3) #define PF_VELOCITY3 (1 << 4) #define PF_MODEL (1 << 5) #define PF_SKINNUM (1 << 6) #define PF_EFFECTS (1 << 7) #define PF_WEAPONFRAME (1 << 8) // only sent for view player #define PF_DEAD (1 << 9) // don't block movement any more #define PF_GIB (1 << 10) // offset the view height differently // bits 11..13 are player move type bits (ZQuake extension) #define PF_PMC_SHIFT 11 #define PF_PMC_MASK 7 #define PF_ONGROUND (1<<14) // ZQuake extension #define PF_SOLID (1<<15) // ZQuake extension // encoded player move types #define PMC_NORMAL 0 // normal ground movement #define PMC_NORMAL_JUMP_HELD 1 // normal ground novement + jump_held #define PMC_OLD_SPECTATOR 2 // fly through walls (QW compatibility mode) #define PMC_SPECTATOR 3 // fly through walls #define PMC_FLY 4 // fly, bump into walls #define PMC_NONE 5 // can't move (client had better lerp the origin...) #define PMC_LOCK 6 // server controls view angles #define PMC_EXTRA3 7 // future extension //============================================== // if the high bit of the client to server byte is set, the low bits are // client move cmd bits // ms and angle2 are always sent, the others are optional #define CM_ANGLE1 (1 << 0) #define CM_ANGLE3 (1 << 1) #define CM_FORWARD (1 << 2) #define CM_SIDE (1 << 3) #define CM_UP (1 << 4) #define CM_BUTTONS (1 << 5) #define CM_IMPULSE (1 << 6) #define CM_ANGLE2 (1 << 7) //============================================== // // Player flags in mvd demos. // Should be in server.h but unfortunately shared with cl_demo.c. // #define DF_ORIGIN 1 #define DF_ANGLES (1 << 3) #define DF_EFFECTS (1 << 6) #define DF_SKINNUM (1 << 7) #define DF_DEAD (1 << 8) #define DF_GIB (1 << 9) #define DF_WEAPONFRAME (1 << 10) #define DF_MODEL (1 << 11) //============================================== // the first 16 bits of a packetentities update holds 9 bits // of entity number and 7 bits of flags #define U_ORIGIN1 (1 << 9) #define U_ORIGIN2 (1 << 10) #define U_ORIGIN3 (1 << 11) #define U_ANGLE2 (1 << 12) #define U_FRAME (1 << 13) #define U_REMOVE (1 << 14) // REMOVE this entity, don't add it #define U_MOREBITS (1 << 15) // if MOREBITS is set, these additional flags are read in next #define U_ANGLE1 (1 << 0) #define U_ANGLE3 (1 << 1) #define U_MODEL (1 << 2) #define U_COLORMAP (1 << 3) #define U_SKIN (1 << 4) #define U_EFFECTS (1 << 5) #define U_SOLID (1 << 6) // the entity should be solid for prediction #define U_CHECKMOREBITS ((1<<9) - 1) /* MVDSV compatibility */ #ifdef PROTOCOL_VERSION_FTE #define U_FTE_EVENMORE (1<<7) //extension info follows //fte extensions //EVENMORE flags #ifdef FTE_PEXT_SCALE #define U_FTE_SCALE (1<<0) //scaler of alias models #endif #ifdef FTE_PEXT_TRANS #define U_FTE_TRANS (1<<1) //transparency value #endif #ifdef FTE_PEXT_TRANS #define PF_TRANS_Z (1<<17) #endif #ifdef FTE_PEXT_FATNESS #define U_FTE_FATNESS (1<<2) //byte describing how fat an alias model should be. //moves verticies along normals // Useful for vacuum chambers... #endif #ifdef FTE_PEXT_MODELDBL #define U_FTE_MODELDBL (1<<3) //extra bit for modelindexes #endif #define U_FTE_UNUSED1 (1<<4) //FIXME: IMPLEMENT #ifdef FTE_PEXT_ENTITYDBL #define U_FTE_ENTITYDBL (1<<5) //use an extra byte for origin parts, cos one of them is off #endif #ifdef FTE_PEXT_ENTITYDBL2 #define U_FTE_ENTITYDBL2 (1<<6) //use an extra byte for origin parts, cos one of them is off #endif #define U_FTE_YETMORE (1<<7) //even more extension info stuff. #define U_FTE_DRAWFLAGS (1<<8) //use an extra qbyte for origin parts, cos one of them is off #define U_FTE_ABSLIGHT (1<<9) //Force a lightlevel #define U_FTE_COLOURMOD (1<<10) //rgb #define U_FTE_DPFLAGS (1<<11) #define U_FTE_TAGINFO (1<<12) #define U_FTE_LIGHT (1<<13) #define U_FTE_EFFECTS16 (1<<14) #define U_FTE_FARMORE (1<<15) #endif //============================================== // a sound with no channel is a local only sound // the sound field has bits 0-2: channel, 3-12: entity #define SND_VOLUME (1 << 15) // a byte #define SND_ATTENUATION (1 << 14) // a byte #define DEFAULT_SOUND_PACKET_VOLUME 255 #define DEFAULT_SOUND_PACKET_ATTENUATION 1.0 // svc_print messages have an id, so messages can be filtered #define PRINT_LOW 0 #define PRINT_MEDIUM 1 #define PRINT_HIGH 2 #define PRINT_CHAT 3 // also go to chat buffer // // temp entity events // #define TE_SPIKE 0 #define TE_SUPERSPIKE 1 #define TE_GUNSHOT 2 #define TE_EXPLOSION 3 #define TE_TAREXPLOSION 4 #define TE_LIGHTNING1 5 #define TE_LIGHTNING2 6 #define TE_WIZSPIKE 7 #define TE_KNIGHTSPIKE 8 #define TE_LIGHTNING3 9 #define TE_LAVASPLASH 10 #define TE_TELEPORT 11 #define TE_BLOOD 12 #define TE_LIGHTNINGBLOOD 13 #define NQ_TE_EXPLOSION2 12 #define NQ_TE_BEAM 13 #define DEFAULT_VIEWHEIGHT 22 /* ========================================================== ELEMENTS COMMUNICATED ACROSS THE NET ========================================================== */ #define MAX_CLIENTS 32 #define UPDATE_BACKUP 64 // copies of entity_state_t to keep buffered (must be power of two) #define UPDATE_MASK (UPDATE_BACKUP - 1) // entity_state_t is the information conveyed from the server // in an update message typedef struct entity_state_s { int number; // edict index int flags; // nolerp, etc vec3_t origin; vec3_t angles; int modelindex; int frame; int colormap; int skinnum; int effects; #ifndef SERVERONLY // Our server does not use it. byte trans; #endif } entity_state_t; #define MAX_PACKET_ENTITIES 64 // doesn't count nails #define MAX_PEXT256_PACKET_ENTITIES 256 // up to 256 ents, look FTE_PEXT_256PACKETENTITIES #define MAX_MVD_PACKET_ENTITIES 300 // !!! MUST not be less than any of above values!!! typedef struct packet_entities_s { int num_entities; entity_state_t entities[MAX_MVD_PACKET_ENTITIES]; } packet_entities_t; typedef struct usercmd_s { byte msec; vec3_t angles; short forwardmove, sidemove, upmove; byte buttons; byte impulse; } usercmd_t; // usercmd button bits #define BUTTON_ATTACK (1 << 0) #define BUTTON_JUMP (1 << 1) #define BUTTON_USE (1 << 2) #define BUTTON_ATTACK2 (1 << 3) // // demo recording // // TODO: Make into an enum. #define dem_cmd 0 // A user cmd movement message. #define dem_read 1 // A net message. #define dem_set 2 // Appears only once at the beginning of a demo, // contains the outgoing / incoming sequence numbers at demo start. #define dem_multiple 3 // MVD ONLY. This message is directed to several clients. #define dem_single 4 // MVD ONLY. This message is directed to a single client. #define dem_stats 5 // MVD ONLY. Stats update for a player. #define dem_all 6 // MVD ONLY. This message is directed to all clients. #ifndef SERVERONLY #define MAX_TEMP_ENTITIES 32 typedef struct temp_entity_s { vec3_t pos; // Position of temp entity. float time; // Time of temp entity. int type; // Type of temp entity. } temp_entity_t; typedef struct temp_entity_list_s { temp_entity_t list[MAX_TEMP_ENTITIES]; int count; } temp_entity_list_t; #endif // !SERVERONLY // hidden messages inserted into .mvd files // embedded in dem_multiple(0) - should be safely skipped in clients // format is * where is duplicated if 0xFFFF. is length of the data packet, not the header enum { mvdhidden_antilag_position = 0x0000, // mvdhidden_antilag_position_header_t mvdhidden_antilag_position_t* mvdhidden_usercmd = 0x0001, // mvdhidden_usercmd_weapons = 0x0002, // mvdhidden_demoinfo = 0x0003, // mvdhidden_commentary_track = 0x0004, // [todo... ?] mvdhidden_commentary_data = 0x0005, // [todo... format-specific] mvdhidden_commentary_text_segment = 0x0006, // [todo... ] mvdhidden_dmgdone = 0x0007, // mvdhidden_usercmd_weapons_ss = 0x0008, // (same format as mvdhidden_usercmd_weapons) mvdhidden_usercmd_weapon_instruction = 0x0009, // mvdhidden_paused_duration = 0x000A, // ... actual time elapsed, not gametime (can be used to keep stream running) ... expected to be QTV only mvdhidden_extended = 0xFFFF // doubt we'll ever get here: read next short... }; #define sizeof_mvdhidden_block_header_t_usercmd (1 + 1 + 1 + 3 * 4 + 3 * 2 + 1 + 1) #define sizeof_mvdhidden_usercmd_weapon_instruction (1 + 1 + 4 + 4 + 10) typedef struct { int length; // this is the number of bytes in the packet, not including this header unsigned short type_id; // If 0xFFFF, read again to extend range } mvdhidden_block_header_t; #define sizeof_mvdhidden_block_header_t_range0 (4 + 2) typedef struct { byte playernum; byte players; unsigned int incoming_seq; float server_time; float target_time; } mvdhidden_antilag_position_header_t; #define sizeof_mvdhidden_antilag_position_header_t (1 + 1 + 4 + 4 + 4) typedef struct { float clientpos[3]; float pos[3]; byte playernum; byte msec; byte predmodel; } mvdhidden_antilag_position_t; #define sizeof_mvdhidden_antilag_position_t (12 + 12 + 1 + 1 + 1) #define MVDHIDDEN_DMGDONE_SPLASHDAMAGE (1 << 15) // mvdhidden_usercmd_weapon_instruction #define MVDHIDDEN_SSWEAPON_PENDING 1 #define MVDHIDDEN_SSWEAPON_HIDE_AXE 2 #define MVDHIDDEN_SSWEAPON_HIDE_SG 4 #define MVDHIDDEN_SSWEAPON_HIDEONDEATH 8 #define MVDHIDDEN_SSWEAPON_WASFIRING 16 #define MVDHIDDEN_SSWEAPON_ENABLED 32 #define MVDHIDDEN_SSWEAPON_FORGETORDER 64 #endif /* !__PROTOCOL_H__ */ mvdsv-0.35/src/q_platform.h000066400000000000000000000231031427146041000157020ustar00rootroot00000000000000/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code 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. Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #ifndef __Q_PLATFORM_H #define __Q_PLATFORM_H // this is for determining if we have an asm version of a C function #define idx64 0 #ifdef Q3_VM #define idx386 0 #define idppc 0 #define idppc_altivec 0 #define idsparc 0 #else #if (defined _M_IX86 || defined __i386__) && !defined(C_ONLY) #define idx386 1 #else #define idx386 0 #endif #if (defined(powerc) || defined(powerpc) || defined(ppc) || \ defined(__ppc) || defined(__ppc__)) && !defined(C_ONLY) #define idppc 1 #if defined(__VEC__) #define idppc_altivec 1 #ifdef MACOS_X // Apple's GCC does this differently than the FSF. #define VECCONST_UINT8(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \ (vector unsigned char) (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) #else #define VECCONST_UINT8(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \ (vector unsigned char) {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p} #endif #else #define idppc_altivec 0 #endif #else #define idppc 0 #define idppc_altivec 0 #endif #if defined(__sparc__) && !defined(C_ONLY) #define idsparc 1 #else #define idsparc 0 #endif #endif #ifndef __ASM_I386__ // don't include the C bits if included from qasm.h // for windows fastcall option #define QDECL //================================================================= WIN64/32 === #if defined(_WIN64) || defined(__WIN64__) #undef idx64 #define idx64 1 #undef QDECL #define QDECL __cdecl #if defined( _MSC_VER ) #define OS_STRING "win_msvc64" #elif defined __MINGW64__ #define OS_STRING "win_mingw64" #else #define OS_STRING "win64" #endif #define ID_INLINE static __inline #define PATH_SEPARATOR "\\" #if defined( __WIN64__ ) #define ARCH_STRING "x86_64" #elif defined _M_ALPHA #define ARCH_STRING "AXP" #endif #define Q3_LITTLE_ENDIAN #define DLEXT "dll" #elif defined(_WIN32) || defined(__WIN32__) #undef QDECL #define QDECL __cdecl #if defined( _MSC_VER ) #define OS_STRING "win_msvc" #elif defined __MINGW32__ #define OS_STRING "win_mingw" #endif #define ID_INLINE static __inline #define PATH_SEPARATOR "\\" #if defined( _M_IX86 ) || defined( __i386__ ) #define ARCH_STRING "x86" #elif defined _M_ALPHA #define ARCH_STRING "AXP" #endif #define Q3_LITTLE_ENDIAN #define DLEXT "dll" #endif //============================================================== MAC OS X === #if defined(MACOS_X) || defined(__APPLE_CC__) // make sure this is defined, just for sanity's sake... #ifndef MACOS_X #define MACOS_X #endif #define OS_STRING "macosx" #define ID_INLINE static inline #define PATH_SEPARATOR "/" #ifdef __ppc__ #define ARCH_STRING "ppc" #define Q3_BIG_ENDIAN #elif defined __i386__ #define ARCH_STRING "i386" #define Q3_LITTLE_ENDIAN #elif defined __x86_64__ #undef idx64 #define idx64 1 #define ARCH_STRING "x86_64" #define Q3_LITTLE_ENDIAN #endif #define DLEXT "dylib" #endif //================================================================= LINUX === #if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(ANDROID) || defined(__ANDROID__) #include #if defined(ANDROID) || defined(__ANDROID__) #define OS_STRING "android" #elif defined(__linux__) #define OS_STRING "linux" #else #define OS_STRING "kFreeBSD" #endif #define ID_INLINE static inline #define PATH_SEPARATOR "/" #if defined __i386__ #define ARCH_STRING "i386" #elif defined __x86_64__ #undef idx64 #define idx64 1 #define ARCH_STRING "x86_64" #elif defined __powerpc64__ #define ARCH_STRING "ppc64" #elif defined __powerpc__ #define ARCH_STRING "ppc" #elif defined __s390__ #define ARCH_STRING "s390" #elif defined __s390x__ #define ARCH_STRING "s390x" #elif defined __ia64__ #define ARCH_STRING "ia64" #elif defined __alpha__ #define ARCH_STRING "alpha" #elif defined __sparc__ #define ARCH_STRING "sparc" #elif defined __arm__ #define ARCH_STRING "arm" #elif defined __cris__ #define ARCH_STRING "cris" #elif defined __hppa__ #define ARCH_STRING "hppa" #elif defined __mips__ #define ARCH_STRING "mips" #elif defined __sh__ #define ARCH_STRING "sh" #endif #if __FLOAT_WORD_ORDER == __BIG_ENDIAN #define Q3_BIG_ENDIAN #else #define Q3_LITTLE_ENDIAN #endif #define DLEXT "so" #endif //=================================================================== BSD === #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) #include #include #ifndef __BSD__ #define __BSD__ #endif #if defined(__FreeBSD__) #define OS_STRING "freebsd" #elif defined(__OpenBSD__) #define OS_STRING "openbsd" #elif defined(__NetBSD__) #define OS_STRING "netbsd" #endif #define ID_INLINE static inline #define PATH_SEPARATOR "/" #ifdef __i386__ #define ARCH_STRING "i386" #elif defined __amd64__ #undef idx64 #define idx64 1 #define ARCH_STRING "amd64" #elif defined __axp__ #define ARCH_STRING "alpha" #endif #if BYTE_ORDER == BIG_ENDIAN #define Q3_BIG_ENDIAN #else #define Q3_LITTLE_ENDIAN #endif #define DLEXT "so" #endif //================================================================= SUNOS === #ifdef __sun #include #include #define OS_STRING "solaris" #define ID_INLINE static inline #define PATH_SEPARATOR "/" #ifdef __i386__ #define ARCH_STRING "i386" #elif defined __sparc #define ARCH_STRING "sparc" #endif #if defined( _BIG_ENDIAN ) #define Q3_BIG_ENDIAN #elif defined( _LITTLE_ENDIAN ) #define Q3_LITTLE_ENDIAN #endif #define DLEXT "so" #endif //================================================================== IRIX === #ifdef __sgi #define OS_STRING "irix" #define ID_INLINE static __inline #define PATH_SEPARATOR "/" #define ARCH_STRING "mips" #define Q3_BIG_ENDIAN // SGI's MIPS are always big endian #define DLEXT "so" #endif //=============================================================== MORPHOS === #ifdef __MORPHOS__ #define OS_STRING "morphos" #define ID_INLINE static inline #define PATH_SEPARATOR "/" #define ARCH_STRING "ppc" #define Q3_BIG_ENDIAN #define DLEXT "so" #endif #ifdef __CYGWIN__ #define OS_STRING "cygwin" #define ID_INLINE static inline #define PATH_SEPARATOR "/" #define ARCH_STRING "x86" #define Q3_LITTLE_ENDIAN #define DLEXT "dll" #endif #ifdef __DJGPP__ #define OS_STRING "msdos" #define ID_INLINE static inline #define PATH_SEPARATOR "/" #define ARCH_STRING "dos" #define Q3_LITTLE_ENDIAN #define DLEXT "dll" #endif #ifdef FTE_TARGET_WEB #define OS_STRING "emscripten" #define ID_INLINE static inline #define PATH_SEPARATOR "/" #define ARCH_STRING "web" #define Q3_LITTLE_ENDIAN #define DLEXT "so" #endif #ifdef NACL #define OS_STRING "nacl" #define ID_INLINE static inline #define PATH_SEPARATOR "/" #define ARCH_STRING "web" #define Q3_LITTLE_ENDIAN #define DLEXT "so" #endif //================================================================== Q3VM === #ifdef Q3_VM #define OS_STRING "q3vm" #define ID_INLINE static #define PATH_SEPARATOR "/" #define ARCH_STRING "bytecode" #define DLEXT "qvm" #endif //=========================================================================== //catch missing defines in above blocks #if !defined( OS_STRING ) #define ARCH_STRING "unknown" //#error "Operating system not supported" #endif #if !defined( ARCH_STRING ) #define ARCH_STRING "unk" //#error "Architecture not supported" #endif #ifndef ID_INLINE #define ID_INLINE static //#error "ID_INLINE not defined" #endif #ifndef PATH_SEPARATOR #define PATH_SEPARATOR "/" //#error "PATH_SEPARATOR not defined" #endif #ifndef DLEXT #define DLEXT "so" //#error "DLEXT not defined" #endif /* //endianness short ShortSwap (short l); int LongSwap (int l); float FloatSwap (const float *f); #if defined( Q3_BIG_ENDIAN ) && defined( Q3_LITTLE_ENDIAN ) #error "Endianness defined as both big and little" #elif defined( Q3_BIG_ENDIAN ) #define LittleShort(x) ShortSwap(x) #define LittleLong(x) LongSwap(x) #define LittleFloat(x) FloatSwap(&x) #define BigShort #define BigLong #define BigFloat #elif defined( Q3_LITTLE_ENDIAN ) #define LittleShort #define LittleLong #define LittleFloat #define BigShort(x) ShortSwap(x) #define BigLong(x) LongSwap(x) #define BigFloat(x) FloatSwap(&x) #elif defined( Q3_VM ) #define LittleShort #define LittleLong #define LittleFloat #define BigShort #define BigLong #define BigFloat #else #error "Endianness not defined" #endif */ //platform string #ifdef NDEBUG #define PLATFORM_STRING OS_STRING "-" ARCH_STRING #else #define PLATFORM_STRING OS_STRING "-" ARCH_STRING "-debug" #endif #endif #endif mvdsv-0.35/src/quakeasm.h000066400000000000000000000017121427146041000153470ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // // quakeasm.h: general asm header file // #ifndef __QUAKEASM_H__ #define __QUAKEASM_H__ // Disallow assembler for 64-bit builds #if defined(_WIN32) && defined(_WIN64) && defined(id386) #undef id386 #endif #endif /* !__QUAKEASM_H__ */ mvdsv-0.35/src/qwcl2.ico000066400000000000000000000020661427146041000151160ustar00rootroot00000000000000(& N( p?pppsx7w??? ( @?pwpp8p0ps70pwppppppsxx?? 0py???mvdsv-0.35/src/qwsvdef.h000066400000000000000000000051611427146041000152210ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // qwsvdef.h -- primary header for server #ifndef __QWSVDEF_H__ #define __QWSVDEF_H__ //for msvc #pragma message lines #define MY_STRINGIFY2(s) #s #define MY_STRINGIFY(s) MY_STRINGIFY2(s) #if defined(_MSC_VER) #define MSVC_LINE __FILE__"("MY_STRINGIFY(__LINE__)") : warning : " #define msg(s) message(MSVC_LINE s) #elif __GNUC__ >=4 #define msg(s) message(s) #else #define msg(...) #endif //define PARANOID // speed sapping error checking #ifdef _MSC_VER #define _CRT_SECURE_NO_DEPRECATE // don't bitch about strncat etc #pragma warning( disable : 4244 4267 4127 4201 4214 4514 4305 4115 4018 4996) #endif #include #include #include #include #include #include #include #include #include #include #include #include "quakeasm.h" #include "bothdefs.h" #include "mathlib.h" #include "zone.h" #include "cvar.h" #include "cmd.h" #include "hash.h" #include "protocol.h" #include "common.h" #include "net.h" #include "sys.h" #include "fs.h" #include "vfs.h" #include "cmodel.h" #include "crc.h" #include "sha1.h" #include "sha3.h" #include "server.h" #include "world.h" #include "pmove.h" #include "log.h" #include "version.h" #ifndef PCRE_STATIC #define PCRE_STATIC #endif #include "pcre/pcre.h" //============================================================================= extern cvar_t sys_nostdout; extern cvar_t developer; extern qbool host_initialized; extern qbool host_everything_loaded; extern double curtime; // not bounded or scaled, shared by local client and server void SV_Error (char *error, ...); void SV_Init (void); void SV_Shutdown (char *finalmsg); #define Host_Error SV_Error // ezquake compatibility void Host_Init (int argc, char **argv, int default_memsize); void Host_ClearMemory (void); void Con_Printf (char *fmt, ...); void Con_DPrintf (char *fmt, ...); #endif /* !__QWSVDEF_H__ */ mvdsv-0.35/src/resource.h000066400000000000000000000040601427146041000153660ustar00rootroot00000000000000/* 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. */ #ifndef __RESOURCE_H__ #define __RESOURCE_H__ //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by winquake.rc // #define IDS_STRING1 1 #define IDI_ICON2 1 #define IDD_DIALOG1 108 #define IDD_PROGRESS 109 #define IDB_QWBITMAP 112 #define IDB_ZQBITMAP 115 #define IDB_BITMAP1 117 #define IDD_DIALOG2 118 #define IDR_MENU1 121 #define IDC_PROGRESS 1000 #define IDC_CLEAR 1001 #define IDC_QUIT 1002 #define IDC_EDIT1 1003 #define IDC_EDIT2 1004 #define IDC_OK 1005 #define IDC_RESTORE 1006 #define ID_MENU 40003 #define ID_MENU_RESTORE 40004 #define ID_MENU_ABOUT 40005 #define ID_MENU_QUIT 40006 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 #define _APS_NEXT_RESOURCE_VALUE 122 #define _APS_NEXT_COMMAND_VALUE 40007 #define _APS_NEXT_CONTROL_VALUE 1002 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif #endif /* !__RESOURCE_H__ */ mvdsv-0.35/src/server.h000066400000000000000000001005631427146041000150520ustar00rootroot00000000000000/* 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. */ // server.h #ifndef __SERVER_H__ #define __SERVER_H__ #include "progs.h" #ifdef USE_PR2 #include "vm.h" #include "pr2.h" #include "g_public.h" #endif #ifndef SERVERONLY #include "qtv.h" #endif #define CHAT_ICON_EXPERIMENTAL 1 #define MAX_MASTERS 8 // max recipients for heartbeat packets #define MAX_SIGNON_BUFFERS 16 // sv_specprint stuff #define SPECPRINT_CENTERPRINT 0x1 #define SPECPRINT_SPRINT 0x2 #define SPECPRINT_STUFFCMD 0x4 typedef enum { ss_dead, // no map loaded ss_loading, // spawning level edicts ss_active // actively running } server_state_t; // some qc commands are only valid before the server has finished // initializing (precache commands, static sounds / objects, etc) typedef struct packet_s { double time; sizebuf_t msg; byte buf[MSG_BUF_SIZE]; // ?MAX_MSGLEN? struct packet_s *next; } packet_t; #define MAX_DELAYED_PACKETS 1024 // maxclients 32 * 77fps * max minping 0.3 = 739.2 #define MAP_NAME_LEN 64 typedef struct { server_state_t state; // precache commands are only valid during load double time; double old_time; // bumped by SV_Physics double old_bot_time; // bumped by SV_RunBots double physicstime; // last time physics was run int lastcheck; // used by PF_checkclient double lastchecktime; // for monster ai qbool paused; // are we paused? double pausedsince; // Sys_DoubleTime() when pause started qbool loadgame; // handle connections specially //check player/eyes models for hacks unsigned model_player_checksum; unsigned model_newplayer_checksum; unsigned eyes_player_checksum; char mapname[MAP_NAME_LEN]; // map name char modelname[MAX_QPATH]; // maps/.bsp, for model_precache[0] unsigned map_checksum; unsigned map_checksum2; cmodel_t *worldmodel; char *model_precache[MAX_MODELS]; // NULL terminated char *vw_model_name[MAX_VWEP_MODELS]; // NULL terminated char *sound_precache[MAX_SOUNDS]; // NULL terminated char *lightstyles[MAX_LIGHTSTYLES]; cmodel_t *models[MAX_MODELS]; int num_edicts; // increases towards MAX_EDICTS int num_baseline_edicts;// number of entities that have baselines edict_t edicts[MAX_EDICTS]; entvars_t *game_edicts; // can NOT be array indexed, because entvars_t is variable sized int max_edicts; // might not MAX_EDICTS if mod allocates memory byte *pvs, *phs; // fully expanded and decompressed // added to every client's unreliable buffer each frame, then cleared sizebuf_t datagram; byte datagram_buf[MAX_DATAGRAM]; // added to every client's reliable buffer each frame, then cleared sizebuf_t reliable_datagram; byte reliable_datagram_buf[MAX_MSGLEN]; // the multicast buffer is used to send a message to a set of clients sizebuf_t multicast; byte multicast_buf[MAX_MSGLEN]; // the signon buffer will be sent to each client as they connect // includes the entity baselines, the static entities, etc // large levels will have >MAX_DATAGRAM sized signons, so // multiple signon messages are kept sizebuf_t signon; unsigned int num_signon_buffers; int signon_buffer_size[MAX_SIGNON_BUFFERS]; byte signon_buffers[MAX_SIGNON_BUFFERS][MAX_DATAGRAM]; qbool mvdrecording; entity_state_t static_entities[512]; int static_entity_count; } server_t; #define NUM_SPAWN_PARMS 16 // { sv_antilag related typedef struct { qbool present; vec3_t laggedpos; } laggedentinfo_t; // } typedef enum { cs_free, // can be reused for a new connection cs_zombie, // client has been disconnected, but don't reuse // connection for a couple seconds cs_preconnected, // has been assigned, but login/realip not settled yet cs_connected, // has been assigned to a client_t, but not in game yet cs_spawned // client is fully in game } sv_client_state_t; // FIXME typedef struct { // received from client // reply double senttime; float ping_time; // { sv_antilag double sv_time; // } packet_entities_t entities; } client_frame_t; typedef struct { double localtime; vec3_t origin; } antilag_position_t; #define MAX_ANTILAG_POSITIONS 128 #define MAX_BACK_BUFFERS 128 #define MAX_STUFFTEXT 256 #define CLIENT_LOGIN_LEN 16 #define CLIENT_NAME_LEN 32 #define LOGIN_CHALLENGE_LENGTH 128 #define LOGIN_FLAG_LENGTH 8 #define LOGIN_MIN_RETRY_TIME 5 // 1 login attempt per x seconds #ifdef MVD_PEXT1_SERVERSIDEWEAPON #define MAX_WEAPONSWITCH_OPTIONS 10 #endif typedef struct client_s { sv_client_state_t state; int extensions; // what ZQuake extensions the client supports int spectator; // non-interactive int vip; qbool sendinfo; // at end of frame, send info to all // this prevents malicious multiple broadcasts float lastnametime; // time of last name change int lastnamecount; // time of last name change unsigned checksum; // checksum for calcs qbool drop; // lose this guy next opportunity int lossage; // loss percentage int userid; // identifying number ctxinfo_t _userinfo_ctx_; // infostring ctxinfo_t _userinfoshort_ctx_; // infostring antilag_position_t antilag_positions[MAX_ANTILAG_POSITIONS]; int antilag_position_next; usercmd_t lastcmd; // for filling in big drops and partial predictions double localtime; // of last message qbool jump_held; float maxspeed; // localized maxspeed float entgravity; // localized ent gravity edict_t *edict; // EDICT_NUM(clientnum+1) #ifdef USE_PR2 int isBot; usercmd_t botcmd; // bot movment #endif char name[CLIENT_NAME_LEN]; // for printing to other people char team[CLIENT_NAME_LEN]; // extracted from userinfo int messagelevel; // for filtering printed messages // the datagram is written to after every frame, but only cleared // when it is sent out to the client. overflow is tolerated. sizebuf_t datagram; byte datagram_buf[MAX_DATAGRAM]; // back buffers for client reliable data sizebuf_t backbuf; int num_backbuf; int backbuf_size[MAX_BACK_BUFFERS]; byte backbuf_data[MAX_BACK_BUFFERS][MAX_MSGLEN]; char stufftext_buf[MAX_STUFFTEXT]; // Use SV_ClientConnectedTime & SV_ClientGameTime instead double connection_started_realtime; // or time of disconnect for zombies double connection_started_curtime; // like connection_started but curtime (not affected by pause) qbool send_message; // set on frames a datagram arrived on // { sv_antilag related laggedentinfo_t laggedents[MAX_CLIENTS]; unsigned int laggedents_count; float laggedents_frac; float laggedents_time; // } // spawn parms are carried from level to level float spawn_parms[NUM_SPAWN_PARMS]; // client known data for deltas int old_frags; int stats[MAX_CL_STATS]; double lastservertimeupdate; // last realtime we sent STAT_TIME to the client client_frame_t frames[UPDATE_BACKUP]; // updates can be deltad from here vfsfile_t *download; // file being downloaded int dupe; // duplicate packets requested #ifdef PROTOCOL_VERSION_FTE #ifdef FTE_PEXT_CHUNKEDDOWNLOADS int download_chunks_perframe; #endif #endif int downloadsize; // total bytes int downloadcount; // bytes sent // demo download list for internal cmd dl function // Added by VVD { int demonum[MAX_ARGS]; qbool demolist; // } Added by VVD int spec_track; // entnum of player tracking double whensaid[10]; // JACK: For floodprots int whensaidhead; // Head value for floodprots double lockedtill; FILE *upload; char uploadfn[MAX_QPATH]; netadr_t snap_from; qbool remote_snap; char login[CLIENT_LOGIN_LEN]; char login_alias[CLIENT_NAME_LEN]; char login_flag[LOGIN_FLAG_LENGTH]; char login_confirmation[LOGIN_CHALLENGE_LENGTH]; char login_challenge[LOGIN_CHALLENGE_LENGTH]; int logged; qbool logged_in_via_web; double login_request_time; int spawncount; // for tracking map changes during downloading //bliP: additional -> int file_percent; qbool special; int logincount; float lasttoptime; // time of last topcolor change int lasttopcount; // count of last topcolor change int spec_print; double cuff_time; //bliP: 24/9 anti speed -> int msecs; double last_check; //<- //<- float lastuserinfotime; // time of last userinfo change int lastuserinfocount; // count of last userinfo change #ifdef PROTOCOL_VERSION_FTE unsigned int fteprotocolextensions; #endif // PROTOCOL_VERSION_FTE #ifdef PROTOCOL_VERSION_FTE2 unsigned int fteprotocolextensions2; #endif // PROTOCOL_VERSION_FTE2 #ifdef PROTOCOL_VERSION_MVD1 unsigned int mvdprotocolextensions1; #endif #ifdef FTE_PEXT2_VOICECHAT unsigned int voice_read; /*place in ring*/ unsigned char voice_mute[MAX_CLIENTS/8]; qbool voice_active; enum { /*note - when recording an mvd, only 'all' will be received by non-spectating viewers. all other chat will only be heard when spectating the receiver(or sender) of said chat*/ /*should we add one to respond to the last speaker? or should that be an automagic +voip_reply instead?*/ VT_TEAM, VT_ALL, VT_NONMUTED, /*cheap, but allows custom private channels with no external pesters*/ VT_PLAYERSLOT0 /*player0+...*/ } voice_target; #endif //===== NETWORK ============ qbool process_pext; // true if we wait for reply from client on "cmd pext" command. int chokecount; int delta_sequence; // -1 = no compression netchan_t netchan; netadr_t realip; // client's ip, not latest proxy's int realip_num; // random value int realip_count; int rip_vip; double delay; double disable_updates_stop; // Vladis qbool maxping_met; // set if user meets maxping requirements packet_t *packets, *last_packet; #ifdef MVD_PEXT1_HIGHLAGTELEPORT // lagged-teleport extension qbool lastteleport_teleport; // true if teleport, otherwise it was spawn int lastteleport_outgoingseq; // outgoing sequence# when the player teleported int lastteleport_incomingseq; // incoming sequence# when the player teleported float lastteleport_teleportyaw; // new yaw angle, post-teleport #endif #ifdef MVD_PEXT1_SERVERSIDEWEAPON // server-side weapons extension int weaponswitch_sequence_set; // need to remember what packet current choices were sent in for forgetorder qbool weaponswitch_pending; qbool weaponswitch_hide; // automatically flick back when not firing qbool weaponswitch_hide_on_death;// switch back to 2 1 when dying qbool weaponswitch_wasfiring; // fire pressed on previous frame (will only hide if so) qbool weaponswitch_enabled; // allow user to disable while connected int weaponswitch_mode; // user preference qbool weaponswitch_forgetorder; // if set, decide best weapon immediately and don't rank on fire byte weaponswitch_priority[MAX_WEAPONSWITCH_OPTIONS]; #endif qbool mvd_write_usercmds; } client_t; // a client can leave the server in one of four ways: // dropping properly by quiting or disconnecting // timing out if no valid messages are received for timeout.value seconds // getting kicked off by the server operator // a program error, like an overflowed reliable buffer typedef struct { int parsecount; vec3_t origin; vec3_t angles; int weaponframe; int frame; int skinnum; int model; int effects; int flags; qbool fixangle; float sec; } demo_client_t; typedef struct { demo_client_t clients[MAX_CLIENTS]; double time; qbool paused; byte pause_duration; // { reset each time frame wroten with SV_MVDWritePackets() sizebuf_t _buf_; // !!! OUCH OUCH OUCH, 64 frames, so it about 2mb !!! // here data with mvd headers, so it up to 4 mvd msg with maximum size, however basically here alot of small msgs, // so this size pathetic byte _buf__data[(MAX_MVD_SIZE + 10) * 4]; int lastto; int lasttype; int lastsize; int lastsize_offset; // this is tricky // } } demo_frame_t; //qtv proxies are meant to send a small header now, bit like http //this header gives supported version numbers and stuff typedef struct mvdpendingdest_s { qbool error; //disables writers, quit ASAP. int socket; char inbuffer[2048]; char outbuffer[2048]; char challenge[64]; qbool hasauthed; int insize; int outsize; qbool must_be_qizmo_tcp_connect; // HACK, this stream should not be allowed but just checked ONLY AND ONLY for qizmo tcp connection double io_time; // when last IO occur on socket, so we can timeout this dest netadr_t na; struct mvdpendingdest_s *nextdest; } mvdpendingdest_t; typedef enum {DEST_NONE, DEST_FILE, DEST_BUFFEREDFILE, DEST_STREAM} desttype_t; #define MAX_PROXY_INBUFFER 4096 /* qqshka: too small??? */ typedef struct mvddest_s { qbool error; //disables writers, quit ASAP. desttype_t desttype; int socket; FILE *file; char name[MAX_QPATH]; char path[MAX_QPATH]; char *cache; int cacheused; int maxcachesize; unsigned int totalsize; // { used by QTV double io_time; // when last IO occur on socket, so we can timeout this dest int id; // dest id, used by QTV only netadr_t na; char inbuffer[MAX_PROXY_INBUFFER]; int inbuffersize; char qtvname[64]; qtvuser_t *qtvuserlist; char qtvaddress[128]; int qtvstreamid; // } struct mvddest_s *nextdest; } mvddest_t; typedef struct { sizebuf_t datagram; byte datagram_data[MAX_MVD_SIZE]; // data without mvd header double time; // sv.time double curtime; // curtime double pingtime; // compare to curtime // Something like time of last mvd message, so we can guess delta milliseconds for next message. // you better not relay on this variable... double prevtime; client_t recorder; qbool fixangle[MAX_CLIENTS]; int stats[MAX_CLIENTS][MAX_CL_STATS]; int parsecount; // current frame, to which we add demo data int lastwritten; // lastwriten frame demo_frame_t frames[UPDATE_BACKUP]; // here we store all previous frames demo_client_t clients[MAX_CLIENTS]; // we store here what we wrote last time so we can delta // ===================================== char mem_set_point; // fields below, like ->dest and ->pendingdest must not be memset to 0 // ===================================== struct mvddest_s *dest; struct mvdpendingdest_s *pendingdest; // last recorded demo's names for command "cmd dl . .." (maximum 15 dots) char *lastdemosname[16]; int lastdemospos; } demo_t; //============================================================================= #define STATFRAMES 100 typedef struct { double active; double idle; double demo; int count; int packets; double latched_active; double latched_idle; double latched_demo; int latched_packets; } svstats_t; // MAX_CHALLENGES is made large to prevent a denial // of service attack that could cycle all of them // out before legitimate users connected #define MAX_CHALLENGES 1024 typedef struct { netadr_t adr; int challenge; int time; } challenge_t; // TCPCONNECT --> typedef struct svtcpstream_s { int socketnum; // socket qbool waitingforprotocolconfirmation; // wait for "qizmo\n", first 6 bytes before confirming that is tcpconnection int inlen; // how much bytes we have in inbuffer char inbuffer[1500]; // recv buffer int outlen; // how much bytes we have in outbuffer char outbuffer[1500 * 5]; // send buffer qbool drop; // do we need drop that connection ASAP float timeouttime; // I/O timeout netadr_t remoteaddr; // peer remoter addr struct svtcpstream_s *next; // next tcpconnection in list } svtcpstream_t; // <-- TCPCONNECT typedef struct { int spawncount; // number of servers spawned since start, // used to check late spawns int lastuserid; // userid of last spawned client socket_t socketip; // main server UDP socket. // TCPCONNECT --> int sockettcp; // server TCP socket, used for QTV/TCPCONNECT. svtcpstream_t * tcpstreams; // <-- TCPCONNECT client_t clients[MAX_CLIENTS]; int serverflags; // episode completion information double last_heartbeat; int heartbeat_sequence; svstats_t stats; char info[MAX_SERVERINFO_STRING]; #ifdef PROTOCOL_VERSION_FTE unsigned int fteprotocolextensions; #endif // PROTOCOL_VERSION_FTE #ifdef PROTOCOL_VERSION_FTE2 unsigned int fteprotocolextensions2; #endif // PROTOCOL_VERSION_FTE2 #ifdef PROTOCOL_VERSION_MVD1 unsigned int mvdprotocolextension1; #endif // log messages are used so that fraglog processes can get stats int logsequence; // the message currently being filled double logtime; // time of last swap sizebuf_t log[2]; byte log_buf[2][MAX_DATAGRAM]; challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting packet_t *free_packets; } server_static_t; //============================================================================= // edict->movetype values #define MOVETYPE_NONE 0 // never moves #define MOVETYPE_ANGLENOCLIP 1 #define MOVETYPE_ANGLECLIP 2 #define MOVETYPE_WALK 3 // gravity #define MOVETYPE_STEP 4 // gravity, special edge handling #define MOVETYPE_FLY 5 #define MOVETYPE_TOSS 6 // gravity #define MOVETYPE_PUSH 7 // no clip to world, push and crush #define MOVETYPE_NOCLIP 8 #define MOVETYPE_FLYMISSILE 9 // extra size to monsters #define MOVETYPE_BOUNCE 10 #define MOVETYPE_LOCK 15 // server controls view angles // edict->solid values #define SOLID_NOT 0 // no interaction with other objects #define SOLID_TRIGGER 1 // touch on edge, but not blocking #define SOLID_BBOX 2 // touch on edge, block #define SOLID_SLIDEBOX 3 // touch on edge, but not an onground #define SOLID_BSP 4 // bsp clip, touch on edge, block // edict->deadflag values #define DAMAGE_NO 0 #define DAMAGE_YES 1 #define DAMAGE_AIM 2 // edict->flags #define FL_FLY 1 #define FL_SWIM 2 #define FL_GLIMPSE 4 #define FL_CLIENT 8 #define FL_INWATER 16 #define FL_MONSTER 32 #define FL_GODMODE 64 #define FL_NOTARGET 128 #define FL_ITEM 256 #define FL_ONGROUND 512 #define FL_PARTIALGROUND 1024 // not all corners are valid #define FL_WATERJUMP 2048 // player jumping out of water // { sv_antilag #define FL_LAGGEDMOVE (1<<16) // } #define SPAWNFLAG_NOT_EASY 256 #define SPAWNFLAG_NOT_MEDIUM 512 #define SPAWNFLAG_NOT_HARD 1024 #define SPAWNFLAG_NOT_DEATHMATCH 2048 #define MULTICAST_ALL 0 #define MULTICAST_PHS 1 #define MULTICAST_PVS 2 #define MULTICAST_ALL_R 3 #define MULTICAST_PHS_R 4 #define MULTICAST_PVS_R 5 #define MULTICAST_KTX1_EXT 6 // Only send to those using ktx1 protocol extension (todo) #define MULTICAST_MVD_HIDDEN 7 // Insert into MVD stream only, as dem_multiple(0) #define MAX_LOCALINFOS 10000 // maps in localinfo { #define LOCALINFO_MAPS_LIST_START 1000 #define LOCALINFO_MAPS_LIST_END 4999 // } #define MAX_REDIRECTMESSAGES 128 #define OUTPUTBUF_SIZE 8000 // { server flags // force player enter server as spectator if all players's slots are busy and // if there are empty slots for spectators and sv_forcespec_onfull == 2 #define SVF_SPEC_ONFULL (1<<0) // do not join server as spectator if server full and sv_forcespec_onfull == 1 #define SVF_NO_SPEC_ONFULL (1<<1) // } server flags //============================================================================ extern cvar_t sv_paused; // 1 - normal, 2 - auto (single player), 3 - both extern cvar_t sv_maxspeed; extern cvar_t sv_mintic, sv_maxtic, sv_maxfps; extern cvar_t sv_antilag, sv_antilag_no_pred, sv_antilag_projectiles; extern int current_skill; extern cvar_t spawn; extern cvar_t teamplay; extern cvar_t serverdemo; extern cvar_t deathmatch; extern cvar_t fraglimit; extern cvar_t timelimit; extern cvar_t skill; extern cvar_t coop; extern cvar_t maxclients; extern cvar_t sv_specprint; //bliP: spectator print extern server_static_t svs; // persistant server info extern server_t sv; // local server extern demo_t demo; // server demo struct extern client_t *sv_client; extern edict_t *sv_player; #define MODEL_NAME_LEN 5 extern char localmodels[MAX_MODELS][MODEL_NAME_LEN]; // inline model names for precache //extern char localinfo[MAX_LOCALINFO_STRING+1]; extern ctxinfo_t _localinfo_; extern qbool sv_error; extern char master_rcon_password[128]; //=========================================================== // // sv_main.c // extern double realtime; // not bounded in any way, changed at start of every frame, never reset typedef enum { ipft_ban, ipft_safe } ipfiltertype_t; typedef struct { unsigned mask; unsigned compare; int level; double time; // for ban expiration ipfiltertype_t type; } ipfilter_t; //bliP: penalty filters -> typedef enum { ft_mute, ft_cuff } filtertype_t; typedef struct { byte ip[4]; double time; filtertype_t type; } penfilter_t; //<- void SV_Frame (double time); void SV_FinalMessage (const char *message); void SV_DropClient (client_t *drop); int SV_CalcPing (client_t *cl); void SV_FullClientUpdate (client_t *client, sizebuf_t *buf); void SV_FullClientUpdateToClient (client_t *client, client_t *cl); qbool SV_CheckBottom (edict_t *ent); qbool SV_movestep (edict_t *ent, vec3_t move, qbool relink); qbool SV_CloseEnough (edict_t *ent, edict_t *goal, float dist); qbool SV_StepDirection (edict_t *ent, float yaw, float dist); void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist); void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg); void SV_MoveToGoal (void); void SV_InitOperatorCommands (void); void SV_SendServerinfo (client_t *client); void SV_ExtractFromUserinfo (client_t *cl, qbool namechanged); int SV_BoundRate (qbool dl, int rate); typedef struct { int sec; int min; int hour; int day; int mon; int year; char str[128]; } date_t; void SV_TimeOfDay(date_t *date, char *timeformat); //bliP: init -> void SV_ListFiles_f (void); void SV_RemoveFile_f (void); void SV_RemoveDirectory_f (void); #define MAX_PENFILTERS 512 void SV_RemoveIPFilter (int i); //static qbool SV_IPCompare (byte *a, byte *b); //static void SV_IPCopy (byte *dest, byte *src); void SV_SavePenaltyFilter (client_t *cl, filtertype_t type, double pentime); double SV_RestorePenaltyFilter (client_t *cl, filtertype_t type); qbool SV_FilterPacket (void); void SV_SendBan (void); qbool GameStarted(void); //<- void SV_Script_f (void); int SV_GenerateUserID (void); // // sv_init.c // int SV_ModelIndex (char *name); void SV_FlushSignon (void); void SV_SpawnServer (char *server, qbool devmap, char* entityfile, qbool loading_savegame); // // sv_phys.c // void SV_ProgStartFrame (qbool isBotFrame); void SV_Physics (void); void SV_CheckVelocity (edict_t *ent); void SV_AddGravity (edict_t *ent, float scale); qbool SV_RunThink (edict_t *ent); void SV_Physics_Toss (edict_t *ent); void SV_RunNewmis (void); void SV_RunNQNewmis (void); void SV_Impact (edict_t *e1, edict_t *e2); void SV_SetMoveVars(void); #ifdef USE_PR2 void SV_RunBots(void); #endif // // sv_send.c // typedef enum {RD_NONE, RD_CLIENT, RD_PACKET, RD_MOD} redirect_t; void SV_BeginRedirect (redirect_t rd); void SV_EndRedirect (void); qbool SV_AddToRedirect(char *msg); void SV_Multicast(vec3_t origin, int to); void SV_MulticastEx(vec3_t origin, int to, const char *cl_reliable_key); void SV_StartParticle(vec3_t org, vec3_t dir, int color, int count, int replacement_te, int replacement_count); void SV_StartSound(edict_t *entity, int channel, char *sample, int volume, float attenuation); void SV_ClientPrintf(client_t *cl, int level, char *fmt, ...); void SV_ClientPrintf2(client_t *cl, int level, char *fmt, ...); void SV_BroadcastPrintf(int level, char *fmt, ...); #define BPRINT_IGNOREINDEMO (1<<0) // broad cast print will be not put in demo #define BPRINT_IGNORECLIENTS (1<<1) // broad cast print will not be seen by clients, but may be seen in demo #define BPRINT_QTVONLY (1<<2) // if broad cast print goes to demo, then it will be only qtv sream, but not file #define BPRINT_IGNORECONSOLE (1<<3) // broad cast print will not be put in server console void SV_BroadcastPrintfEx (int level, int flags, char *fmt, ...); void SV_BroadcastCommand (char *fmt, ...); void SV_SendClientMessages (void); void SV_SendDemoMessage(void); void SV_SendMessagesToAll (void); void SV_FindModelNumbers (void); // // sv_user.c // void SV_ExecuteClientMessage (client_t *cl); void SV_UserInit (void); void SV_TogglePause (const char *msg, int bit); void ProcessUserInfoChange (client_t* sv_client, const char* key, const char* old_value); void SV_RotateCmd(client_t* cl, usercmd_t* cmd); #ifdef FTE_PEXT2_VOICECHAT void SV_VoiceInitClient(client_t *client); void SV_VoiceSendPacket(client_t *client, sizebuf_t *buf); #endif // // sv_ccmds.c // void SV_Status_f (void); void SV_ServerinfoChanged (char *key, char *string); void SV_SendServerInfoChange (char *key, char *value); void SV_KickClient(client_t* client, const char* reason); // // sv_ents.c // void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg, qbool recorder); void SV_SetVisibleEntitiesForBot (client_t* client); // // sv_nchan.c // void ClientReliableCheckBlock(client_t *cl, int maxsize); void ClientReliable_FinishWrite(client_t *cl); void ClientReliableWrite_Begin(client_t *cl, int c, int maxsize); void ClientReliableWrite_Angle(client_t *cl, float f); void ClientReliableWrite_Angle16(client_t *cl, float f); void ClientReliableWrite_Byte(client_t *cl, int c); void ClientReliableWrite_Char(client_t *cl, int c); void ClientReliableWrite_Float(client_t *cl, float f); void ClientReliableWrite_Coord(client_t *cl, float f); void ClientReliableWrite_Long(client_t *cl, int c); void ClientReliableWrite_Short(client_t *cl, int c); void ClientReliableWrite_String(client_t *cl, char *s); void ClientReliableWrite_SZ(client_t *cl, void *data, int len); void SV_ClearReliable (client_t *cl); // clear cl->netchan.message and backbuf // // sv_demo.c // void MVD_MSG_WriteChar (const int c); void MVD_MSG_WriteByte (const int c); void MVD_MSG_WriteShort (const int c); void MVD_MSG_WriteLong (const int c); void MVD_MSG_WriteFloat (const float f); void MVD_MSG_WriteString (const char *s); void MVD_MSG_WriteCoord (const float f); void MVD_MSG_WriteAngle (const float f); void MVD_SZ_Write (const void *data, int length); qbool MVDWrite_Begin(byte type, int to, int size); qbool MVDWrite_HiddenBlockBegin(int length); qbool MVDWrite_HiddenBlock(const void* data, int length); void SV_MVD_Record_f (void); void SV_MVDEasyRecord_f (void); void SV_MVDStop (int reason, qbool mvdonly); void SV_MVDStop_f (void); qbool SV_MVDWritePackets (int num); void SV_MVD_SendInitialGamestate(mvddest_t *dest); qbool SV_MVD_Record (mvddest_t *dest, qbool mapchange); mvddest_t *DestByName (char *name); void DestClose (mvddest_t *d, qbool destroyfiles); int DemoWriteDest (void *data, int len, mvddest_t *d); extern demo_t demo; // server demo struct extern cvar_t sv_demoUseCache; extern cvar_t sv_demoCacheSize; extern cvar_t sv_demoMaxDirSize; extern cvar_t sv_demoClearOld; extern cvar_t sv_demoDir; extern cvar_t sv_demoDirAlt; extern cvar_t sv_demofps; extern cvar_t sv_demoPings; extern cvar_t sv_demoMaxSize; extern cvar_t sv_demoExtraNames; extern cvar_t sv_demoPrefix; extern cvar_t sv_demoSuffix; extern cvar_t sv_demotxt; extern cvar_t sv_onrecordfinish; extern cvar_t sv_ondemoremove; extern cvar_t sv_demoRegexp; extern cvar_t sv_silentrecord; void SV_MVDInit (void); char *SV_MVDNum(int num); // // sv_demo_misc.c // char *SV_PrintTeams (void); void Run_sv_demotxt_and_sv_onrecordfinish (const char *dest_name, const char *dest_path, qbool destroyfiles); qbool SV_DirSizeCheck (void); char *SV_CleanName (unsigned char *name); int Dem_CountPlayers (void); char *Dem_Team (int num); char *Dem_PlayerName (int num); char *Dem_PlayerNameTeam (char *t); int Dem_CountTeamPlayers (char *t); char *quote (char *str); void CleanName_Init (void); void SV_LastScores_f (void); void SV_DemoList_f (void); void SV_DemoListRegex_f (void); void SV_MVDRemove_f (void); void SV_MVDRemoveNum_f (void); void SV_MVDInfoAdd_f (void); void SV_MVDInfoRemove_f (void); void SV_MVDInfo_f (void); void SV_LastScores_f (void); char* SV_MVDName2Txt (const char *name); void SV_MVDEmbedInfo_f(void); // // sv_demo_qtv.c // extern cvar_t qtv_streamtimeout; void SV_MVDStream_Poll(void); void SV_MVDCloseStreams(void); void SV_QTV_Init(void); void DemoWriteQTV (sizebuf_t *msg); void QTVsv_FreeUserList(mvddest_t *d); void QTV_Streams_List (void); void QTV_Streams_UserList (void); const char* SV_MVDDemoName(void); // // sv_login.c // void SV_LoadAccounts(void); void SV_CreateAccount_f(void); void SV_RemoveAccount_f(void); void SV_ListAccount_f (void); void Login_Init (void); qbool SV_Login(client_t *cl); void SV_Logout(client_t *cl); void SV_ParseWebLogin(client_t* cl); void SV_ParseLogin(client_t *cl); void SV_LoginCheckTimeOut(client_t *cl); void SV_LoginWebCheck(client_t* cl); void SV_LoginWebFailed(client_t* cl); qbool SV_LoginRequired(client_t* cl); qbool SV_LoginBlockJoinRequest(client_t* cl); // sv_master.c void SV_SetMaster_f (void); void SV_Heartbeat_f (void); void Master_Shutdown (void); void Master_Heartbeat (void); // sv_save.c void SV_SaveGame_f (void); void SV_LoadGame_f (void); // void SV_WriteDelta(client_t* client, entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qbool force); qbool SV_SkipCommsBotMessage(client_t* client); // #ifdef SERVERONLY #include "central.h" #else extern qbool server_cfg_done; #endif // These functions tell us how much time has passed since the client connected // Sometimes this should be affected by pause (scoreboards) and sometimes not (spam, networking) // GameTime() stops while game is paused, Connected() continues as normal // Both return 0 if client hasn't connected yet double SV_ClientConnectedTime(client_t* client); // real-world time passed double SV_ClientGameTime(client_t* client); // affected by pause void SV_SetClientConnectionTime(client_t* client); #ifdef SERVERONLY // mvdsv not changed over to enums yet, which was more about documentation #define SV_CommandLineEnableCheats() (COM_CheckParm("-cheats")) #define SV_CommandLineEnableLocalCommand() (COM_CheckParm("-enablelocalcommand")) #define SV_CommandLineDemoCacheArgument() (COM_CheckParm("-democache")) #define SV_CommandLineProgTypeArgument() (COM_CheckParm("-progtype")) #define SV_CommandLineUseMinimumMemory() (COM_CheckParm("-minmemory")) #define SV_CommandLineHeapSizeMemoryKB() (COM_CheckParm("-heapsize")) #define SV_CommandLineHeapSizeMemoryMB() (COM_CheckParm("-mem")) #else #define SV_CommandLineEnableCheats() (COM_CheckParm(cmdline_param_server_enablecheats)) #define SV_CommandLineEnableLocalCommand() (COM_CheckParm(cmdline_param_server_enablelocalcommand)) #define SV_CommandLineDemoCacheArgument() (COM_CheckParm(cmdline_param_server_democache_kb)) #define SV_CommandLineProgTypeArgument() (COM_CheckParm(cmdline_param_server_progtype)) #define SV_CommandLineUseMinimumMemory() (COM_CheckParm(cmdline_param_host_memory_minimum)) #define SV_CommandLineHeapSizeMemoryKB() (COM_CheckParm(cmdline_param_host_memory_kb)) #define SV_CommandLineHeapSizeMemoryMB() (COM_CheckParm(cmdline_param_host_memory_mb)) #endif #endif /* !__SERVER_H__ */ mvdsv-0.35/src/sha1.c000066400000000000000000000125141427146041000143710ustar00rootroot00000000000000/* SHA-1 in C By Steve Reid 100% Public Domain Test Vectors (from FIPS PUB 180-1) "abc" A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 A million repetitions of "a" 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F */ /* #define SHA1HANDSOFF * Copies data before messing with it. */ #include "qwsvdef.h" /* Hash a single 512-bit block. This is the core of the algorithm. */ static void SHA1Transform (unsigned int state[5], unsigned char buffer[64]) { unsigned int a, b, c, d, e; typedef union { unsigned char c[64]; unsigned int l[16]; } CHAR64LONG16; CHAR64LONG16* block; #ifdef SHA1HANDSOFF static unsigned char workspace[64]; block = (CHAR64LONG16*)workspace; memcpy(block, buffer, 64); #else block = (CHAR64LONG16*)buffer; #endif /* Copy context->state[] to working vars */ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; /* 4 rounds of 20 operations each. Loop unrolled. */ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); /* Add the working vars back into context.state[] */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; /* Wipe variables */ a = b = c = d = e = 0; } /* SHA1Init - Initialize new context */ static void SHA1Init(SHA1_CTX* context) { /* SHA1 initialization constants */ context->state[0] = 0x67452301; context->state[1] = 0xEFCDAB89; context->state[2] = 0x98BADCFE; context->state[3] = 0x10325476; context->state[4] = 0xC3D2E1F0; context->count[0] = context->count[1] = 0; } /* Run your data through this. */ static void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int len) { unsigned int i, j; j = (context->count[0] >> 3) & 63; if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; context->count[1] += (len >> 29); if ((j + len) > 63) { memcpy(&context->buffer[j], data, (i = 64-j)); SHA1Transform(context->state, context->buffer); for ( ; i + 63 < len; i += 64) { SHA1Transform(context->state, &data[i]); } j = 0; } else i = 0; memcpy(&context->buffer[j], &data[i], len - i); } /* Add padding and return the message digest. */ static void SHA1Final(unsigned char digest[DIGEST_SIZE], SHA1_CTX* context) { unsigned int i, j; unsigned char finalcount[8]; for (i = 0; i < 8; i++) { finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ } SHA1Update(context, (unsigned char *)"\200", 1); while ((context->count[0] & 504) != 448) { SHA1Update(context, (unsigned char *)"\0", 1); } SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ for (i = 0; i < DIGEST_SIZE; i++) { digest[i] = (unsigned char) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); } /* Wipe variables */ i = j = 0; memset(context->buffer, 0, sizeof(context->buffer)); memset(context->state, 0, sizeof(context->state)); memset(context->count, 0, sizeof(context->count)); memset(&finalcount, 0, sizeof(finalcount)); #ifdef SHA1HANDSOFF /* make SHA1Transform overwrite it's own static vars */ SHA1Transform(context->state, context->buffer); #endif } //VVD: SHA1 crypt static char *bin2hex (unsigned char *d) { static char ret[DIGEST_SIZE * 2 + 1]; int i; for (i = 0; i < DIGEST_SIZE * 2; i += 2, d++) snprintf(ret + i, DIGEST_SIZE * 2 + 1 - i, "%02X", *d); return ret; } char *SHA1 (char *string) { SHA1_CTX context; unsigned char digest[DIGEST_SIZE]; SHA1Init(&context); SHA1Update(&context, (unsigned char*)string, strlen(string)); SHA1Final(digest, &context); return bin2hex(digest); } static SHA1_CTX context; void SHA1_Init (void) { SHA1Init(&context); } void SHA1_Update (char *string) { SHA1Update(&context, (unsigned char *)string, strlen((char*)string)); } char *SHA1_Final(void) { unsigned char digest[DIGEST_SIZE]; SHA1Final(digest, &context); return bin2hex(digest); } mvdsv-0.35/src/sha1.h000066400000000000000000000037171427146041000144030ustar00rootroot00000000000000/* SHA-1 in C By Steve Reid 100% Public Domain Test Vectors (from FIPS PUB 180-1) "abc" A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 A million repetitions of "a" 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F */ #ifndef __SHA1_H__ #define __SHA1_H__ /* #define SHA1HANDSOFF * Copies data before messing with it. */ typedef struct { unsigned int state[5]; unsigned int count[2]; unsigned char buffer[64]; } SHA1_CTX; #define DIGEST_SIZE 20 //void SHA1Transform(unsigned int state[5], unsigned char buffer[64]); //void (SHA1_CTX* context); //void (SHA1_CTX* context, unsigned char* data, unsigned int len); //void (unsigned char digest[DIGEST_SIZE], SHA1_CTX* context); #define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) /* blk0() and blk() perform the initial expand. */ /* I got the idea of expanding during the round function from SSLeay */ /* #ifdef __BIG_ENDIAN__Q__ #define blk0(i) block->l[i] #else #define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | \ (rol(block->l[i], 8) & 0x00FF00FF)) #endif */ #define blk0(i) (block->l[i] = BigLong(block->l[i])) #define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ ^block->l[(i+2)&15]^block->l[i&15],1)) /* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ #define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); #define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); #define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); #define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); #define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); //VVD: SHA1 crypt char *SHA1 (char *string); void SHA1_Init (void); void SHA1_Update (char* data); char *SHA1_Final (void); #endif /* !__SHA1_H__ */ mvdsv-0.35/src/sha3.c000066400000000000000000000250531427146041000143750ustar00rootroot00000000000000/* ------------------------------------------------------------------------- * Works when compiled for either 32-bit or 64-bit targets, optimized for * 64 bit. * * Canonical implementation of Init/Update/Finalize for SHA-3 byte input. * * SHA3-256, SHA3-384, SHA-512 are implemented. SHA-224 can easily be added. * * Based on code from http://keccak.noekeon.org/ . * * I place the code that I wrote into public domain, free to use. * * I would appreciate if you give credits to this work if you used it to * write or test * your code. * * Aug 2015. Andrey Jivsov. crypto@brainhub.org * ---------------------------------------------------------------------- */ #include #include #include #include "sha3.h" #define SHA3_ASSERT( x ) #define SHA3_TRACE( format, ...) #define SHA3_TRACE_BUF(format, buf, l) /* * This flag is used to configure "pure" Keccak, as opposed to NIST SHA3. */ #define SHA3_USE_KECCAK_FLAG 0x80000000 #define SHA3_CW(x) ((x) & (~SHA3_USE_KECCAK_FLAG)) #if defined(_MSC_VER) #define SHA3_CONST(x) x #else #define SHA3_CONST(x) x##L #endif #ifndef SHA3_ROTL64 #define SHA3_ROTL64(x, y) \ (((x) << (y)) | ((x) >> ((sizeof(uint64_t)*8) - (y)))) #endif static const uint64_t keccakf_rndc[24] = { SHA3_CONST(0x0000000000000001UL), SHA3_CONST(0x0000000000008082UL), SHA3_CONST(0x800000000000808aUL), SHA3_CONST(0x8000000080008000UL), SHA3_CONST(0x000000000000808bUL), SHA3_CONST(0x0000000080000001UL), SHA3_CONST(0x8000000080008081UL), SHA3_CONST(0x8000000000008009UL), SHA3_CONST(0x000000000000008aUL), SHA3_CONST(0x0000000000000088UL), SHA3_CONST(0x0000000080008009UL), SHA3_CONST(0x000000008000000aUL), SHA3_CONST(0x000000008000808bUL), SHA3_CONST(0x800000000000008bUL), SHA3_CONST(0x8000000000008089UL), SHA3_CONST(0x8000000000008003UL), SHA3_CONST(0x8000000000008002UL), SHA3_CONST(0x8000000000000080UL), SHA3_CONST(0x000000000000800aUL), SHA3_CONST(0x800000008000000aUL), SHA3_CONST(0x8000000080008081UL), SHA3_CONST(0x8000000000008080UL), SHA3_CONST(0x0000000080000001UL), SHA3_CONST(0x8000000080008008UL) }; static const unsigned keccakf_rotc[24] = { 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 }; static const unsigned keccakf_piln[24] = { 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1 }; /* generally called after SHA3_KECCAK_SPONGE_WORDS-ctx->capacityWords words * are XORed into the state s */ static void keccakf(uint64_t s[25]) { int i, j, round; uint64_t t, bc[5]; #define KECCAK_ROUNDS 24 for(round = 0; round < KECCAK_ROUNDS; round++) { /* Theta */ for(i = 0; i < 5; i++) bc[i] = s[i] ^ s[i + 5] ^ s[i + 10] ^ s[i + 15] ^ s[i + 20]; for(i = 0; i < 5; i++) { t = bc[(i + 4) % 5] ^ SHA3_ROTL64(bc[(i + 1) % 5], 1); for(j = 0; j < 25; j += 5) s[j + i] ^= t; } /* Rho Pi */ t = s[1]; for(i = 0; i < 24; i++) { j = keccakf_piln[i]; bc[0] = s[j]; s[j] = SHA3_ROTL64(t, keccakf_rotc[i]); t = bc[0]; } /* Chi */ for(j = 0; j < 25; j += 5) { for(i = 0; i < 5; i++) bc[i] = s[j + i]; for(i = 0; i < 5; i++) s[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5]; } /* Iota */ s[0] ^= keccakf_rndc[round]; } } /* *************************** Public Inteface ************************ */ /* For Init or Reset call these: */ sha3_return_t sha3_Init(void *priv, unsigned bitSize) { sha3_context *ctx = (sha3_context *) priv; if( bitSize != 256 && bitSize != 384 && bitSize != 512 ) return SHA3_RETURN_BAD_PARAMS; memset(ctx, 0, sizeof(*ctx)); ctx->capacityWords = 2 * bitSize / (8 * sizeof(uint64_t)); return SHA3_RETURN_OK; } void sha3_Init256(void *priv) { sha3_Init(priv, 256); } void sha3_Init384(void *priv) { sha3_Init(priv, 384); } void sha3_Init512(void *priv) { sha3_Init(priv, 512); } enum SHA3_FLAGS sha3_SetFlags(void *priv, enum SHA3_FLAGS flags) { sha3_context *ctx = (sha3_context *) priv; flags &= SHA3_FLAGS_KECCAK; ctx->capacityWords |= (flags == SHA3_FLAGS_KECCAK ? SHA3_USE_KECCAK_FLAG : 0); return flags; } void sha3_Update(void *priv, void const *bufIn, size_t len) { sha3_context *ctx = (sha3_context *) priv; /* 0...7 -- how much is needed to have a word */ unsigned old_tail = (8 - ctx->byteIndex) & 7; size_t words; unsigned tail; size_t i; const uint8_t *buf = bufIn; SHA3_TRACE_BUF("called to update with:", buf, len); SHA3_ASSERT(ctx->byteIndex < 8); SHA3_ASSERT(ctx->wordIndex < sizeof(ctx->u.s) / sizeof(ctx->u.s[0])); if(len < old_tail) { /* have no complete word or haven't started * the word yet */ SHA3_TRACE("because %d<%d, store it and return", (unsigned)len, (unsigned)old_tail); /* endian-independent code follows: */ while (len--) ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8); SHA3_ASSERT(ctx->byteIndex < 8); return; } if(old_tail) { /* will have one word to process */ SHA3_TRACE("completing one word with %d bytes", (unsigned)old_tail); /* endian-independent code follows: */ len -= old_tail; while (old_tail--) ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8); /* now ready to add saved to the sponge */ ctx->u.s[ctx->wordIndex] ^= ctx->saved; SHA3_ASSERT(ctx->byteIndex == 8); ctx->byteIndex = 0; ctx->saved = 0; if(++ctx->wordIndex == (SHA3_KECCAK_SPONGE_WORDS - SHA3_CW(ctx->capacityWords))) { keccakf(ctx->u.s); ctx->wordIndex = 0; } } /* now work in full words directly from input */ SHA3_ASSERT(ctx->byteIndex == 0); words = len / sizeof(uint64_t); tail = len - words * sizeof(uint64_t); SHA3_TRACE("have %d full words to process", (unsigned)words); for(i = 0; i < words; i++, buf += sizeof(uint64_t)) { const uint64_t t = (uint64_t) (buf[0]) | ((uint64_t) (buf[1]) << 8 * 1) | ((uint64_t) (buf[2]) << 8 * 2) | ((uint64_t) (buf[3]) << 8 * 3) | ((uint64_t) (buf[4]) << 8 * 4) | ((uint64_t) (buf[5]) << 8 * 5) | ((uint64_t) (buf[6]) << 8 * 6) | ((uint64_t) (buf[7]) << 8 * 7); #if defined(__x86_64__ ) || defined(__i386__) SHA3_ASSERT(memcmp(&t, buf, 8) == 0); #endif ctx->u.s[ctx->wordIndex] ^= t; if(++ctx->wordIndex == (SHA3_KECCAK_SPONGE_WORDS - SHA3_CW(ctx->capacityWords))) { keccakf(ctx->u.s); ctx->wordIndex = 0; } } SHA3_TRACE("have %d bytes left to process, save them", (unsigned)tail); /* finally, save the partial word */ SHA3_ASSERT(ctx->byteIndex == 0 && tail < 8); while (tail--) { SHA3_TRACE("Store byte %02x '%c'", *buf, *buf); ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8); } SHA3_ASSERT(ctx->byteIndex < 8); SHA3_TRACE("Have saved=0x%016" PRIx64 " at the end", ctx->saved); } /* This is simply the 'update' with the padding block. * The padding block is 0x01 || 0x00* || 0x80. First 0x01 and last 0x80 * bytes are always present, but they can be the same byte. */ void const * sha3_Finalize(void *priv) { sha3_context *ctx = (sha3_context *) priv; SHA3_TRACE("called with %d bytes in the buffer", ctx->byteIndex); /* Append 2-bit suffix 01, per SHA-3 spec. Instead of 1 for padding we * use 1<<2 below. The 0x02 below corresponds to the suffix 01. * Overall, we feed 0, then 1, and finally 1 to start padding. Without * M || 01, we would simply use 1 to start padding. */ uint64_t t; if( ctx->capacityWords & SHA3_USE_KECCAK_FLAG ) { /* Keccak version */ t = (uint64_t)(((uint64_t) 1) << (ctx->byteIndex * 8)); } else { /* SHA3 version */ t = (uint64_t)(((uint64_t)(0x02 | (1 << 2))) << ((ctx->byteIndex) * 8)); } ctx->u.s[ctx->wordIndex] ^= ctx->saved ^ t; ctx->u.s[SHA3_KECCAK_SPONGE_WORDS - SHA3_CW(ctx->capacityWords) - 1] ^= SHA3_CONST(0x8000000000000000UL); keccakf(ctx->u.s); /* Return first bytes of the ctx->s. This conversion is not needed for * little-endian platforms e.g. wrap with #if !defined(__BYTE_ORDER__) * || !defined(__ORDER_LITTLE_ENDIAN__) || __BYTE_ORDER__!=__ORDER_LITTLE_ENDIAN__ * ... the conversion below ... * #endif */ { unsigned i; for(i = 0; i < SHA3_KECCAK_SPONGE_WORDS; i++) { const unsigned t1 = (uint32_t) ctx->u.s[i]; const unsigned t2 = (uint32_t) ((ctx->u.s[i] >> 16) >> 16); ctx->u.sb[i * 8 + 0] = (uint8_t) (t1); ctx->u.sb[i * 8 + 1] = (uint8_t) (t1 >> 8); ctx->u.sb[i * 8 + 2] = (uint8_t) (t1 >> 16); ctx->u.sb[i * 8 + 3] = (uint8_t) (t1 >> 24); ctx->u.sb[i * 8 + 4] = (uint8_t) (t2); ctx->u.sb[i * 8 + 5] = (uint8_t) (t2 >> 8); ctx->u.sb[i * 8 + 6] = (uint8_t) (t2 >> 16); ctx->u.sb[i * 8 + 7] = (uint8_t) (t2 >> 24); } } SHA3_TRACE_BUF("Hash: (first 32 bytes)", ctx->u.sb, 256 / 8); return (ctx->u.sb); } sha3_return_t sha3_HashBuffer( unsigned bitSize, enum SHA3_FLAGS flags, const void *in, unsigned inBytes, void *out, unsigned outBytes ) { sha3_return_t err; sha3_context c; err = sha3_Init(&c, bitSize); if( err != SHA3_RETURN_OK ) return err; if( sha3_SetFlags(&c, flags) != flags ) { return SHA3_RETURN_BAD_PARAMS; } sha3_Update(&c, in, inBytes); const void *h = sha3_Finalize(&c); if(outBytes > bitSize/8) outBytes = bitSize/8; memcpy(out, h, outBytes); return SHA3_RETURN_OK; } //========================================================================= static void byte_to_hex(uint8_t b, char s[3]) { unsigned i = 1; s[0] = s[1] = '0'; s[2] = '\0'; while(b) { unsigned t = b & 0x0f; if( t < 10 ) { s[i] = '0' + t; } else { s[i] = 'a' + t - 10; } i--; b >>= 4; } } void sha3_512_ByteToHex(char out[SHA3_512_DIGEST_HEX_STR_SIZE], const uint8_t in[SHA3_512_DIGEST_SIZE]) { int i; for (i = 0; i < SHA3_512_DIGEST_SIZE; i++) { byte_to_hex(in[i], out + i * 2); } } mvdsv-0.35/src/sha3.h000066400000000000000000000052531427146041000144020ustar00rootroot00000000000000#ifndef SHA3_H #define SHA3_H #include /* ------------------------------------------------------------------------- * Works when compiled for either 32-bit or 64-bit targets, optimized for * 64 bit. * * Canonical implementation of Init/Update/Finalize for SHA-3 byte input. * * SHA3-256, SHA3-384, SHA-512 are implemented. SHA-224 can easily be added. * * Based on code from http://keccak.noekeon.org/ . * * I place the code that I wrote into public domain, free to use. * * I would appreciate if you give credits to this work if you used it to * write or test * your code. * * Aug 2015. Andrey Jivsov. crypto@brainhub.org * ---------------------------------------------------------------------- */ /* 'Words' here refers to uint64_t */ #define SHA3_KECCAK_SPONGE_WORDS \ (((1600)/8/*bits to byte*/)/sizeof(uint64_t)) typedef struct sha3_context_ { uint64_t saved; /* the portion of the input message that we * didn't consume yet */ union { /* Keccak's state */ uint64_t s[SHA3_KECCAK_SPONGE_WORDS]; uint8_t sb[SHA3_KECCAK_SPONGE_WORDS * 8]; } u; unsigned byteIndex; /* 0..7--the next byte after the set one * (starts from 0; 0--none are buffered) */ unsigned wordIndex; /* 0..24--the next word to integrate input * (starts from 0) */ unsigned capacityWords; /* the double size of the hash output in * words (e.g. 16 for Keccak 512) */ } sha3_context; enum SHA3_FLAGS { SHA3_FLAGS_NONE=0, SHA3_FLAGS_KECCAK=1 }; enum SHA3_RETURN { SHA3_RETURN_OK=0, SHA3_RETURN_BAD_PARAMS=1 }; typedef enum SHA3_RETURN sha3_return_t; /* For Init or Reset call these: */ sha3_return_t sha3_Init(void *priv, unsigned bitSize); void sha3_Init256(void *priv); void sha3_Init384(void *priv); void sha3_Init512(void *priv); enum SHA3_FLAGS sha3_SetFlags(void *priv, enum SHA3_FLAGS); void sha3_Update(void *priv, void const *bufIn, size_t len); void const *sha3_Finalize(void *priv); /* Single-call hashing */ sha3_return_t sha3_HashBuffer( unsigned bitSize, /* 256, 384, 512 */ enum SHA3_FLAGS flags, /* SHA3_FLAGS_NONE or SHA3_FLAGS_KECCAK */ const void *in, unsigned inBytes, void *out, unsigned outBytes ); /* up to bitSize/8; truncation OK */ //========================================================================= #define SHA3_512_DIGEST_SIZE (512 / 8) #define SHA3_512_DIGEST_HEX_STR_SIZE (SHA3_512_DIGEST_SIZE * 2 + 1) void sha3_512_ByteToHex(char out[SHA3_512_DIGEST_HEX_STR_SIZE], const uint8_t in[SHA3_512_DIGEST_SIZE]); #endif mvdsv-0.35/src/sv_ccmds.c000066400000000000000000001140551427146041000153410ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef CLIENTONLY #include "qwsvdef.h" cvar_t sv_cheats = {"sv_cheats", "0"}; qbool sv_allow_cheats = false; int fp_messages=4, fp_persecond=4, fp_secondsdead=10; char fp_msg[255] = { 0 }; extern cvar_t sv_logdir; //bliP: 24/7 logdir extern redirect_t sv_redirected; void SV_Localinfo_Set (const char *name, const char *value); /* =============================================================================== OPERATOR CONSOLE ONLY COMMANDS These commands can only be entered from stdin or by a remote operator datagram =============================================================================== */ /* ================== SV_Quit ================== */ void SV_Quit (qbool restart) { SV_Shutdown ("Server shutdown.\n"); #ifdef SERVERONLY Sys_Quit (restart); #else Host_Quit(); // will also call SV_Shutdown(), but it is not an issue. #endif } /* ================== SV_Quit_f ================== */ void SV_Quit_f (void) { SV_Quit(false); } /* ================== SV_Restart_f ================== */ void SV_Restart_f (void) { SV_Quit(true); } /* ============ SV_Logfile ============ */ void SV_Logfile (int sv_log, qbool newlog) { int sv_port = NET_UDPSVPort(); char name[MAX_OSPATH]; int i; // newlog - stands for: we want open new log file if (logs[sv_log].sv_logfile) { // turn off logging fclose (logs[sv_log].sv_logfile); logs[sv_log].sv_logfile = NULL; // in case of NON "newlog" we do some additional work and exit function if (!newlog) { Con_Printf ("%s", logs[sv_log].message_off); logs[sv_log].log_level = 0; return; } } // always use new log file for frag log if (sv_log == FRAG_LOG || sv_log == MOD_FRAG_LOG) newlog = true; for (i = 0; i < 1000; i++) { snprintf (name, sizeof(name), "%s/%s%d_%04d.log", sv_logdir.string, logs[sv_log].file_name, sv_port, i); if (!COM_FileExists(name)) break; // file doesn't exist } if (!newlog) //use last log if possible snprintf (name, sizeof(name), "%s/%s%d_%04d.log", sv_logdir.string, logs[sv_log].file_name, sv_port, (int)max(0, i - 1)); Con_Printf ("Logging %s to %s\n", logs[sv_log].message_on, name); if (!(logs[sv_log].sv_logfile = fopen (name, "a"))) { Con_Printf ("Failed.\n"); logs[sv_log].sv_logfile = NULL; return; } switch (sv_log) { case TELNET_LOG: logs[TELNET_LOG].log_level = Cvar_Value("telnet_log_level"); break; case CONSOLE_LOG: logs[CONSOLE_LOG].log_level = Cvar_Value("qconsole_log_say"); break; default: logs[sv_log].log_level = 1; } } /* ============ SV_Logfile_f ============ */ void SV_Logfile_f (void) { SV_Logfile(CONSOLE_LOG, false); } /* ============ SV_ErrorLogfile_f ============ */ void SV_ErrorLogfile_f (void) { SV_Logfile(ERROR_LOG, false); } /* ============ SV_RconLogfile_f ============ */ void SV_RconLogfile_f (void) { SV_Logfile(RCON_LOG, false); } /* ============ SV_RconLogfile_f ============ */ void SV_TelnetLogfile_f (void) { SV_Logfile(TELNET_LOG, false); } /* ============ SV_FragLogfile_f ============ */ void SV_FragLogfile_f (void) { SV_Logfile(FRAG_LOG, false); } //bliP: player log /* ============ SV_PlayerLogfile_f ============ */ void SV_PlayerLogfile_f (void) { SV_Logfile(PLAYER_LOG, false); } //<- /* ============ SV_ModFragLogfile_f ============ */ void SV_ModFragLogfile_f (void) { SV_Logfile(MOD_FRAG_LOG, false); } log_t logs[MAX_LOG] = { {NULL, "logfile", "qconsole_", "File logging off.\n", "console", SV_Logfile_f, 0}, {NULL, "logerrors", "qerror_", "Error logging off.\n", "errors", SV_ErrorLogfile_f, 0}, {NULL, "logrcon", "rcon_", "Rcon logging off.\n", "rcon", SV_RconLogfile_f, 0}, {NULL, "logtelnet", "qtelnet_", "Telnet logging off.\n", "telnet", SV_TelnetLogfile_f, 0}, {NULL, "fraglogfile", "frag_", "Frag file logging off.\n", "frags", SV_FragLogfile_f, 0}, {NULL, "logplayers", "player_", "Player logging off.\n", "players", SV_PlayerLogfile_f, 0},//bliP: player logging {NULL, "modfraglogfile", "modfrag_", "Mod frag file logging off.\n", "modfrags", SV_ModFragLogfile_f, 0} }; /* ================== SV_SetPlayer Sets sv_client and sv_player to the player with idnum Cmd_Argv(1) ================== */ qbool SV_SetPlayer (void) { client_t *cl; int i; int idnum; idnum = Q_atoi(Cmd_Argv(1)); // HACK: for cheat commands which comes from client rather than from server console if (sv_client && sv_redirected == RD_CLIENT) { idnum = sv_client->userid; } for (i=0,cl=svs.clients ; istate) continue; if (cl->userid == idnum) { sv_client = cl; sv_player = sv_client->edict; return true; } } Con_Printf ("Userid %i is not on the server\n", idnum); return false; } /* ================== SV_God_f Sets client to godmode ================== */ void SV_God_f (void) { if (!sv_allow_cheats) { Con_Printf ("Cheats are not allowed on this server\n"); return; } if (!SV_SetPlayer ()) return; sv_player->v->flags = (int)sv_player->v->flags ^ FL_GODMODE; if (!((int)sv_player->v->flags & FL_GODMODE) ) SV_ClientPrintf (sv_client, PRINT_HIGH, "godmode OFF\n"); else SV_ClientPrintf (sv_client, PRINT_HIGH, "godmode ON\n"); } void SV_Noclip_f (void) { if (!sv_allow_cheats) { Con_Printf ("Cheats are not allowed on this server\n"); return; } if (!SV_SetPlayer ()) return; if (sv_player->v->movetype != MOVETYPE_NOCLIP) { sv_player->v->movetype = MOVETYPE_NOCLIP; SV_ClientPrintf (sv_client, PRINT_HIGH, "noclip ON\n"); } else { sv_player->v->movetype = MOVETYPE_WALK; SV_ClientPrintf (sv_client, PRINT_HIGH, "noclip OFF\n"); } } /* ================== SV_Give_f ================== */ void SV_Give_f (void) { char *t; int v, cnt; if (!sv_allow_cheats) { Con_Printf ("Cheats are not allowed on this server\n"); return; } if (!SV_SetPlayer ()) return; // HACK: for cheat commands which comes from client rather than from server console cnt = (sv_redirected == RD_CLIENT ? 1 : 2); t = Cmd_Argv(cnt++); v = Q_atoi (Cmd_Argv(cnt++)); switch (t[0]) { case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': sv_player->v->items = (int)sv_player->v->items | IT_SHOTGUN<< (t[0] - '2'); break; case 's': sv_player->v->ammo_shells = v; break; case 'n': sv_player->v->ammo_nails = v; break; case 'r': sv_player->v->ammo_rockets = v; break; case 'h': sv_player->v->health = v; break; case 'c': sv_player->v->ammo_cells = v; break; } } void SV_Fly_f (void) { if (!sv_allow_cheats) { Con_Printf ("Cheats are not allowed on this server\n"); return; } if (!SV_SetPlayer ()) return; if (sv_player->v->solid != SOLID_SLIDEBOX) return; // dead don't fly if (sv_player->v->movetype != MOVETYPE_FLY) { sv_player->v->movetype = MOVETYPE_FLY; SV_ClientPrintf (sv_client, PRINT_HIGH, "flymode ON\n"); } else { sv_player->v->movetype = MOVETYPE_WALK; SV_ClientPrintf (sv_client, PRINT_HIGH, "flymode OFF\n"); } } /* ====================== SV_Map_f handle a map command from the console or progs. ====================== */ void SV_Map (qbool now) { static char level[MAX_QPATH]; static char expanded[MAX_QPATH]; static char entityfile[MAX_QPATH]; static qbool changed = false; char *s; // if now, change it if (now) { if (!changed) return; changed = false; if (!FS_FLocateFile(expanded, FSLFRT_IFFOUND, NULL)) { Sys_Printf ("Can't find %s\n", expanded); return; } if (sv.mvdrecording) SV_MVDStop_f(); #ifndef SERVERONLY CL_BeginLocalConnection (); #endif // -> scream if ((int)frag_log_type.value) { //bliP: date check -> date_t date; SV_TimeOfDay(&date, "%a %b %d, %H:%M:%S %Y"); s = va("\\newmap\\%s\\\\\\\\%d-%d-%d %d:%d:%d\\\n", level, date.year, date.mon+1, //bliP: check me - date.mon or date.mon+1? existing code was date.mon+1 date.day, date.hour, date.min, date.sec); //<- if (logs[FRAG_LOG].sv_logfile) SZ_Print (&svs.log[svs.logsequence&1], s); SV_Write_Log(FRAG_LOG, 0, s); } // <- SV_SpawnServer (level, !strcasecmp(Cmd_Argv(0), "devmap"), entityfile, false); #ifdef SERVERONLY SV_BroadcastCommand ("changing\n" "reconnect\n"); SV_SendMessagesToAll (); #else SV_BroadcastCommand ("reconnect\n"); #endif return; } // get the map name, but don't change now, could be executed from progs.dat if (Cmd_Argc() < 2 || Cmd_Argc() > 3) { Con_Printf ("map [] : continue game on a new level\n"); return; } strlcpy (level, Cmd_Argv(1), MAX_QPATH); memset(entityfile, 0, sizeof(entityfile)); if (Cmd_Argc() >= 3) strlcpy (entityfile, Cmd_Argv(2), MAX_QPATH); // check to make sure the level exists snprintf (expanded, MAX_QPATH, "maps/%s.bsp", level); // Flush FS cache on each map change. FS_FlushFSHash(); if (!FS_FLocateFile(expanded, FSLFRT_IFFOUND, NULL)) { Con_Printf ("Can't find %s\n", expanded); return; } changed = true; #ifndef SERVERONLY com_serveractive = true; #endif } void SV_Map_f (void) { SV_Map(false); } /*================== SV_ReplaceChar Replace char in string ==================*/ void SV_ReplaceChar(char *s, char from, char to) { if (s) for ( ;*s ; ++s) if (*s == from) *s = to; } //bliP: ls, rm, rmdir, chmod -> /*================== SV_ListFiles_f Lists files ==================*/ void SV_ListFiles_f (void) { dir_t dir; file_t *list; char *key; char *dirname; int i; if (Cmd_Argc() < 2) { Con_Printf ("ls \n"); return; } dirname = Cmd_Argv(1); SV_ReplaceChar(dirname, '\\', '/'); // Double-check then move to FS_UnsafeFilename() ? if ( !strncmp(dirname, "../", 3) || strstr(dirname, "/../") || *dirname == '/' || ( (i = strlen(dirname)) < 3 ? 0 : !strncmp(dirname + i - 3, "/..", 4) ) || !strncmp(dirname, "..", 3) #ifdef _WIN32 || ( dirname[1] == ':' && ((*dirname >= 'a' && *dirname <= 'z') || (*dirname >= 'A' && *dirname <= 'Z')) ) #endif //_WIN32 ) { Con_Printf("Unable to list %s\n", dirname); return; } Con_Printf("Content of %s/*.*\n", dirname); dir = Sys_listdir(va("%s", dirname), ".*", SORT_BY_NAME); list = dir.files; if (!list->name[0]) { Con_Printf("No files\n"); return; } key = (Cmd_Argc() == 3) ? Cmd_Argv(2) : (char *) ""; //directories... for (; list->name[0]; list++) { if (!strstr(list->name, key) || !list->isdir) continue; Con_Printf("- %s\n", list->name); } list = dir.files; //files... for (; list->name[0]; list++) { if (!strstr(list->name, key) || list->isdir) continue; if ((int)list->size / 1024 > 0) Con_Printf("%s %.0fKB (%.2fMB)\n", list->name, (float)list->size / 1024, (float)list->size / 1024 / 1024); else Con_Printf("%s %dB\n", list->name, list->size); } Con_Printf("Total: %d files, %.0fKB (%.2fMB)\n", dir.numfiles, (float)dir.size / 1024, (float)dir.size / 1024 / 1024); } /*================== SV_RemoveDirectory_f Removes an empty directory ==================*/ void SV_RemoveDirectory_f (void) { char *dirname; if (Cmd_Argc() != 2) { Con_Printf("rmdir \n"); return; } dirname = Cmd_Argv(1); SV_ReplaceChar(dirname, '\\', '/'); if ( !strncmp(dirname, "../", 3) || strstr(dirname, "/../") || *dirname == '/' #ifdef _WIN32 || ( dirname[1] == ':' && ((*dirname >= 'a' && *dirname <= 'z') || (*dirname >= 'A' && *dirname <= 'Z')) ) #endif //_WIN32 ) { Con_Printf("Unable to remove\n"); return; } if (!Sys_rmdir(dirname)) Con_Printf("Directory %s successfully removed\n", dirname); else Con_Printf("Unable to remove directory %s\n", dirname); } /*================== SV_RemoveFile_f Remove a file ==================*/ void SV_RemoveFile_f (void) { char *dirname; char *filename; int i; if (Cmd_Argc() < 3) { Con_Printf("rm { | * | *} - removes a file | with token | all\n"); return; } dirname = Cmd_Argv(1); filename = Cmd_Argv(2); SV_ReplaceChar(dirname, '\\', '/'); SV_ReplaceChar(filename, '\\', '/'); if ( !strncmp(dirname, "../", 3) || strstr(dirname, "/../") || *dirname == '/' || strchr(filename, '/') || ( (i = strlen(filename)) < 3 ? 0 : !strncmp(filename + i - 3, "/..", 4) ) #ifdef _WIN32 || ( dirname[1] == ':' && ((*dirname >= 'a' && *dirname <= 'z') || (*dirname >= 'A' && *dirname <= 'Z')) ) #endif //_WIN32 ) { Con_Printf("Unable to remove\n"); return; } if (*filename == '*') //token, many files { dir_t dir; file_t *list; // remove all files with specified token filename++; dir = Sys_listdir(va("%s", dirname), ".*", SORT_BY_NAME); list = dir.files; for (i = 0; list->name[0]; list++) { if (!list->isdir && strstr(list->name, filename)) { if (!Sys_remove(va("%s/%s", dirname, list->name))) { Con_Printf("Removing %s...\n", list->name); i++; } } } if (i) Con_Printf("%d files removed\n", i); else Con_Printf("No matching found\n"); } else // 1 file { if (!Sys_remove(va("%s/%s", dirname, filename))) Con_Printf("File %s successfully removed\n", filename); else Con_Printf("Unable to remove file %s\n", filename); } // force cache rebuild. FS_FlushFSHash(); } /*================== SV_ChmodFile_f Chmod a script ==================*/ #ifndef _WIN32 void SV_ChmodFile_f (void) { char *_mode, *filename; unsigned int mode, m; if (Cmd_Argc() != 3) { Con_Printf("chmod \n"); return; } _mode = Cmd_Argv(1); filename = Cmd_Argv(2); if (!strncmp(filename, "../", 3) || strstr(filename, "/../") || *filename == '/' || strlen(_mode) != 3 || ( (m = strlen(filename)) < 3 ? 0 : !strncmp(filename + m - 3, "/..", 4) )) { Con_Printf("Unable to chmod\n"); return; } for (mode = 0; *_mode; _mode++) { m = *_mode - '0'; if (m > 7) { Con_Printf("Unable to chmod\n"); return; } mode = (mode << 3) + m; } if (chmod(filename, mode)) Con_Printf("Unable to chmod %s\n", filename); else Con_Printf("Chmod %s successful\n", filename); } #endif //_WIN32 /*================== SV_LocalCommand_f Execute system command ==================*/ //bliP: REMOVE ME REMOVE ME REMOVE ME REMOVE ME REMOVE ME -> void SV_LocalCommand_f (void) { int i, c; char str[1024], *temp_file = "__output_temp_file__"; if ((c = Cmd_Argc()) < 2) { Con_Printf("localcommand [command]\n"); return; } str[0] = 0; for (i = 1; i < c; i++) { strlcat (str, Cmd_Argv(i), sizeof(str)); strlcat (str, " ", sizeof(str)); } strlcat (str, va("> %s 2>&1\n", temp_file), sizeof(str)); if (system(str) == -1) Con_Printf("command failed\n"); else { char buf[512]; FILE *f; if ((f = fopen(temp_file, "rt")) == NULL) Con_Printf("(empty)\n"); else { while (!feof(f)) { buf[fread (buf, 1, sizeof(buf) - 1, f)] = 0; Con_Printf("%s", buf); } fclose(f); if (Sys_remove(temp_file)) Con_Printf("Unable to remove file %s\n", temp_file); } } } //REMOVE ME REMOVE ME REMOVE ME REMOVE ME REMOVE ME /* ================== SV_Kick_f Kick a user off of the server ================== */ void SV_Kick_f (void) { int i, j; client_t *cl; int uid; int c; char reason[80] = ""; c = Cmd_Argc (); if (c < 2) { #ifndef SERVERONLY // some mods use a "kick" alias for their own needs, sigh if (CL_ClientState() && Cmd_FindAlias("kick")) { Cmd_ExecuteString (Cmd_AliasString("kick")); return; } #endif Con_Printf ("kick [reason]\n"); return; } uid = Q_atoi(Cmd_Argv(1)); for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (!cl->state) continue; if (cl->userid == uid) { if (c > 2) { strlcpy (reason, " (", sizeof(reason)); for (j=2 ; jstate; cl->state = cs_free; // HACK: don't broadcast to this client SV_BroadcastPrintf(PRINT_HIGH, "%s was kicked%s\n", cl->name, reason); cl->state = (sv_client_state_t)saved_state; SV_ClientPrintf(cl, PRINT_HIGH, "You were kicked from the game%s\n", reason); SV_LogPlayer(cl, va("kick%s\n", reason), 1); //bliP: logging SV_DropClient(cl); } //bliP: mute, cuff -> int SV_MatchUser (char *s) { int i; client_t *cl; for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (!cl->state) continue; if (!strcmp (cl->name, s)) return cl->userid; } i = Q_atoi(s); return i; } /* ================ SV_Cuff_f ================ */ #define MAXPENALTY 1200.0 void SV_Cuff_f (void) { int c, i, uid; double mins = 0.5; qbool done = false; qbool print = true; client_t *cl; char reason[80]; char text[100]; if ((c = Cmd_Argc()) < 2) { Con_Printf ("usage: cuff [reason]\n(default = 0.5, 0 = cancel cuff).\n"); return; } uid = SV_MatchUser(Cmd_Argv(1)); if (!uid) { Con_Printf ("Couldn't find user %s\n", Cmd_Argv(1)); return; } if (c >= 3) { mins = Q_atof(Cmd_Argv(2)); if (mins < 0.0 || mins > MAXPENALTY) { mins = MAXPENALTY; } } for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (!cl->state) continue; if (cl->userid == uid) { cl->cuff_time = curtime + (mins * 60.0); done = true; break; } } if (done) { reason[0] = 0; if (c > 2) { for (i = 3; i < c; i++) { strlcat (reason, Cmd_Argv(i), sizeof(reason) - 1 - strlen(reason)); if (i < c - 1) strlcat (reason, " ", sizeof(reason) - strlen(reason)); } } if (mins) { SV_BroadcastPrintf (PRINT_CHAT, "%s cuffed for %.1f minutes%s%s\n", cl->name, mins, reason[0] ? ": " : "", reason[0] ? reason : ""); snprintf(text, sizeof(text), "You are cuffed for %.1f minutes%s%s\n", mins, reason[0] ? "\n\n" : "", reason[0] ? reason : ""); ClientReliableWrite_Begin(cl,svc_centerprint, 2+strlen(text)); ClientReliableWrite_String (cl, text); } else { if (print) { SV_BroadcastPrintf (PRINT_CHAT, "%s un-cuffed.\n", cl->name); } } } else { Con_Printf ("Couldn't find user %s\n", Cmd_Argv(1)); } } /* ================ SV_Mute_f ================ */ void SV_Mute_f (void) { int c, i, uid; double mins = 0.5; qbool done = false; qbool print = true; client_t *cl; char reason[1024]; char text[1024]; char *ptr; if ((c = Cmd_Argc()) < 2) { Con_Printf ("usage: mute [reason]\n(default = 0.5, 0 = cancel mute).\n"); return; } uid = SV_MatchUser(Cmd_Argv(1)); if (!uid) { Con_Printf ("Couldn't find user %s\n", Cmd_Argv(1)); return; } if (c >= 3) { ptr = Cmd_Argv(2); if (*ptr == '*') { ptr++; print = false; } mins = Q_atof(ptr); if (mins < 0.0 || mins > MAXPENALTY) mins = MAXPENALTY; } for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (!cl->state) continue; if (cl->userid == uid) { cl->lockedtill = curtime + (mins * 60.0); done = true; break; } } if (done) { reason[0] = 0; if (c > 2) { for (i = 3; i < c; i++) { strlcat (reason, Cmd_Argv(i), sizeof(reason) - 1 - strlen(reason)); if (i < c - 1) strlcat (reason, " ", sizeof(reason) - strlen(reason)); } } if (mins) { if (print) SV_BroadcastPrintf (PRINT_CHAT, "%s muted for %.1f minutes%s%s\n", cl->name, mins, reason[0] ? ": " : "", reason[0] ? reason : ""); snprintf(text, sizeof(text), "You are muted for %.1f minutes%s%s\n", mins, reason[0] ? "\n\n" : "", reason[0] ? reason : ""); ClientReliableWrite_Begin(cl, svc_centerprint, 2+strlen(text)); ClientReliableWrite_String (cl, text); } else { if (print) { SV_BroadcastPrintf (PRINT_CHAT, "%s un-muted.\n", cl->name); } } } else { Con_Printf ("Couldn't find user %s\n", Cmd_Argv(1)); } } void SV_RemovePenalty_f (void) { int i; int num; extern int numpenfilters; if (Cmd_Argc() != 2) { Con_Printf ("penaltyremove [num]\n"); return; } num = Q_atoi(Cmd_Argv(1)); for (i = 0; i < numpenfilters; i++) { if (i == num) { SV_RemoveIPFilter (i); Con_Printf ("Removed.\n"); return; } } Con_Printf ("Didn't find penalty filter %i.\n", num); } void SV_ListPenalty_f (void) { client_t *cl; int i; char s[8]; extern int numpenfilters; extern penfilter_t penfilters[MAX_PENFILTERS]; Con_Printf ("Active Penalty List:\n"); for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (!cl->state) continue; if (cl->lockedtill >= curtime) { Con_Printf ("%i %s mute (remaining: %d)\n", cl->userid, cl->name, (cl->lockedtill) ? (int)(cl->lockedtill - curtime) : 0); } if (cl->cuff_time >= curtime) { Con_Printf ("%i %s cuff (remaining: %d)\n", cl->userid, cl->name, (cl->cuff_time) ? (int)(cl->cuff_time - curtime) : 0); } } Con_Printf ("Saved Penalty List:\n"); for (i = 0; i < numpenfilters; i++) { switch (penfilters[i].type) { case ft_mute: strlcpy(s, "Mute", sizeof(s)); break; case ft_cuff: strlcpy(s, "Cuff", sizeof(s)); break; default: strlcpy(s, "Unknown", sizeof(s)); break; } Con_Printf ("%i: %s for %i.%i.%i.%i (remaining: %d)\n", i, s, penfilters[i].ip[0], penfilters[i].ip[1], penfilters[i].ip[2], penfilters[i].ip[3], (penfilters[i].time) ? (int)(penfilters[i].time - realtime) : 0); } } //<- /* ================ SV_Resolve resolve IP via DNS lookup ================ */ char *SV_Resolve(char *addr) { #if defined (__linux__) || defined (_WIN32) unsigned long ip; #else in_addr_t ip; #endif struct hostent *hp; ip = inet_addr(addr); if ((hp = gethostbyaddr((const char *)&ip, sizeof(ip), AF_INET)) != NULL) addr = hp->h_name; return addr; } /* ================== SV_Nslookup_f ================== */ void SV_Nslookup_f (void) { char *ip, *name; if (Cmd_Argc() != 2) { Con_Printf ("Usage: nslookup \n"); return; } ip = Cmd_Argv(1); name = SV_Resolve(ip); if (ip != name) Con_Printf ("Name: %s\nAddress: %s\n", name, ip); else Con_Printf ("Couldn't resolve %s\n", ip); } /* ================ SV_Status_f ================ */ extern cvar_t sv_use_dns; void SV_Status_f (void) { int i; client_t *cl; float cpu, avg, pak, demo1 = 0.0; char *s; cpu = (svs.stats.latched_active + svs.stats.latched_idle); if (cpu) { demo1 = 100.0 * svs.stats.latched_demo / cpu; cpu = 100.0 * svs.stats.latched_active / cpu; } avg = 1000 * svs.stats.latched_active / STATFRAMES; pak = (float)svs.stats.latched_packets / STATFRAMES; Con_Printf ("net address : %s\n" "cpu utilization (overall) : %3i%%\n" "cpu utilization (recording) : %3i%%\n" "avg response time : %i ms\n" "packets/frame : %5.2f (%d)\n", NET_AdrToString (net_local_sv_ipadr), (int)cpu, (int)demo1, (int)avg, pak, num_prstr); switch (sv_redirected) { case RD_NONE: Con_Printf ("name ping frags id address real ip\n" "---------------- ---- ----- ------ ---------------------- ---------------\n"); for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (!cl->state) continue; s = NET_BaseAdrToString(cl->netchan.remote_address); Con_Printf ("%-16s %4i %5i %6i %-22s ", cl->name, (int)SV_CalcPing(cl), (int)cl->edict->v->frags, cl->userid, (int)sv_use_dns.value ? SV_Resolve(s) : s); if (cl->realip.ip[0]) Con_Printf ("%-15s", NET_BaseAdrToString (cl->realip)); Con_Printf (cl->spectator ? (char *) "(s)" : (char *) ""); switch (cl->state) { case cs_connected: case cs_preconnected: Con_Printf (" CONNECTING\n"); continue; case cs_zombie: Con_Printf (" ZOMBIE\n"); continue; default: Con_Printf ("\n"); } } break; //case RD_MOD: //case RD_CLIENT: //case RD_PACKET: default: // most remote clients are 40 columns // 01234567890123456789012345678901234567890123456789 Con_Printf ("name ping frags id\n"); Con_Printf (" address\n"); Con_Printf (" real ip\n"); Con_Printf ("------------------ ---- ----- ------\n"); for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (!cl->state) continue; s = NET_BaseAdrToString(cl->netchan.remote_address); Con_Printf ("%-18s %4i %5i %6s %s\n%-36s\n", cl->name, (int)SV_CalcPing(cl), (int)cl->edict->v->frags, Q_yelltext((unsigned char*)va("%d", cl->userid)), cl->spectator ? " (s)" : "", (int)sv_use_dns.value ? SV_Resolve(s) : s); if (cl->realip.ip[0]) Con_Printf ("%-36s\n", NET_BaseAdrToString (cl->realip)); switch (cl->state) { case cs_connected: case cs_preconnected: Con_Printf ("CONNECTING\n"); continue; case cs_zombie: Con_Printf ("ZOMBIE\n"); continue; default:; } } } // switch Con_Printf ("\n"); } /* ================== SV_Check_maps_f ================== */ void SV_Check_maps_f(void) { dir_t d; file_t *list; int i, j, maps_id1; d = Sys_listdir("id1/maps", ".bsp$", SORT_BY_NAME); list = d.files; for (i = LOCALINFO_MAPS_LIST_START; list->name[0] && i <= LOCALINFO_MAPS_LIST_END; list++, i++) { list->name[strlen(list->name) - 4] = 0; if (!list->name[0]) continue; SV_Localinfo_Set(va("%d", i), list->name); } maps_id1 = i - 1; d = Sys_listdir("qw/maps", ".bsp$", SORT_BY_NAME); list = d.files; for (; list->name[0] && i <= LOCALINFO_MAPS_LIST_END; list++, i++) { list->name[strlen(list->name) - 4] = 0; if (!list->name[0]) continue; for (j = LOCALINFO_MAPS_LIST_START; j <= maps_id1; j++) if (!strncmp(Info_Get(&_localinfo_, va("%d", j)), list->name, MAX_KEY_STRING)) break; if (j <= maps_id1) continue; SV_Localinfo_Set(va("%d", i), list->name); } for (; i <= LOCALINFO_MAPS_LIST_END; i++) { SV_Localinfo_Set(va("%d", i), ""); } } /* ================== SV_ConSay_f ================== */ void SV_ConSay_f(void) { client_t *client; int j; char *p; char text[1024] = "console: "; if (Cmd_Argc () < 2) return; p = Cmd_Args(); if (*p == '"') { p++; p[strlen(p)-1] = 0; } strlcat(text, p, sizeof(text)); strlcat(text, "\n", sizeof(text)); for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) { if (client->state != cs_spawned) continue; SV_ClientPrintf2(client, PRINT_CHAT, "%s", text); } if (sv.mvdrecording) { if (MVDWrite_Begin (dem_all, 0, strlen(text)+3)) { MVD_MSG_WriteByte (svc_print); MVD_MSG_WriteByte (PRINT_CHAT); MVD_MSG_WriteString (text); } } Sys_Printf("%s", text); SV_Write_Log(CONSOLE_LOG, 1, text); } void SV_SendServerInfoChange(char *key, char *value) { if (!sv.state) return; MSG_WriteByte (&sv.reliable_datagram, svc_serverinfo); MSG_WriteString (&sv.reliable_datagram, key); MSG_WriteString (&sv.reliable_datagram, value); } //Cvar system calls this when a CVAR_SERVERINFO cvar changes void SV_ServerinfoChanged (char *key, char *string) { string = Cvar_ServerInfoValue(key, string); if (strcmp(string, Info_ValueForKey (svs.info, key))) { Info_SetValueForKey (svs.info, key, string, MAX_SERVERINFO_STRING); SV_SendServerInfoChange (key, string); } } /* =========== SV_Serverinfo_f Examine or change the serverinfo string =========== */ void SV_Serverinfo_f (void) { cvar_t *var; char *s; char *key, *value; if (Cmd_Argc() == 1) { Con_Printf ("Server info settings:\n"); Info_Print (svs.info); Con_Printf ("[%d/%d]\n", strlen(svs.info), MAX_SERVERINFO_STRING); return; } //bliP: sane serverinfo usage (mercury) -> if (Cmd_Argc() == 2) { s = Info_ValueForKey(svs.info, Cmd_Argv(1)); if (*s) Con_Printf ("Serverinfo %s: \"%s\"\n", Cmd_Argv(1), s); else Con_Printf ("No such key %s\n", Cmd_Argv(1)); return; } //<- if (Cmd_Argc() != 3) { Con_Printf ("usage: serverinfo [ [ ] ]\n"); return; } key = Cmd_Argv(1); value = Cmd_Argv(2); if (key[0] == '*') { Con_Printf ("Star variables cannot be changed.\n"); return; } // force serverinfo "0" vars to be "" if (!strcmp(value, "0")) value = ""; // if the key is also a serverinfo cvar, change it too var = Cvar_Find(key); if (var && (var->flags & CVAR_SERVERINFO)) { Cvar_Set (var, value); // this call SV_ServerinfoChanged() as well. } else { SV_ServerinfoChanged(key, value); } } void SV_Localinfo_Set (const char *name, const char *value) { char *old_value; if (!name || !*name) return; if (!value) value = ""; old_value = Info_Get(&_localinfo_, name); // remember old value. Info_Set (&_localinfo_, name, value); // set new value. if (mod_localinfoChanged) { pr_global_struct->time = sv.time; pr_global_struct->self = 0; PR_SetTmpString(&G_INT(OFS_PARM0), name); PR_SetTmpString(&G_INT(OFS_PARM1), old_value); PR_SetTmpString(&G_INT(OFS_PARM2), Info_Get(&_localinfo_, name)); PR_ExecuteProgram (mod_localinfoChanged); } } /* =========== SV_Localinfo_f Examine or change the localinfo string =========== */ void SV_Localinfo_f (void) { if (Cmd_Argc() == 1) { char info[MAX_LOCALINFO_STRING]; Con_Printf ("Local info settings:\n"); Info_ReverseConvert(&_localinfo_, info, sizeof(info)); Info_Print (info); Con_Printf ("[%d/%d]\n", strlen(info), sizeof(info)); return; } //bliP: sane localinfo usage (mercury) -> if (Cmd_Argc() == 2) { char *s = Info_Get(&_localinfo_, Cmd_Argv(1)); if (*s) Con_Printf ("Localinfo %s: \"%s\"\n", Cmd_Argv(1), s); else Con_Printf ("No such key %s\n", Cmd_Argv(1)); return; } //<- if (Cmd_Argc() != 3) { Con_Printf ("usage: localinfo [ ]\n"); return; } if (Cmd_Argv(1)[0] == '*') { Con_Printf ("Star variables cannot be changed.\n"); return; } SV_Localinfo_Set(Cmd_Argv(1), Cmd_Argv(2)); } /* =========== SV_User_f Examine a users info strings =========== */ void SV_User_f (void) { char info[MAX_EXT_INFO_STRING]; if (Cmd_Argc() != 2) { Con_Printf ("Usage: user \n"); return; } if (!SV_SetPlayer ()) return; Info_ReverseConvert(&sv_client->_userinfo_ctx_, info, sizeof(info)); Info_Print(info); Con_DPrintf ("[%d/%d]\n", strlen(info), sizeof(info)); } /* ================ SV_Gamedir Sets the fake *gamedir to a different directory. ================ */ void SV_Gamedir (void) { char *dir; if (Cmd_Argc() == 1) { Con_Printf ("Current *gamedir: %s\n", Info_ValueForKey (svs.info, "*gamedir")); return; } if (Cmd_Argc() != 2) { Con_Printf ("Usage: sv_gamedir \n"); return; } dir = Cmd_Argv(1); if (strstr(dir, "..") || strchr(dir, '/') || strchr(dir, '\\') || strchr(dir, ':') ) { Con_Printf ("*Gamedir should be a single filename, not a path\n"); return; } Info_SetValueForStarKey (svs.info, "*gamedir", dir, MAX_SERVERINFO_STRING); } /* ================ SV_Floodport_f Sets the gamedir and path to a different directory. ================ */ void SV_Floodprot_f (void) { int arg1, arg2, arg3; if (Cmd_Argc() == 1) { if (fp_messages) { Con_Printf ("Current floodprot settings: \nAfter %d msgs per %d seconds, silence for %d seconds\n", fp_messages, fp_persecond, fp_secondsdead); return; } else Con_Printf ("No floodprots enabled.\n"); } if (Cmd_Argc() != 4) { Con_Printf ("Usage: floodprot <# of messages> \n"); Con_Printf ("Use floodprotmsg to set a custom message to say to the flooder.\n"); return; } arg1 = Q_atoi(Cmd_Argv(1)); arg2 = Q_atoi(Cmd_Argv(2)); arg3 = Q_atoi(Cmd_Argv(3)); if (arg1<=0 || arg2 <= 0 || arg3<=0) { Con_Printf ("All values must be positive numbers\n"); return; } if (arg1 > 10) { Con_Printf ("Can only track up to 10 messages.\n"); return; } fp_messages = arg1; fp_persecond = arg2; fp_secondsdead = arg3; } void SV_Floodprotmsg_f (void) { if (Cmd_Argc() == 1) { Con_Printf("Current msg: %s\n", fp_msg); return; } else if (Cmd_Argc() != 2) { Con_Printf("Usage: floodprotmsg \"\"\n"); return; } snprintf(fp_msg, sizeof(fp_msg), "%s", Cmd_Argv(1)); } /* ================ SV_Gamedir_f Sets the gamedir and path to a different directory. ================ */ void SV_Gamedir_f (void) { char *dir; if (Cmd_Argc() == 1) { Con_Printf ("Current gamedir: %s\n", fs_gamedir); return; } if (Cmd_Argc() != 2) { Con_Printf ("Usage: gamedir \n"); return; } dir = Cmd_Argv(1); if (strstr(dir, "..") || strchr(dir, '/') || strchr(dir, '\\') || strchr(dir, ':') ) { Con_Printf ("Gamedir should be a single filename, not a path\n"); return; } #ifndef SERVERONLY if (CL_ClientState()) { Con_Printf ("you must disconnect before changing gamedir\n"); return; } #endif FS_SetGamedir (dir, false); Info_SetValueForStarKey (svs.info, "*gamedir", dir, MAX_SERVERINFO_STRING); } /* ================ SV_Snap ================ */ void SV_Snap (int uid) { client_t *cl; char pcxname[80]; char checkname[MAX_OSPATH]; int i; FILE *f; for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (cl->state < cs_preconnected) continue; if (cl->userid == uid) break; } if (i >= MAX_CLIENTS) { Con_Printf ("userid not found\n"); return; } FS_CreatePath (va ("%s/snap/", fs_gamedir)); snprintf (pcxname, sizeof (pcxname), "%d-00.pcx", uid); for (i=0 ; i<=99 ; i++) { pcxname[strlen(pcxname) - 6] = i/10 + '0'; pcxname[strlen(pcxname) - 5] = i%10 + '0'; snprintf (checkname, MAX_OSPATH, "%s/snap/%s", fs_gamedir, pcxname); f = fopen (checkname, "rb"); if (!f) break; // file doesn't exist fclose (f); } if (i==100) { Con_Printf ("Snap: Couldn't create a file, clean some out.\n"); return; } strlcpy(cl->uploadfn, checkname, MAX_QPATH); memcpy(&cl->snap_from, &net_from, sizeof(net_from)); if (sv_redirected != RD_NONE) cl->remote_snap = true; else cl->remote_snap = false; ClientReliableWrite_Begin (cl, svc_stufftext, 24); ClientReliableWrite_String (cl, "cmd snap\n"); Con_Printf ("Requesting snap from user %d...\n", uid); } /* ================ SV_Snap_f ================ */ void SV_Snap_f (void) { int uid; if (Cmd_Argc() != 2) { Con_Printf ("Usage: snap \n"); return; } uid = Q_atoi(Cmd_Argv(1)); SV_Snap(uid); } /* ================ SV_Snap ================ */ void SV_SnapAll_f (void) { client_t *cl; int i; for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (cl->state < cs_preconnected || cl->spectator) continue; SV_Snap(cl->userid); } } // QW262 --> /* ================ SV_MasterPassword ================ */ void SV_MasterPassword_f (void) { #ifdef SERVERONLY if (!host_everything_loaded) #else if (!server_cfg_done) #endif strlcpy(master_rcon_password, Cmd_Argv(1), sizeof(master_rcon_password)); else Con_DPrintf("master_rcon_password can be set only in server.cfg\n"); } // <-- QW262 /* ================== SV_InitOperatorCommands ================== */ void SV_InitOperatorCommands (void) { int i; Cvar_Register (&sv_cheats); if (SV_CommandLineEnableCheats()) { sv_allow_cheats = true; Cvar_SetValue (&sv_cheats, 1); Info_SetValueForStarKey (svs.info, "*cheats", "ON", MAX_SERVERINFO_STRING); } for (i = MIN_LOG; i < MAX_LOG; ++i) Cmd_AddCommand (logs[i].command, logs[i].function); Cmd_AddCommand ("nslookup", SV_Nslookup_f); Cmd_AddCommand ("check_maps", SV_Check_maps_f); Cmd_AddCommand ("snap", SV_Snap_f); Cmd_AddCommand ("snapall", SV_SnapAll_f); Cmd_AddCommand ("kick", SV_Kick_f); Cmd_AddCommand ("status", SV_Status_f); //bliP: init -> Cmd_AddCommand ("rmdir", SV_RemoveDirectory_f); Cmd_AddCommand ("rm", SV_RemoveFile_f); Cmd_AddCommand ("ls", SV_ListFiles_f); Cmd_AddCommand ("mute", SV_Mute_f); Cmd_AddCommand ("cuff", SV_Cuff_f); Cmd_AddCommand ("penaltylist", SV_ListPenalty_f); Cmd_AddCommand ("penaltyremove", SV_RemovePenalty_f); #ifndef _WIN32 Cmd_AddCommand ("chmod", SV_ChmodFile_f); #endif //_WIN32 //<- if (SV_CommandLineEnableLocalCommand()) Cmd_AddCommand ("localcommand", SV_LocalCommand_f); Cmd_AddCommand ("map", SV_Map_f); #ifdef SERVERONLY Cmd_AddCommand ("devmap", SV_Map_f); #else if (IsDeveloperMode()) { Cmd_AddCommand("devmap", SV_Map_f); } #endif Cmd_AddCommand ("setmaster", SV_SetMaster_f); Cmd_AddCommand ("heartbeat", SV_Heartbeat_f); Cmd_AddCommand ("save", SV_SaveGame_f); Cmd_AddCommand ("load", SV_LoadGame_f); #ifdef SERVERONLY Cmd_AddCommand ("say", SV_ConSay_f); Cmd_AddCommand ("quit", SV_Quit_f); Cmd_AddCommand ("restart", SV_Restart_f); #endif #ifdef SERVERONLY Cmd_AddCommand ("god", SV_God_f); Cmd_AddCommand ("give", SV_Give_f); Cmd_AddCommand ("noclip", SV_Noclip_f); #endif Cmd_AddCommand ("localinfo", SV_Localinfo_f); #ifdef SERVERONLY Cmd_AddCommand ("serverinfo", SV_Serverinfo_f); Cmd_AddCommand ("user", SV_User_f); // FIXME: probably should be done like CL_Serverinfo_f(). #endif Cmd_AddCommand ("gamedir", SV_Gamedir_f); Cmd_AddCommand ("sv_gamedir", SV_Gamedir); // I wonder why it registered in host.c in ezquake... #ifdef SERVERONLY Cmd_AddCommand ("floodprot", SV_Floodprot_f); Cmd_AddCommand ("floodprotmsg", SV_Floodprotmsg_f); #endif Cmd_AddCommand ("master_rcon_password", SV_MasterPassword_f); } #endif // !CLIENTONLY mvdsv-0.35/src/sv_demo.c000066400000000000000000001317431427146041000151770ustar00rootroot00000000000000/* 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 included (GNU.txt) 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. */ // sv_demo.c - mvd demo related code #ifndef CLIENTONLY #include "qwsvdef.h" // minimal cache which can be used for demos, must be few times greater than DEMO_FLUSH_CACHE_IF_LESS_THAN_THIS #define DEMO_CACHE_MIN_SIZE 0x1000000 // flush demo cache if we have less than this free bytes #define DEMO_FLUSH_CACHE_IF_LESS_THAN_THIS 65536 static void sv_demoDir_OnChange(cvar_t *cvar, char *value, qbool *cancel); cvar_t sv_demoUseCache = {"sv_demoUseCache", "0"}; cvar_t sv_demoCacheSize = {"sv_demoCacheSize", "0", CVAR_ROM}; cvar_t sv_demoMaxDirSize = {"sv_demoMaxDirSize", "102400"}; cvar_t sv_demoClearOld = {"sv_demoClearOld", "0"}; cvar_t sv_demoDir = {"sv_demoDir", "demos", 0, sv_demoDir_OnChange}; cvar_t sv_demoDirAlt = {"sv_demoDirAlt", "", 0, sv_demoDir_OnChange }; cvar_t sv_demofps = {"sv_demofps", "30"}; cvar_t sv_demoIdlefps = {"sv_demoIdlefps", "10"}; cvar_t sv_demoPings = {"sv_demopings", "3"}; cvar_t sv_demoMaxSize = {"sv_demoMaxSize", "20480"}; cvar_t sv_demoExtraNames = {"sv_demoExtraNames", "0"}; cvar_t sv_demoPrefix = {"sv_demoPrefix", ""}; cvar_t sv_demoSuffix = {"sv_demoSuffix", ""}; cvar_t sv_demotxt = {"sv_demotxt", "1"}; cvar_t sv_onrecordfinish = {"sv_onRecordFinish", ""}; cvar_t sv_ondemoremove = {"sv_onDemoRemove", ""}; cvar_t sv_demoRegexp = {"sv_demoRegexp", "\\.mvd(\\.(gz|bz2|rar|zip))?$"}; cvar_t sv_silentrecord = {"sv_silentrecord", "0"}; cvar_t extralogname = {"extralogname", "unset"}; // no sv_ prefix? WTF! mvddest_t *singledest; // only one .. is allowed (security) static void sv_demoDir_OnChange(cvar_t *cvar, char *value, qbool *cancel) { if (cvar == &sv_demoDir && !value[0]) { *cancel = true; return; } if (value[0] == '.' && value[1] == '.') { value += 2; } if (strstr(value, "/..")) { *cancel = true; return; } } // { MVD writing functions, just wrappers void MVD_MSG_WriteChar (const int c) { MSG_WriteChar (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, c); } void MVD_MSG_WriteByte (const int c) { MSG_WriteByte (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, c); } void MVD_MSG_WriteShort (const int c) { MSG_WriteShort (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, c); } void MVD_MSG_WriteLong (const int c) { MSG_WriteLong (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, c); } void MVD_MSG_WriteFloat (const float f) { MSG_WriteFloat (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, f); } void MVD_MSG_WriteString (const char *s) { MSG_WriteString (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, s); } void MVD_MSG_WriteCoord (const float f) { MSG_WriteCoord (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, f); } void MVD_MSG_WriteAngle (const float f) { MSG_WriteAngle (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, f); } void MVD_SZ_Write (const void *data, int length) { SZ_Write (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, data, length); } // } mvddest_t *DestByName (char *name) { mvddest_t *d; for (d = demo.dest; d; d = d->nextdest) if (!strncmp(name, d->name, sizeof(d->name)-1)) return d; return NULL; } void DestClose (mvddest_t *d, qbool destroyfiles) { char path[MAX_OSPATH]; if (d->cache) Q_free(d->cache); if (d->file) fclose(d->file); if (d->socket) closesocket(d->socket); if (d->qtvuserlist) QTVsv_FreeUserList(d); if (destroyfiles) { snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, d->path, d->name); Sys_remove(path); strlcpy(path + strlen(path) - 3, "txt", MAX_OSPATH - strlen(path) + 3); Sys_remove(path); // force cache rebuild. FS_FlushFSHash(); } Q_free(d); } // // complete - just force flush for cached dests (dest->desttype == DEST_BUFFEREDFILE) // void DestFlush (qbool complete) { int len; mvddest_t *d, *t; if (!demo.dest) return; while (demo.dest->error) { d = demo.dest; demo.dest = d->nextdest; DestClose(d, false); if (!demo.dest) { SV_MVDStop(3, false); return; } } for (d = demo.dest; d; d = d->nextdest) { switch(d->desttype) { case DEST_FILE: fflush (d->file); break; case DEST_BUFFEREDFILE: if (d->cacheused + DEMO_FLUSH_CACHE_IF_LESS_THAN_THIS > d->maxcachesize || complete) { len = (int)fwrite(d->cache, 1, d->cacheused, d->file); if (len != d->cacheused) { Sys_Printf("DestFlush: fwrite() error\n"); d->error = true; } fflush(d->file); d->cacheused = 0; } break; case DEST_STREAM: if (d->io_time + qtv_streamtimeout.value <= Sys_DoubleTime()) { // problem what send() have internal buffer, so send() success some time even peer side does't read, // this may take some time before internal buffer overflow and timeout trigger, depends of buffer size. Sys_Printf("DestFlush: stream timeout\n"); d->error = true; } if (d->cacheused && !d->error) { len = send(d->socket, d->cache, d->cacheused, 0); if (len == 0) //client died { // d->error = true; // man says: The calls return the number of characters sent, or -1 if an error occurred. // so 0 is legal or what? } else if (len > 0) //we put some data through { //move up the buffer d->cacheused -= len; memmove(d->cache, d->cache+len, d->cacheused); d->io_time = Sys_DoubleTime(); // update IO activity } else { //error of some kind. would block or something if (qerrno != EWOULDBLOCK && qerrno != EAGAIN) { Sys_Printf("DestFlush: error on stream\n"); d->error = true; } } } break; case DEST_NONE: default: Sys_Error("DestFlush: encountered bad dest."); } if (d->desttype != DEST_STREAM) // no max size for stream { if ((unsigned int)sv_demoMaxSize.value && d->totalsize > ((unsigned int)sv_demoMaxSize.value * 1024)) { Sys_Printf("DestFlush: sv_demoMaxSize = %db trigger for dest\n", (int)sv_demoMaxSize.value); d->error = true; } } while (d->nextdest && d->nextdest->error) { t = d->nextdest; d->nextdest = t->nextdest; DestClose(t, false); } } } // if param "mvdonly" == true then close only demos, not QTV's steams static int DestCloseAllFlush (qbool destroyfiles, qbool mvdonly) { int numclosed = 0; mvddest_t *d, **prev, *next; DestFlush(true); //make sure it's all written. prev = &demo.dest; d = demo.dest; while (d) { next = d->nextdest; if (!mvdonly || d->desttype != DEST_STREAM) { desttype_t dt = d->desttype; char dest_name[sizeof(d->name)]; char dest_path[sizeof(d->path)]; strlcpy(dest_name, d->name, sizeof(dest_name)); strlcpy(dest_path, d->path, sizeof(dest_path)); *prev = d->nextdest; DestClose(d, destroyfiles); // NOTE: this free dest struck, so we can't use 'd' below numclosed++; if (dt != DEST_STREAM && dest_name[0]) // ignore stream or empty file name Run_sv_demotxt_and_sv_onrecordfinish (dest_name, dest_path, destroyfiles); } else prev = &d->nextdest; d = next; } return numclosed; } int DemoWriteDest (void *data, int len, mvddest_t *d) { int ret; if (d->error) return 0; d->totalsize += len; switch(d->desttype) { case DEST_FILE: ret = (int)fwrite(data, 1, len, d->file); if (ret != len) { Sys_Printf("DemoWriteDest: fwrite() error\n"); d->error = true; return 0; } break; case DEST_BUFFEREDFILE: //these write to a cache, which is flushed later case DEST_STREAM: if (d->cacheused + len > d->maxcachesize) { Sys_Printf("DemoWriteDest: cache overflow %d > %d\n", d->cacheused + len, d->maxcachesize); d->error = true; return 0; } memcpy(d->cache + d->cacheused, data, len); d->cacheused += len; break; case DEST_NONE: default: Sys_Error("DemoWriteDest: encountered bad dest."); } return len; } static void DemoWrite (void *data, int len) //broadcast to all proxies/mvds { mvddest_t *d; for (d = demo.dest; d; d = d->nextdest) { if (singledest && singledest != d) continue; DemoWriteDest(data, len, d); } } /* ==================== MVDWrite_Begin ==================== */ static qbool MVDWrite_BeginEx (byte type, int to, int size) { qbool new_mvd_msg; if (!sv.mvdrecording) return false; // we alredy overflowed, sorry but it no go if (demo.frames[demo.parsecount&UPDATE_MASK]._buf_.overflowed) return false; // ERROR if (size > MAX_MVD_SIZE) { Sys_Printf("MVDWrite_Begin: bad demo message size: %d > MAX_MVD_SIZE\n", size); return false; // ERROR } // check we have proper demo message type switch (type) { case dem_all: case dem_multiple: case dem_single: case dem_stats: break; default: Sys_Printf("MVDWrite_Begin: bad demo message type: %d\n", type); return false; // ERROR } new_mvd_msg = demo.frames[demo.parsecount&UPDATE_MASK].lasttype != type || demo.frames[demo.parsecount&UPDATE_MASK].lastto != to || demo.frames[demo.parsecount&UPDATE_MASK].lastsize + size > MAX_MVD_SIZE; if (new_mvd_msg) { // we need add mvd header for next message since "type" or "to" differ from previous message, // or it first message in this frame. MVD_MSG_WriteByte(0); // 0 milliseconds demo.frames[demo.parsecount&UPDATE_MASK].lasttype = type; demo.frames[demo.parsecount&UPDATE_MASK].lastto = to; demo.frames[demo.parsecount&UPDATE_MASK].lastsize = size; // set initial size switch (type) { case dem_all: MVD_MSG_WriteByte(dem_all); break; case dem_multiple: MVD_MSG_WriteByte(dem_multiple); MVD_MSG_WriteLong(to); break; case dem_single: case dem_stats: MVD_MSG_WriteByte(type | (to << 3)); // msg "type" and "to" incapsulated in one byte break; default: return false; // ERROR: wrong type } MVD_MSG_WriteLong(size); // msg size if (demo.frames[demo.parsecount&UPDATE_MASK]._buf_.overflowed) return false; // ERROR: overflow // THIS IS TRICKY, SORRY. // remember size offset, so latter we can access it demo.frames[demo.parsecount&UPDATE_MASK].lastsize_offset = demo.frames[demo.parsecount&UPDATE_MASK]._buf_.cursize - 4; } else { // we must alredy have mvd header here of previous message, so just change size in header byte *buf; int lastsize; demo.frames[demo.parsecount&UPDATE_MASK].lastsize += size; // THIS IS TRICKY, SORRY. // and here we use size offset lastsize = demo.frames[demo.parsecount&UPDATE_MASK].lastsize; buf = demo.frames[demo.parsecount&UPDATE_MASK]._buf_.data + demo.frames[demo.parsecount&UPDATE_MASK].lastsize_offset; buf[0] = lastsize&0xff; buf[1] = (lastsize>> 8)&0xff; buf[2] = (lastsize>>16)&0xff; buf[3] = (lastsize>>24)&0xff; } return (sv.mvdrecording ? true : false); } qbool MVDWrite_Begin (byte type, int to, int size) { if (!sv.mvdrecording) return false; if (!MVDWrite_BeginEx(type, to, size)) { Con_DPrintf("MVDWrite_Begin: error\n"); if (sv.mvdrecording) SV_MVDStop(4, false); // stop all mvd recording return false; } return (sv.mvdrecording ? true : false); } qbool MVDWrite_HiddenBlockBegin(int length) { return MVDWrite_Begin(dem_multiple, 0, length); } qbool MVDWrite_HiddenBlock(const void* data, int length) { if (MVDWrite_HiddenBlockBegin(length)) { MVD_SZ_Write(data, length); return true; } return false; } /* ==================== MVD_FrameDeltaTime Get frame time mark (delta between two demo frames). Also advance demo.prevtime. ==================== */ static byte MVD_FrameDeltaTime (double time1) { int msec; if (!sv.mvdrecording) return 0; msec = (time1 - demo.prevtime) * 1000; demo.prevtime += 0.001 * msec; if (msec > 255) msec = 255; if (msec < 2) msec = 0; // uh, why 0 but not 2? return (byte)msec; } /* ==================== MVD_WriteMessage ==================== */ static qbool MVD_WriteMessage (sizebuf_t *msg, byte msec) { int len; byte c; if (!sv.mvdrecording) return false; if (msg && msg->overflowed) return false; // ERROR c = msec; DemoWrite(&c, sizeof(c)); c = dem_all; DemoWrite (&c, sizeof(c)); if (msg && msg->cursize) { len = LittleLong (msg->cursize); DemoWrite (&len, 4); DemoWrite (msg->data, msg->cursize); } else { len = LittleLong (0); DemoWrite (&len, 4); } return (sv.mvdrecording ? true : false); } static void SV_MVDWritePausedTimeToStreams(demo_frame_t* frame) { // When writing out a paused frame, send a packet to let QTV keep delay in sync if (frame->paused) { mvddest_t* d; sizebuf_t msg; byte msg_buffer[128]; byte duration = frame->pause_duration; SZ_Init(&msg, msg_buffer, sizeof(msg_buffer)); MSG_WriteByte(&msg, 0); // 0: duration == 0, for demos MSG_WriteByte(&msg, dem_multiple); // 1: target of the packet MSG_WriteLong(&msg, 0); // 2- 5: 0 ... demo_multiple(0) => hidden packet MSG_WriteLong(&msg, LittleLong(sizeof(short) + sizeof(byte))); // 6-10: length = + MSG_WriteShort(&msg, LittleShort(mvdhidden_paused_duration)); // 11-12: tell QTV how much time has really passed MSG_WriteByte(&msg, duration); // 13: true ms value, as demo packets will have 0 for (d = demo.dest; d; d = d->nextdest) { if (d->desttype == DEST_STREAM) { DemoWriteDest(msg.data, msg.cursize, d); } } } } /* ==================== SV_MVDWritePackets Interpolates to get exact players position for current frame and writes packets to the disk/memory ==================== */ static qbool SV_MVDWritePacketsEx (int num) { demo_frame_t *frame, *nextframe; demo_client_t *cl, *nextcl = NULL, *last_cl; int i, j, flags; qbool valid; double time1, playertime, nexttime; vec3_t origin, angles; sizebuf_t msg; byte msg_buf[MAX_MVD_SIZE]; // data without mvd header byte msec; if (!sv.mvdrecording) return false; // allow overflow, but cancel demo recording in case of overflow SZ_InitEx(&msg, msg_buf, sizeof(msg_buf), true); if (num > demo.parsecount - demo.lastwritten + 1) num = demo.parsecount - demo.lastwritten + 1; // 'num' frames to write for ( ; num; num--, demo.lastwritten++) { SZ_Clear(&msg); frame = &demo.frames[demo.lastwritten&UPDATE_MASK]; time1 = frame->time; nextframe = frame; SV_MVDWritePausedTimeToStreams(frame); // find two frames // one before the exact time (time - msec) and one after, // then we can interpolte exact position for current frame for (i = 0, cl = frame->clients, last_cl = demo.clients; i < MAX_CLIENTS; i++, cl++, last_cl++) { if (cl->parsecount != demo.lastwritten) continue; // not valid if (!cl->parsecount && svs.clients[i].state != cs_spawned) continue; // not valid, occur on first frame nexttime = playertime = time1 - cl->sec; valid = false; for (j = demo.lastwritten+1; nexttime < time1 && j < demo.parsecount; j++) { nextframe = &demo.frames[j&UPDATE_MASK]; nextcl = &nextframe->clients[i]; if (nextcl->parsecount != j) break; // disconnected? if (nextcl->fixangle) break; // respawned, or walked into teleport, do not interpolate! if (!(nextcl->flags & DF_DEAD) && (cl->flags & DF_DEAD)) break; // respawned, do not interpolate nexttime = nextframe->time - nextcl->sec; if (nexttime >= time1) { // good, found what we were looking for valid = true; break; } } if (valid) { float f = 0; float z = nexttime - playertime; if ( z ) f = (time1 - nexttime) / z; for (j = 0; j < 3; j++) { angles[j] = AdjustAngle(cl->angles[j], nextcl->angles[j], 1.0 + f); origin[j] = nextcl->origin[j] + f * (nextcl->origin[j] - cl->origin[j]); } } else { VectorCopy(cl->origin, origin); VectorCopy(cl->angles, angles); } // now write it to buf flags = cl->flags; for (j = 0; j < 3; j++) if (origin[j] != last_cl->origin[j]) flags |= DF_ORIGIN << j; for (j = 0; j < 3; j++) if (angles[j] != last_cl->angles[j]) flags |= DF_ANGLES << j; if (cl->model != last_cl->model) flags |= DF_MODEL; if (cl->effects != last_cl->effects) flags |= DF_EFFECTS; if (cl->skinnum != last_cl->skinnum) flags |= DF_SKINNUM; if (cl->weaponframe != last_cl->weaponframe) flags |= DF_WEAPONFRAME; MSG_WriteByte (&msg, svc_playerinfo); MSG_WriteByte (&msg, i); MSG_WriteShort (&msg, flags); MSG_WriteByte (&msg, cl->frame); for (j = 0 ; j < 3 ; j++) if (flags & (DF_ORIGIN << j)) MSG_WriteCoord (&msg, origin[j]); for (j = 0 ; j < 3 ; j++) if (flags & (DF_ANGLES << j)) MSG_WriteAngle16 (&msg, angles[j]); if (flags & DF_MODEL) MSG_WriteByte (&msg, cl->model); if (flags & DF_SKINNUM) MSG_WriteByte (&msg, cl->skinnum); if (flags & DF_EFFECTS) MSG_WriteByte (&msg, cl->effects); if (flags & DF_WEAPONFRAME) MSG_WriteByte (&msg, cl->weaponframe); cl->flags = flags; // save in last_cl what we wrote to msg so later we can delta from it *last_cl = *cl; // struct copy } // get frame time mark (delta between two frames) msec = MVD_FrameDeltaTime(time1); if (demo.frames[demo.lastwritten&UPDATE_MASK]._buf_.overflowed) { Con_DPrintf("SV_MVDWritePackets: error: in _buf_.overflowed\n"); return false; // ERROR } // write cumulative data from different sources if (demo.frames[demo.lastwritten&UPDATE_MASK]._buf_.cursize) { // well, first byte must be milliseconds, set it then demo.frames[demo.lastwritten&UPDATE_MASK]._buf_.data[0] = msec; DemoWrite(demo.frames[demo.lastwritten&UPDATE_MASK]._buf_.data, demo.frames[demo.lastwritten&UPDATE_MASK]._buf_.cursize); msec = 0; // That matter, we wrote time mark, so next data(if any) in this frame follow with zero milliseconds offset, since it same frame. // NOTE: demo.frames[demo.lastwritten&UPDATE_MASK]._buf_.cursize possible to be zero, in this case we wrote msec below... } // Write data about players, if we have data. // also if we does't have data and did't wrote msec above, we wrote it here, even packet will be empty, this will break fuh, // but I think I correct, also it doubtfull what we did't wrote msec above. if (msg.cursize || msec) { if (!MVD_WriteMessage(&msg, msec)) { Con_DPrintf("SV_MVDWritePackets: error: in msg\n"); return false; // ERROR } } // { reset frame for future usage SZ_Clear(&demo.frames[demo.lastwritten&UPDATE_MASK]._buf_); demo.frames[demo.lastwritten&UPDATE_MASK].lastto = 0; demo.frames[demo.lastwritten&UPDATE_MASK].lasttype = 0; demo.frames[demo.lastwritten&UPDATE_MASK].lastsize = 0; demo.frames[demo.lastwritten&UPDATE_MASK].lastsize_offset = 0; // } if (!sv.mvdrecording) { Con_DPrintf("SV_MVDWritePackets: error: in sv.mvdrecording\n"); return false; // ERROR } } if (!sv.mvdrecording) return false; // ERROR if (demo.lastwritten > demo.parsecount) demo.lastwritten = demo.parsecount; return true; } qbool SV_MVDWritePackets (int num) { if (!sv.mvdrecording) return false; if (!SV_MVDWritePacketsEx(num)) { Con_DPrintf("SV_MVDWritePackets: error\n"); if (sv.mvdrecording) SV_MVDStop(4, false); // stop all mvd recording return false; } return (sv.mvdrecording ? true : false); } /* ==================== SV_InitRecord ==================== */ static mvddest_t *SV_InitRecordFile (char *name) { char *s; mvddest_t *dst; FILE *file; char path[MAX_OSPATH]; Con_DPrintf("SV_InitRecordFile: Demo name: \"%s\"\n", name); file = fopen (name, "wb"); if (!file) { Con_Printf ("ERROR: couldn't open \"%s\"\n", name); return NULL; } dst = (mvddest_t*) Q_malloc (sizeof(mvddest_t)); if (!(int)sv_demoUseCache.value) { dst->desttype = DEST_FILE; dst->file = file; dst->maxcachesize = 0; } else { dst->desttype = DEST_BUFFEREDFILE; dst->file = file; dst->maxcachesize = 1024 * (int) sv_demoCacheSize.value; dst->cache = (char *) Q_malloc (dst->maxcachesize); } s = name + strlen(name); while (*s != '/') s--; strlcpy(dst->name, s+1, sizeof(dst->name)); strlcpy(dst->path, sv_demoDir.string, sizeof(dst->path)); if ( !sv_silentrecord.value ) SV_BroadcastPrintf (PRINT_CHAT, "Server starts recording (%s):\n%s\n", (dst->desttype == DEST_BUFFEREDFILE) ? "memory" : "disk", s+1); Cvar_SetROM(&serverdemo, dst->name); strlcpy(path, name, MAX_OSPATH); strlcpy(path + strlen(path) - 3, "txt", MAX_OSPATH - strlen(path) + 3); if ((int)sv_demotxt.value) { FILE *f; char *text; if (sv_demotxt.value == 2) { if ((f = fopen (path, "a+t"))) fclose(f); // at least made empty file } else if ((f = fopen (path, "w+t"))) { text = SV_PrintTeams(); fwrite(text, strlen(text), 1, f); fflush(f); fclose(f); } } else Sys_remove(path); // force cache rebuild. FS_FlushFSHash(); return dst; } static void SV_AddLastDemo(void) { char *name = NULL; mvddest_t *d; for (d = demo.dest; d; d = d->nextdest) { if (d->desttype != DEST_STREAM && d->name[0]) { name = d->name; break; // we found file dest with non empty name, use it as last demo name } } if (name && name[0]) { size_t name_len = strlen(name) + 1; demo.lastdemospos = (demo.lastdemospos + 1) & 0xF; Q_free(demo.lastdemosname[demo.lastdemospos]); demo.lastdemosname[demo.lastdemospos] = (char *) Q_malloc(name_len); strlcpy(demo.lastdemosname[demo.lastdemospos], name, name_len); Con_DPrintf("SV_MVDStop: Demo name for 'cmd dl .': \"%s\"\n", demo.lastdemosname[demo.lastdemospos]); } } /* ==================== SV_MVDStop stop recording a demo ==================== */ void SV_MVDStop (int reason, qbool mvdonly) { static qbool instop = false; int numclosed; if (instop) { Con_Printf("SV_MVDStop: recursion\n"); return; } if (!sv.mvdrecording) { Con_Printf ("Not recording a demo.\n"); return; } instop = true; // SET TO TRUE, DON'T FORGET SET TO FALSE ON RETURN if (reason == 2 || reason == 3 || reason == 4) { if (reason == 4) mvdonly = false; // if serious error, then close all dests including qtv streams // stop and remove DestCloseAllFlush(true, mvdonly); if (!demo.dest) sv.mvdrecording = false; if (reason == 4) SV_BroadcastPrintf (PRINT_CHAT, "Error in MVD/QTV recording, recording stopped\n"); else if (reason == 3) SV_BroadcastPrintf (PRINT_CHAT, "QTV disconnected\n"); else { if ( !sv_silentrecord.value ) SV_BroadcastPrintf (PRINT_CHAT, "Server recording canceled, demo removed\n"); } Cvar_SetROM(&serverdemo, ""); instop = false; // SET TO FALSE return; } // write a disconnect message to the demo file // FIXME: qqshka: add clearup to be sure message will fit!!! if (MVDWrite_Begin(dem_all, 0, 2+strlen("EndOfDemo"))) { MVD_MSG_WriteByte (svc_disconnect); MVD_MSG_WriteString ("EndOfDemo"); } // finish up SV_MVDWritePackets(demo.parsecount - demo.lastwritten + 1); // last recorded demo's names for command "cmd dl . .." (maximum 15 dots) SV_AddLastDemo(); numclosed = DestCloseAllFlush(false, mvdonly); if (!demo.dest) sv.mvdrecording = false; if (numclosed) { if (!reason) { if ( !sv_silentrecord.value ) SV_BroadcastPrintf (PRINT_CHAT, "Server recording completed\n"); } else SV_BroadcastPrintf (PRINT_CHAT, "Server recording stopped\nMax demo size exceeded\n"); } Cvar_SetROM(&serverdemo, ""); instop = false; // SET TO FALSE } /* ==================== SV_MVDStop_f ==================== */ void SV_MVDStop_f (void) { SV_MVDStop(0, true); } /* ==================== SV_MVD_Cancel_f Stops recording, and removes the demo ==================== */ void SV_MVD_Cancel_f (void) { SV_MVDStop(2, true); } /* ==================== SV_WriteRecordMVDMessage ==================== */ static void SV_WriteRecordMVDMessage (sizebuf_t *msg) { int len; byte c; if (!sv.mvdrecording) return; if (!msg->cursize) return; c = 0; DemoWrite (&c, sizeof(c)); c = dem_all; DemoWrite (&c, sizeof(c)); len = LittleLong (msg->cursize); DemoWrite (&len, 4); DemoWrite (msg->data, msg->cursize); } /* ==================== SV_WriteRecordMVDStatsMessage ==================== */ static void SV_WriteRecordMVDStatsMessage (sizebuf_t *msg, int client) { int len; byte c; if (!sv.mvdrecording) return; if (!msg->cursize) return; if (client < 0 || client >= MAX_CLIENTS) return; c = 0; DemoWrite (&c, sizeof(c)); c = dem_stats | (client << 3) ; // msg "type" and "to" incapsulated in one byte DemoWrite (&c, sizeof(c)); len = LittleLong (msg->cursize); DemoWrite (&len, 4); DemoWrite (msg->data, msg->cursize); } /* static void SV_WriteSetMVDMessage (void) { int len; byte c; if (!sv.mvdrecording) return; c = 0; DemoWrite (&c, sizeof(c)); c = dem_set; DemoWrite (&c, sizeof(c)); len = LittleLong(0); DemoWrite (&len, 4); len = LittleLong(0); DemoWrite (&len, 4); } */ // mvd/qtv related stuff // Well, here is a chance what player connect after demo recording started, // so demo.info[edictnum - 1].model == player_model so SV_MVDWritePackets() will not wrote player model index, // so client during playback this demo will got invisible model, because model index will be 0. // Fixing that. // Btw, struct demo contain different client specific structs, may be they need clearing too, not sure. void MVD_PlayerReset(int player) { if (player < 0 || player >= MAX_CLIENTS) { // protect from lamers Con_Printf("MVD_PlayerReset: wrong player num %d\n", player); return; } memset(&(demo.clients[player]), 0, sizeof(demo.clients[0])); } qbool SV_MVD_Record (mvddest_t *dest, qbool mapchange) { int i; if (mapchange) { if (dest) // during mapchange dest must be NULL return false; } else { if (!dest) // in non mapchange dest must be not NULL return false; } // If we initialize MVD recording, then reset some data structs if (!sv.mvdrecording) { // this is either mapchange and we have QTV connected // or we just use /record or whatever command first time and here no recording yet // and here we memset() not whole demo_t struct, but part, // so demo.dest and demo.pendingdest is not overwriten memset(&demo, 0, (int)((uintptr_t)&(((demo_t *)0)->mem_set_point))); for (i = 0; i < UPDATE_BACKUP; i++) { // set up buffer for record in each frame SZ_InitEx(&demo.frames[i]._buf_, demo.frames[i]._buf__data, sizeof(demo.frames[0]._buf__data), true); } // set up buffer for non releable data SZ_InitEx(&demo.datagram, demo.datagram_data, sizeof(demo.datagram_data), true); } if (mapchange) { // // map change, sent initial stats to all dests // SV_MVD_SendInitialGamestate(NULL); } else { // // seems we initializing new dest, sent initial stats only to this dest // dest->nextdest = demo.dest; demo.dest = dest; SV_MVD_SendInitialGamestate(dest); } // done return true; } void SV_MVD_SendInitialGamestate(mvddest_t* dest) { sizebuf_t buf; unsigned char buf_data[MAX_MSGLEN]; unsigned int n; char* s, info[MAX_EXT_INFO_STRING]; client_t* player; edict_t* ent; char* gamedir; int i; if (!demo.dest) return; sv.mvdrecording = true; // NOTE: afaik set to false on map change, so restore it here demo.pingtime = demo.time = sv.time; singledest = dest; /*-------------------------------------------------*/ // serverdata // send the info about the new client to all connected clients SZ_Init(&buf, buf_data, sizeof(buf_data)); // send the serverdata gamedir = Info_ValueForKey(svs.info, "*gamedir"); if (!gamedir[0]) gamedir = "qw"; MSG_WriteByte(&buf, svc_serverdata); #ifdef FTE_PEXT_FLOATCOORDS //fix up extensions to match sv_bigcoords correctly. sorry for old clients not working. if (msg_coordsize == 4) demo.recorder.fteprotocolextensions |= FTE_PEXT_FLOATCOORDS; else demo.recorder.fteprotocolextensions &= ~FTE_PEXT_FLOATCOORDS; #endif #ifdef PROTOCOL_VERSION_FTE if (demo.recorder.fteprotocolextensions) { MSG_WriteLong(&buf, PROTOCOL_VERSION_FTE); MSG_WriteLong(&buf, demo.recorder.fteprotocolextensions); } #endif #ifdef PROTOCOL_VERSION_FTE2 if (demo.recorder.fteprotocolextensions2) { MSG_WriteLong(&buf, PROTOCOL_VERSION_FTE2); MSG_WriteLong(&buf, demo.recorder.fteprotocolextensions2); } #endif #ifdef PROTOCOL_VERSION_MVD1 demo.recorder.mvdprotocolextensions1 |= MVD_PEXT1_HIDDEN_MESSAGES; if (demo.recorder.mvdprotocolextensions1) { MSG_WriteLong(&buf, PROTOCOL_VERSION_MVD1); MSG_WriteLong(&buf, demo.recorder.mvdprotocolextensions1); } #endif MSG_WriteLong (&buf, PROTOCOL_VERSION); MSG_WriteLong (&buf, svs.spawncount); MSG_WriteString (&buf, gamedir); MSG_WriteFloat (&buf, sv.time); // send full levelname MSG_WriteString (&buf, PR_GetEntityString(sv.edicts->v->message)); // send the movevars MSG_WriteFloat(&buf, movevars.gravity); MSG_WriteFloat(&buf, movevars.stopspeed); MSG_WriteFloat(&buf, movevars.maxspeed); MSG_WriteFloat(&buf, movevars.spectatormaxspeed); MSG_WriteFloat(&buf, movevars.accelerate); MSG_WriteFloat(&buf, movevars.airaccelerate); MSG_WriteFloat(&buf, movevars.wateraccelerate); MSG_WriteFloat(&buf, movevars.friction); MSG_WriteFloat(&buf, movevars.waterfriction); MSG_WriteFloat(&buf, movevars.entgravity); // send music MSG_WriteByte (&buf, svc_cdtrack); MSG_WriteByte (&buf, 0); // none in demos // send server info string MSG_WriteByte (&buf, svc_stufftext); MSG_WriteString (&buf, va("fullserverinfo \"%s\"\n", svs.info) ); // flush packet SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); // soundlist MSG_WriteByte (&buf, svc_soundlist); MSG_WriteByte (&buf, 0); n = 0; s = sv.sound_precache[n+1]; while (s) { MSG_WriteString (&buf, s); if (buf.cursize > MAX_MSGLEN/2) { MSG_WriteByte (&buf, 0); MSG_WriteByte (&buf, n); SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); MSG_WriteByte (&buf, svc_soundlist); MSG_WriteByte (&buf, n + 1); } n++; s = sv.sound_precache[n+1]; } if (buf.cursize) { MSG_WriteByte (&buf, 0); MSG_WriteByte (&buf, 0); SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); } // modellist MSG_WriteByte (&buf, svc_modellist); MSG_WriteByte (&buf, 0); n = 0; s = sv.model_precache[n+1]; while (s) { MSG_WriteString (&buf, s); if (buf.cursize > MAX_MSGLEN/2) { MSG_WriteByte (&buf, 0); MSG_WriteByte (&buf, n); SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); MSG_WriteByte (&buf, svc_modellist); MSG_WriteByte (&buf, n + 1); } n++; s = sv.model_precache[n+1]; } if (buf.cursize) { MSG_WriteByte (&buf, 0); MSG_WriteByte (&buf, 0); SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); } // static entities { int i, j; entity_state_t from = { 0 }; for (i = 0; i < sv.static_entity_count; ++i) { entity_state_t* s = &sv.static_entities[i]; if (buf.cursize >= MAX_MSGLEN/2) { SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); } if (demo.recorder.fteprotocolextensions & FTE_PEXT_SPAWNSTATIC2) { MSG_WriteByte(&buf, svc_fte_spawnstatic2); SV_WriteDelta(&demo.recorder, &from, s, &buf, true); } else if (s->modelindex < 256) { MSG_WriteByte(&buf, svc_spawnstatic); MSG_WriteByte(&buf, s->modelindex); MSG_WriteByte(&buf, s->frame); MSG_WriteByte(&buf, s->colormap); MSG_WriteByte(&buf, s->skinnum); for (j = 0; j < 3; ++j) { MSG_WriteCoord(&buf, s->origin[j]); MSG_WriteAngle(&buf, s->angles[j]); } } } } // entity baselines { static entity_state_t empty_baseline = { 0 }; int i, j; for (i = 0; i < sv.num_baseline_edicts; ++i) { edict_t* svent = EDICT_NUM(i); entity_state_t* s = &svent->e.baseline; if (buf.cursize >= MAX_MSGLEN/2) { SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); } if (!s->number || !s->modelindex || !memcmp(s, &empty_baseline, sizeof(empty_baseline))) { continue; } if (demo.recorder.fteprotocolextensions & FTE_PEXT_SPAWNSTATIC2) { MSG_WriteByte(&buf, svc_fte_spawnbaseline2); SV_WriteDelta(&demo.recorder, &empty_baseline, s, &buf, true); } else if (s->modelindex < 256) { MSG_WriteByte(&buf, svc_spawnbaseline); MSG_WriteShort(&buf, i); MSG_WriteByte(&buf, s->modelindex); MSG_WriteByte(&buf, s->frame); MSG_WriteByte(&buf, s->colormap); MSG_WriteByte(&buf, s->skinnum); for (j = 0; j < 3; j++) { MSG_WriteCoord(&buf, s->origin[j]); MSG_WriteAngle(&buf, s->angles[j]); } } } } // prespawn for (n = 0; n < sv.num_signon_buffers; n++) { if (buf.cursize+sv.signon_buffer_size[n] > MAX_MSGLEN/2) { SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); } SZ_Write (&buf, sv.signon_buffers[n], sv.signon_buffer_size[n]); } if (buf.cursize > MAX_MSGLEN/2) { SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); } MSG_WriteByte (&buf, svc_stufftext); MSG_WriteString (&buf, va("cmd spawn %i 0\n",svs.spawncount) ); if (buf.cursize) { SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); } // send current status of all other players for (i = 0; i < MAX_CLIENTS; i++) { player = svs.clients + i; // there spectators NOT ignored, since this info required, at least userinfo MSG_WriteByte (&buf, svc_updatefrags); MSG_WriteByte (&buf, i); MSG_WriteShort (&buf, player->old_frags); MSG_WriteByte (&buf, svc_updateping); MSG_WriteByte (&buf, i); MSG_WriteShort (&buf, SV_CalcPing(player)); MSG_WriteByte (&buf, svc_updatepl); MSG_WriteByte (&buf, i); MSG_WriteByte (&buf, player->lossage); MSG_WriteByte (&buf, svc_updateentertime); MSG_WriteByte (&buf, i); MSG_WriteFloat (&buf, SV_ClientGameTime(player)); Info_ReverseConvert(&player->_userinfoshort_ctx_, info, sizeof(info)); Info_RemovePrefixedKeys (info, '_'); // server passwords, etc MSG_WriteByte (&buf, svc_updateuserinfo); MSG_WriteByte (&buf, i); MSG_WriteLong (&buf, player->userid); MSG_WriteString (&buf, info); if (buf.cursize > MAX_MSGLEN/2) { SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); } } if (buf.cursize) { SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); } // this set proper model origin and angles etc for players for (i = 0; i < MAX_CLIENTS; i++) { vec3_t origin, angles; int j, flags; player = svs.clients + i; ent = player->edict; if (player->state != cs_spawned) continue; if (player->spectator) continue; // ignore specs flags = (DF_ORIGIN << 0) | (DF_ORIGIN << 1) | (DF_ORIGIN << 2) | (DF_ANGLES << 0) | (DF_ANGLES << 1) | (DF_ANGLES << 2) | DF_EFFECTS | DF_SKINNUM | (ent->v->health <= 0 ? DF_DEAD : 0) | (ent->v->mins[2] != -24 ? DF_GIB : 0) | DF_WEAPONFRAME | DF_MODEL; VectorCopy(ent->v->origin, origin); VectorCopy(ent->v->angles, angles); angles[0] *= -3; #ifdef USE_PR2 if( player->isBot ) VectorCopy(ent->v->v_angle, angles); #endif angles[2] = 0; // no roll angle if (ent->v->health <= 0) { // don't show the corpse looking around... angles[0] = 0; angles[1] = ent->v->angles[1]; angles[2] = 0; } MSG_WriteByte (&buf, svc_playerinfo); MSG_WriteByte (&buf, i); MSG_WriteShort (&buf, flags); MSG_WriteByte (&buf, ent->v->frame); for (j = 0 ; j < 3 ; j++) if (flags & (DF_ORIGIN << j)) MSG_WriteCoord (&buf, origin[j]); for (j = 0 ; j < 3 ; j++) if (flags & (DF_ANGLES << j)) MSG_WriteAngle16 (&buf, angles[j]); if (flags & DF_MODEL) MSG_WriteByte (&buf, ent->v->modelindex); if (flags & DF_SKINNUM) MSG_WriteByte (&buf, ent->v->skin); if (flags & DF_EFFECTS) MSG_WriteByte (&buf, ent->v->effects); if (flags & DF_WEAPONFRAME) MSG_WriteByte (&buf, ent->v->weaponframe); if (buf.cursize > MAX_MSGLEN/2) { SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); } } // we really need clear buffer before sending stats if (buf.cursize) { SV_WriteRecordMVDMessage (&buf); SZ_Clear (&buf); } // send stats for (i = 0; i < MAX_CLIENTS; i++) { int stats[MAX_CL_STATS]; int j; player = svs.clients + i; ent = player->edict; if (player->state != cs_spawned) continue; if (player->spectator) continue; memset(stats, 0, sizeof(stats)); stats[STAT_HEALTH] = ent->v->health; stats[STAT_WEAPON] = SV_ModelIndex(PR_GetEntityString(ent->v->weaponmodel)); stats[STAT_AMMO] = ent->v->currentammo; stats[STAT_ARMOR] = ent->v->armorvalue; stats[STAT_SHELLS] = ent->v->ammo_shells; stats[STAT_NAILS] = ent->v->ammo_nails; stats[STAT_ROCKETS] = ent->v->ammo_rockets; stats[STAT_CELLS] = ent->v->ammo_cells; stats[STAT_ACTIVEWEAPON] = ent->v->weapon; if (ent->v->health > 0) // viewheight for PF_DEAD & PF_GIB is hardwired stats[STAT_VIEWHEIGHT] = ent->v->view_ofs[2]; // stuff the sigil bits into the high bits of items for sbar stats[STAT_ITEMS] = (int) ent->v->items | ((int) PR_GLOBAL(serverflags) << 28); for (j = 0; j < MAX_CL_STATS; j++) { if (stats[j] >= 0 && stats[j] <= 255) { MSG_WriteByte(&buf, svc_updatestat); MSG_WriteByte(&buf, j); MSG_WriteByte(&buf, stats[j]); } else { MSG_WriteByte(&buf, svc_updatestatlong); MSG_WriteByte(&buf, j); MSG_WriteLong(&buf, stats[j]); } } if (buf.cursize) { SV_WriteRecordMVDStatsMessage(&buf, i); SZ_Clear (&buf); } } // above stats writing must clear buffer if (buf.cursize) { Sys_Error("SV_MVD_SendInitialGamestate: buf.cursize %d", buf.cursize); } // send all current light styles for (i=0 ; i ==================== */ void SV_MVD_Record_f (void) { int c; char name[MAX_OSPATH+MAX_DEMO_NAME]; char newname[MAX_DEMO_NAME]; c = Cmd_Argc(); if (c != 2) { Con_Printf ("record \n"); return; } if (sv.state != ss_active) { Con_Printf ("Not active yet.\n"); return; } // clear old demos if (!SV_DirSizeCheck()) return; strlcpy(newname, va("%s%s%s", sv_demoPrefix.string, SV_CleanName((unsigned char*)Cmd_Argv(1)), sv_demoSuffix.string), sizeof(newname) - 4); Sys_mkdir(va("%s/%s", fs_gamedir, sv_demoDir.string)); snprintf (name, sizeof(name), "%s/%s/%s", fs_gamedir, sv_demoDir.string, newname); if ((c = strlen(name)) > 3) if (strcmp(name + c - 4, ".mvd")) strlcat(name, ".mvd", sizeof(name)); // // open the demo file and start recording // SV_MVD_Record (SV_InitRecordFile(name), false); } /* ==================== SV_MVDEasyRecord_f easyrecord [demoname] ==================== */ void SV_MVDEasyRecord_f (void) { int c; char name[MAX_DEMO_NAME]; char name2[MAX_OSPATH*7]; // scream char name4[MAX_OSPATH*7]; // scream int i; dir_t dir; char *name3; c = Cmd_Argc(); if (c > 2) { Con_Printf ("easyrecord [demoname]\n"); return; } if (!SV_DirSizeCheck()) // clear old demos return; if (c == 2) strlcpy (name, Cmd_Argv(1), sizeof(name)); else { i = Dem_CountPlayers(); if ((int)teamplay.value >= 1 && i > 2) { // Teamplay snprintf (name, sizeof(name), "%don%d_", Dem_CountTeamPlayers(Dem_Team(1)), Dem_CountTeamPlayers(Dem_Team(2))); if ((int)sv_demoExtraNames.value > 0) { strlcat (name, va("[%s]_%s_vs_[%s]_%s_%s", Dem_Team(1), Dem_PlayerNameTeam(Dem_Team(1)), Dem_Team(2), Dem_PlayerNameTeam(Dem_Team(2)), sv.mapname), sizeof(name)); } else strlcat (name, va("%s_vs_%s_%s", Dem_Team(1), Dem_Team(2), sv.mapname), sizeof(name)); } else { if (i == 2) { // Duel snprintf (name, sizeof(name), "duel_%s_vs_%s_%s", Dem_PlayerName(1), Dem_PlayerName(2), sv.mapname); } else { // FFA snprintf (name, sizeof(name), "ffa_%s(%d)", sv.mapname, i); } } } // <- // Make sure the filename doesn't contain illegal characters strlcpy(name, va("%s%s%s", sv_demoPrefix.string, SV_CleanName((unsigned char*)name), sv_demoSuffix.string), MAX_DEMO_NAME); strlcpy(name2, name, sizeof(name2)); Sys_mkdir(va("%s/%s", fs_gamedir, sv_demoDir.string)); // FIXME: very SLOW if (!(name3 = quote(name2))) return; dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string), va("^%s%s", name3, sv_demoRegexp.string), SORT_NO); Q_free(name3); for (i = 1; dir.numfiles; ) { snprintf(name2, sizeof(name2), "%s_%02i", name, i++); if (!(name3 = quote(name2))) return; dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string), va("^%s%s", name3, sv_demoRegexp.string), SORT_NO); Q_free(name3); } strlcpy(name4, name2, sizeof(name4)); snprintf(name2, sizeof(name2), "%s", va("%s/%s/%s.mvd", fs_gamedir, sv_demoDir.string, name2)); snprintf(name4, sizeof(name4), "%s", va("%s/%s.xml", sv_demoDir.string, name4)); Cvar_Set(&extralogname, name4); SV_MVD_Record (SV_InitRecordFile(name2), false); } //============================================================ static void MVD_Init (void) { int p, size = DEMO_CACHE_MIN_SIZE; memset(&demo, 0, sizeof(demo)); // clear whole demo struct at least once Cvar_Register (&sv_demofps); Cvar_Register (&sv_demoIdlefps); Cvar_Register (&sv_demoPings); Cvar_Register (&sv_demoUseCache); Cvar_Register (&sv_demoCacheSize); Cvar_Register (&sv_demoMaxSize); Cvar_Register (&sv_demoMaxDirSize); Cvar_Register (&sv_demoClearOld); //bliP: 24/9 clear old demos Cvar_Register (&sv_demoDir); Cvar_Register (&sv_demoDirAlt); Cvar_Register (&sv_demoPrefix); Cvar_Register (&sv_demoSuffix); Cvar_Register (&sv_onrecordfinish); Cvar_Register (&sv_ondemoremove); Cvar_Register (&sv_demotxt); Cvar_Register (&sv_demoExtraNames); Cvar_Register (&sv_demoRegexp); Cvar_Register (&sv_silentrecord); Cvar_Register (&extralogname); p = SV_CommandLineDemoCacheArgument(); if (p) { if (p < COM_Argc()-1) size = Q_atoi (COM_Argv(p+1)) * 1024; else Sys_Error ("MVD_Init: you must specify a size in KB after -democache"); } if (size < DEMO_CACHE_MIN_SIZE) { Con_Printf("Minimum memory size for demo cache is %dk\n", DEMO_CACHE_MIN_SIZE / 1024); size = DEMO_CACHE_MIN_SIZE; } Cvar_SetROM(&sv_demoCacheSize, va("%d", size/1024)); CleanName_Init(); } void SV_UserCmdTrace_f(void) { const char* user = Cmd_Argv(1); const char* option_ = Cmd_Argv(2); qbool option = false; int uid, i; if (Cmd_Argc() != 3) { Con_Printf("Usage: %s userid (on | off)\n", Cmd_Argv(0)); return; } if (!strcmp(option_, "on")) { option = true; } else if (strcmp(option_, "off")) { Con_Printf("Usage: %s userid (on | off)\n", Cmd_Argv(0)); return; } uid = atoi(user); if (!uid) { Con_Printf("Usage: %s userid (on | off)\n", Cmd_Argv(0)); return; } for (i = 0; i < MAX_CLIENTS; i++) { if (!svs.clients[i].state) { continue; } if (svs.clients[i].userid == uid) { svs.clients[i].mvd_write_usercmds = option; return; } } Con_Printf("Couldn't find userid %d\n", uid); return; } void SV_MVDInit(void) { MVD_Init(); #ifdef SERVERONLY // name clashes with client. // would be nice to prefix it with sv_demo*, // but mods use it like that already, so we keep it for backward compatibility. Cmd_AddCommand ("record", SV_MVD_Record_f); Cmd_AddCommand ("easyrecord", SV_MVDEasyRecord_f); Cmd_AddCommand ("stop", SV_MVDStop_f); #endif // that how thouse commands should be called. Cmd_AddCommand ("sv_demorecord", SV_MVD_Record_f); Cmd_AddCommand ("sv_demoeasyrecord",SV_MVDEasyRecord_f); Cmd_AddCommand ("sv_demostop", SV_MVDStop_f); // that one does not clashes with client, but keep name for backward compatibility. Cmd_AddCommand ("cancel", SV_MVD_Cancel_f); // that how thouse commands should be called. Cmd_AddCommand ("sv_democancel", SV_MVD_Cancel_f); // this ones prefixed OK. Cmd_AddCommand ("sv_lastscores", SV_LastScores_f); Cmd_AddCommand ("sv_demolist", SV_DemoList_f); Cmd_AddCommand ("sv_demolistr", SV_DemoListRegex_f); Cmd_AddCommand ("sv_demolistregex", SV_DemoListRegex_f); Cmd_AddCommand ("sv_demoremove", SV_MVDRemove_f); Cmd_AddCommand ("sv_demonumremove", SV_MVDRemoveNum_f); Cmd_AddCommand ("sv_demoinfoadd", SV_MVDInfoAdd_f); Cmd_AddCommand ("sv_demoinforemove",SV_MVDInfoRemove_f); Cmd_AddCommand ("sv_demoinfo", SV_MVDInfo_f); Cmd_AddCommand ("sv_demoembedinfo", SV_MVDEmbedInfo_f); // not prefixed. Cmd_AddCommand ("script", SV_Script_f); Cmd_AddCommand ("sv_usercmdtrace", SV_UserCmdTrace_f); SV_QTV_Init(); } const char* SV_MVDDemoName(void) { mvddest_t* d; for (d = demo.dest; d; d = d->nextdest) { if (d->desttype == DEST_STREAM) { continue; // streams are not saved on to HDD, so ignore it... } if (d->name[0]) { return d->name; } } return NULL; } #endif // !CLIENTONLY mvdsv-0.35/src/sv_demo_misc.c000066400000000000000000000626171427146041000162150ustar00rootroot00000000000000/* 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 included (GNU.txt) 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. */ // sv_demo_misc.c - misc demo related stuff, helpers #ifndef CLIENTONLY #include "qwsvdef.h" #ifndef SERVERONLY #include "pcre.h" #endif #define MAX_DEMOINFO_SIZE (1024 * 200) static char chartbl[256]; /* ==================== CleanName_Init sets chararcter table for quake text->filename translation ==================== */ void CleanName_Init (void) { int i; for (i = 0; i < 256; i++) chartbl[i] = (((i&127) < 'a' || (i&127) > 'z') && ((i&127) < '0' || (i&127) > '9')) ? '_' : (i&127); // special cases // numbers for (i = 18; i < 29; i++) chartbl[i] = chartbl[i + 128] = i + 30; // allow lowercase only for (i = 'A'; i <= 'Z'; i++) chartbl[i] = chartbl[i+128] = i + 'a' - 'A'; // brackets chartbl[29] = chartbl[29+128] = chartbl[128] = '('; chartbl[31] = chartbl[31+128] = chartbl[130] = ')'; chartbl[16] = chartbl[16 + 128]= '['; chartbl[17] = chartbl[17 + 128] = ']'; // dot chartbl[5] = chartbl[14] = chartbl[15] = chartbl[28] = chartbl[46] = '.'; chartbl[5 + 128] = chartbl[14 + 128] = chartbl[15 + 128] = chartbl[28 + 128] = chartbl[46 + 128] = '.'; // ! chartbl[33] = chartbl[33 + 128] = '!'; // # chartbl[35] = chartbl[35 + 128] = '#'; // % chartbl[37] = chartbl[37 + 128] = '%'; // & chartbl[38] = chartbl[38 + 128] = '&'; // ' chartbl[39] = chartbl[39 + 128] = '\''; // ( chartbl[40] = chartbl[40 + 128] = '('; // ) chartbl[41] = chartbl[41 + 128] = ')'; // + chartbl[43] = chartbl[43 + 128] = '+'; // - chartbl[45] = chartbl[45 + 128] = '-'; // @ chartbl[64] = chartbl[64 + 128] = '@'; // ^ // chartbl[94] = chartbl[94 + 128] = '^'; chartbl[91] = chartbl[91 + 128] = '['; chartbl[93] = chartbl[93 + 128] = ']'; chartbl[16] = chartbl[16 + 128] = '['; chartbl[17] = chartbl[17 + 128] = ']'; chartbl[123] = chartbl[123 + 128] = '{'; chartbl[125] = chartbl[125 + 128] = '}'; } /* ==================== SV_CleanName Cleans the demo name, removes restricted chars, makes name lowercase ==================== */ char *SV_CleanName (unsigned char *name) { static char text[1024]; char *out = text; if (!name || !*name) { *out = '\0'; return text; } *out = chartbl[*name++]; while (*name && ((out - text) < (int) sizeof(text))) if (*out == '_' && chartbl[*name] == '_') name++; else *++out = chartbl[*name++]; *++out = 0; return text; } /* ==================== SV_DirSizeCheck Deletes sv_demoClearOld files from demo dir if out of space ==================== */ qbool SV_DirSizeCheck (void) { dir_t dir; file_t *list; int n; if ((int)sv_demoMaxDirSize.value) { dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string), ".*", SORT_NO/*BY_DATE*/); if ((float)dir.size > sv_demoMaxDirSize.value * 1024) { if ((int)sv_demoClearOld.value <= 0) { Con_Printf("Insufficient directory space, increase sv_demoMaxDirSize\n"); return false; } list = dir.files; n = (int) sv_demoClearOld.value; Con_Printf("Clearing %d old demos\n", n); // HACK!!! HACK!!! HACK!!! if ((int)sv_demotxt.value) // if our server record demos and txts, then to remove n <<= 1; // 50 demos, we have to remove 50 demos and 50 txts = 50*2 = 100 files qsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_date); for (; list->name[0] && n > 0; list++) { if (list->isdir) continue; Sys_remove(va("%s/%s/%s", fs_gamedir, sv_demoDir.string, list->name)); //Con_Printf("Remove %d - %s/%s/%s\n", n, fs_gamedir, sv_demoDir.string, list->name); n--; } // force cache rebuild. FS_FlushFSHash(); } } return true; } void Run_sv_demotxt_and_sv_onrecordfinish (const char *dest_name, const char *dest_path, qbool destroyfiles) { char path[MAX_OSPATH]; snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, dest_path, dest_name); strlcpy(path + strlen(path) - 3, "txt", MAX_OSPATH - strlen(path) + 3); if ((int)sv_demotxt.value && !destroyfiles) // dont keep txt's for deleted demos { FILE *f; char *text; if (sv_demotxt.value == 2) { if ((f = fopen (path, "a+t"))) fclose(f); // at least made empty file, but do not owerwite } else if ((f = fopen (path, "w+t"))) { text = SV_PrintTeams(); fwrite(text, strlen(text), 1, f); fflush(f); fclose(f); } } if (sv_onrecordfinish.string[0] && !destroyfiles) // dont gzip deleted demos { extern redirect_t sv_redirected; redirect_t old = sv_redirected; char *p; if ((p = strchr(sv_onrecordfinish.string, ' ')) != NULL) *p = 0; // strip parameters strlcpy(path, dest_name, sizeof(path)); #ifdef SERVERONLY COM_StripExtension(path); #else COM_StripExtension(path, path, sizeof(path)); #endif sv_redirected = RD_NONE; // onrecord script is called always from the console Cmd_TokenizeString(va("script %s \"%s\" \"%s\" %s", sv_onrecordfinish.string, dest_path, path, p != NULL ? p+1 : "")); if (p) *p = ' '; // restore params SV_Script_f(); sv_redirected = old; } // force cache rebuild. FS_FlushFSHash(); } char *SV_PrintTeams (void) { char *teams[MAX_CLIENTS]; int i, j, numcl = 0, numt = 0, scores; client_t *clients[MAX_CLIENTS]; char buf[2048]; static char lastscores[2048]; extern cvar_t teamplay; date_t date; SV_TimeOfDay(&date, "%a %b %d, %H:%M:%S %Y"); // count teams and players for (i=0; i < MAX_CLIENTS; i++) { if (svs.clients[i].state != cs_spawned) continue; if (svs.clients[i].spectator) continue; clients[numcl++] = &svs.clients[i]; for (j = 0; j < numt; j++) if (!strcmp(svs.clients[i].team, teams[j])) break; if (j != numt) continue; teams[numt++] = svs.clients[i].team; } // create output lastscores[0] = 0; snprintf(buf, sizeof(buf), "date %s\nmap %s\nteamplay %d\ndeathmatch %d\ntimelimit %d\n", date.str, sv.mapname, (int)teamplay.value, (int)deathmatch.value, (int)timelimit.value); if (numcl == 2) // duel { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "player1: %s (%i)\nplayer2: %s (%i)\n", clients[0]->name, clients[0]->old_frags, clients[1]->name, clients[1]->old_frags); snprintf(lastscores, sizeof(lastscores), "duel: %s vs %s @ %s - %i:%i\n", clients[0]->name, clients[1]->name, sv.mapname, clients[0]->old_frags, clients[1]->old_frags); } else if (!(int)teamplay.value) // ffa { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "players:\n"); snprintf(lastscores, sizeof(lastscores), "ffa:"); for (i = 0; i < numcl; i++) { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %s (%i)\n", clients[i]->name, clients[i]->old_frags); snprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores), " %s(%i)", clients[i]->name, clients[i]->old_frags); } snprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores), " @ %s\n", sv.mapname); } else { // teamplay snprintf(lastscores, sizeof(lastscores), "tp:"); for (j = 0; j < numt; j++) { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "team[%i] %s:\n", j, teams[j]); snprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores), "%s[", teams[j]); scores = 0; for (i = 0; i < numcl; i++) if (!strcmp(clients[i]->team, teams[j])) { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %s (%i)\n", clients[i]->name, clients[i]->old_frags); snprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores), " %s(%i) ", clients[i]->name, clients[i]->old_frags); scores += clients[i]->old_frags; } snprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores), "](%i) ", scores); } snprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores), "@ %s\n", sv.mapname); } Q_normalizetext(buf); Q_normalizetext(lastscores); strlcat(lastscores, buf, sizeof(lastscores)); return lastscores; } void SV_DemoList (qbool use_regex) { mvddest_t *d; dir_t dir; file_t *list; float free_space; int i, j, n; int files[MAX_DIRFILES + 1]; int r; pcre *preg; const char *errbuf; memset(files, 0, sizeof(files)); Con_Printf("Listing content of %s/%s/%s\n", fs_gamedir, sv_demoDir.string, sv_demoRegexp.string); dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string), sv_demoRegexp.string, SORT_BY_DATE); list = dir.files; if (!list->name[0]) { Con_Printf("no demos\n"); } for (i = 1, n = 0; list->name[0]; list++, i++) { for (j = 1; j < Cmd_Argc(); j++) { if (use_regex) { if (!(preg = pcre_compile(Q_normalizetext(Cmd_Argv(j)), PCRE_CASELESS, &errbuf, &r, NULL))) { Con_Printf("Sys_listdir: pcre_compile(%s) error: %s at offset %d\n", Cmd_Argv(j), errbuf, r); pcre_free(preg); break; } switch (r = pcre_exec(preg, NULL, list->name, strlen(list->name), 0, 0, NULL, 0)) { case 0: pcre_free(preg); continue; case PCRE_ERROR_NOMATCH: break; default: Con_Printf("Sys_listdir: pcre_exec(%s, %s) error code: %d\n", Cmd_Argv(j), list->name, r); } pcre_free(preg); break; } else if (strstr(list->name, Cmd_Argv(j)) == NULL) break; } if (Cmd_Argc() == j) { files[n++] = i; } } list = dir.files; for (j = (GameStarted() && n > 100) ? n - 100 : 0; files[j]; j++) { i = files[j]; if ((d = DestByName(list[i - 1].name))) Con_Printf("*%4d: %s (%dk)\n", i, list[i - 1].name, d->totalsize / 1024); else Con_Printf("%4d: %s (%dk)\n", i, list[i - 1].name, list[i - 1].size / 1024); } for (d = demo.dest; d; d = d->nextdest) { if (d->desttype == DEST_STREAM) continue; // streams are not saved on to HDD, so inogre it... dir.size += d->totalsize; } Con_Printf("\ndirectory size: %.1fMB\n", (float)dir.size / (1024 * 1024)); if ((int)sv_demoMaxDirSize.value) { free_space = (sv_demoMaxDirSize.value * 1024 - dir.size) / (1024 * 1024); if (free_space < 0) free_space = 0; Con_Printf("space available: %.1fMB\n", free_space); } } void SV_DemoList_f (void) { SV_DemoList (false); } void SV_DemoListRegex_f (void) { SV_DemoList (true); } char *SV_MVDNum (int num) { file_t *list; dir_t dir; if (!num) return NULL; // last recorded demo's names for command "cmd dl . .." (maximum 15 dots) if (num & 0xFF000000) { char *name = demo.lastdemosname[(demo.lastdemospos - (num >> 24) + 1) & 0xF]; char *name2; int c; if (!(name2 = quote(name))) return NULL; if ((c = strlen(name2)) > 5) name2[c - 5] = '\0'; // crop quoted extension '\.mvd' dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string), va("^%s%s", name2, sv_demoRegexp.string), SORT_NO); list = dir.files; if (dir.numfiles > 1) { Con_Printf("SV_MVDNum: where are %d demos with name: %s%s\n", dir.numfiles, name2, sv_demoRegexp.string); } if (!dir.numfiles) { Con_Printf("SV_MVDNum: where are no demos with name: %s%s\n", name2, sv_demoRegexp.string); return NULL; } Q_free(name2); //Con_Printf("%s", dir.files[0].name); return dir.files[0].name; } dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string), sv_demoRegexp.string, SORT_BY_DATE); list = dir.files; if (num & 0x00800000) { num |= 0xFF000000; num += dir.numfiles; } else { --num; } if (num > dir.numfiles) return NULL; while (list->name[0] && num) { list++; num--; } return list->name[0] ? list->name : NULL; } #define OVECCOUNT 3 char *SV_MVDName2Txt (const char *name) { char s[MAX_OSPATH]; int len; int r, ovector[OVECCOUNT]; pcre *preg; const char *errbuf; if (!name) return NULL; if (!*name) return NULL; strlcpy(s, name, MAX_OSPATH); len = strlen(s); if (!(preg = pcre_compile(sv_demoRegexp.string, PCRE_CASELESS, &errbuf, &r, NULL))) { Con_Printf("SV_MVDName2Txt: pcre_compile(%s) error: %s at offset %d\n", sv_demoRegexp.string, errbuf, r); pcre_free(preg); return NULL; } r = pcre_exec(preg, NULL, s, len, 0, 0, ovector, OVECCOUNT); pcre_free(preg); if (r < 0) { switch (r) { case PCRE_ERROR_NOMATCH: return NULL; default: Con_Printf("SV_MVDName2Txt: pcre_exec(%s, %s) error code: %d\n", sv_demoRegexp.string, s, r); return NULL; } } else { if (ovector[0] + 5 > MAX_OSPATH) len = MAX_OSPATH - 5; else len = ovector[0]; } s[len++] = '.'; s[len++] = 't'; s[len++] = 'x'; s[len++] = 't'; s[len] = '\0'; //Con_Printf("%d) %s, %s\n", r, name, s); return va("%s", s); } static char *SV_MVDTxTNum (int num) { return SV_MVDName2Txt (SV_MVDNum(num)); } void SV_MVDRemove_f (void) { char name[MAX_DEMO_NAME], *ptr; char path[MAX_OSPATH]; int i; if (Cmd_Argc() != 2) { Con_Printf("rmdemo - removes the demo\nrmdemo * - removes demo with in the name\nrmdemo * - removes all demos\n"); return; } ptr = Cmd_Argv(1); if (*ptr == '*') { dir_t dir; file_t *list; // remove all demos with specified token ptr++; dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string), sv_demoRegexp.string, SORT_BY_DATE); list = dir.files; for (i = 0;list->name[0]; list++) { if (strstr(list->name, ptr)) { if (sv.mvdrecording && DestByName(list->name)/*!strcmp(list->name, demo.name)*/) SV_MVDStop_f(); // FIXME: probably we must stop not all demos, but only partial dest // stop recording first; snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, list->name); if (!Sys_remove(path)) { Con_Printf("removing %s...\n", list->name); i++; } Sys_remove(SV_MVDName2Txt(path)); } } if (i) { Con_Printf("%d demos removed\n", i); } else { Con_Printf("no match found\n"); } // force cache rebuild. FS_FlushFSHash(); return; } strlcpy(name, Cmd_Argv(1), MAX_DEMO_NAME); COM_DefaultExtension(name, ".mvd"); snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, name); if (sv.mvdrecording && DestByName(name) /*!strcmp(name, demo.name)*/) SV_MVDStop_f(); // FIXME: probably we must stop not all demos, but only partial dest if (!Sys_remove(path)) { Con_Printf("demo %s successfully removed\n", name); if (*sv_ondemoremove.string) { extern redirect_t sv_redirected; redirect_t old = sv_redirected; sv_redirected = RD_NONE; // this script is called always from the console Cmd_TokenizeString(va("script %s \"%s\" \"%s\"", sv_ondemoremove.string, sv_demoDir.string, name)); SV_Script_f(); sv_redirected = old; } } else Con_Printf("unable to remove demo %s\n", name); Sys_remove(SV_MVDName2Txt(path)); // force cache rebuild. FS_FlushFSHash(); } void SV_MVDRemoveNum_f (void) { int num; char *val, *name; char path[MAX_OSPATH]; if (Cmd_Argc() != 2) { Con_Printf("rmdemonum <#>\n"); return; } val = Cmd_Argv(1); if ((num = Q_atoi(val)) == 0 && val[0] != '0') { Con_Printf("rmdemonum <#>\n"); return; } name = SV_MVDNum(num); if (name != NULL) { if (sv.mvdrecording && DestByName(name)/*!strcmp(name, demo.name)*/) SV_MVDStop_f(); // FIXME: probably we must stop not all demos, but only partial dest snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, name); if (!Sys_remove(path)) { Con_Printf("demo %s successfully removed\n", name); if (*sv_ondemoremove.string) { extern redirect_t sv_redirected; redirect_t old = sv_redirected; sv_redirected = RD_NONE; // this script is called always from the console Cmd_TokenizeString(va("script %s \"%s\" \"%s\"", sv_ondemoremove.string, sv_demoDir.string, name)); SV_Script_f(); sv_redirected = old; } } else Con_Printf("unable to remove demo %s\n", name); Sys_remove(SV_MVDName2Txt(path)); // force cache rebuild. FS_FlushFSHash(); } else Con_Printf("invalid demo num\n"); } void SV_MVDInfoAdd_f (void) { char *name, *args, path[MAX_OSPATH]; FILE *f; if (Cmd_Argc() < 3) { Con_Printf("usage:demoInfoAdd \n = * for currently recorded demo\n"); return; } if (!strcmp(Cmd_Argv(1), "*") || !strcmp(Cmd_Argv(1), "**")) { const char* demoname = SV_MVDDemoName(); if (!sv.mvdrecording || !demoname) { Con_Printf("Not recording demo!\n"); return; } snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(demoname)); } else { name = SV_MVDTxTNum(Q_atoi(Cmd_Argv(1))); if (!name) { Con_Printf("invalid demo num\n"); return; } snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, name); } if ((f = fopen(path, !strcmp(Cmd_Argv(1), "**") ? "a+b" : "a+t")) == NULL) { Con_Printf("failed to open the file\n"); return; } if (!strcmp(Cmd_Argv(1), "**")) { // put content of one file to another FILE *src; snprintf(path, MAX_OSPATH, "%s/%s", fs_gamedir, Cmd_Argv(2)); if ((src = fopen(path, "rb")) == NULL) // open src { Con_Printf("failed to open input file\n"); } else { byte buf[1024 * 200] = { 0 }; // 200 kb size_t sz = fread((void*)buf, 1, sizeof(buf), src); // read from src if (sz <= 0) { Con_Printf("failed to read or empty input file\n"); } else { // write to f if (sz != fwrite((void*)buf, 1, sz, f)) { Con_Printf("failed write to file\n"); } } fclose(src); // close src } } else { // skip demonum args = Cmd_Args(); while (*args > 32) args++; while (*args && *args <= 32) args++; fwrite(args, strlen(args), 1, f); fwrite("\n", 1, 1, f); } fflush(f); fclose(f); // force cache rebuild. FS_FlushFSHash(); } // Put content of one file into .mvd void SV_MVDEmbedInfo_f(void) { // put content of one file to another FILE* src; byte* buf; size_t sz; char name[MAX_OSPATH]; char path[MAX_OSPATH]; if (Cmd_Argc() == 1) { Con_Printf("Embeds contents of a file into mvd/qtv stream\n"); Con_Printf("Usage: %s \n", Cmd_Argv(0)); return; } // No config files, no sub-directories strlcpy(name, Cmd_Argv(1), sizeof(name)); snprintf(path, MAX_OSPATH, "%s/%s", fs_gamedir, Cmd_Argv(1)); if (FS_UnsafeFilename(path) || !strcasecmp(COM_FileExtension(name), "cfg")) { Con_Printf("Unsafe filename detected - cancelled\n"); return; } if ((src = fopen(path, "rb")) == NULL) { Con_Printf("failed to open input file\n"); return; } buf = Q_malloc(MAX_DEMOINFO_SIZE); sz = fread((void*)buf, 1, MAX_DEMOINFO_SIZE, src); fclose(src); if (sz <= 0) { Con_Printf("failed to read or empty input file\n"); Q_free(buf); return; } Con_Printf("Embedding (%s):\n", path); // embed in .mvd/qtv (sanity check limits) if (sz < 2 * 1024 * 1024) { mvdhidden_block_header_t header; byte* data = buf; short block_number = 1; while (sz > 0) { int prefix_length = sizeof_mvdhidden_block_header_t_range0 + sizeof(block_number); int length = (int)min(sz + prefix_length, MAX_MVD_SIZE); if (MVDWrite_HiddenBlockBegin(length)) { length -= prefix_length; sz -= length; if (sz == 0) { block_number = 0; } header.length = LittleLong(length + sizeof(short)); header.type_id = LittleShort(mvdhidden_demoinfo); MVD_SZ_Write(&header.length, sizeof(header.length)); MVD_SZ_Write(&header.type_id, sizeof(header.type_id)); MVD_SZ_Write(&block_number, sizeof(block_number)); MVD_SZ_Write(data, length); data += length; ++block_number; } else { Con_Printf("failed write to mvd/qtv\n"); Q_free(buf); break; } } } Q_free(buf); } void SV_MVDInfoRemove_f (void) { char *name, path[MAX_OSPATH]; if (Cmd_Argc() < 2) { Con_Printf("usage:demoInfoRemove \n = * for currently recorded demo\n"); return; } if (!strcmp(Cmd_Argv(1), "*")) { if (!sv.mvdrecording || !demo.dest) { Con_Printf("Not recording demo!\n"); return; } // snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, demo.path, SV_MVDName2Txt(demo.name)); // FIXME: dunno is this right, just using first dest, also may be we must use demo.dest->path instead of sv_demoDir snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(demo.dest->name)); } else { name = SV_MVDTxTNum(Q_atoi(Cmd_Argv(1))); if (!name) { Con_Printf("invalid demo num\n"); return; } snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, name); } if (Sys_remove(path)) Con_Printf("failed to remove the file %s\n", path); else Con_Printf("file %s removed\n", path); // force cache rebuild. FS_FlushFSHash(); } void SV_MVDInfo_f (void) { unsigned char buf[512]; FILE *f = NULL; char *name, path[MAX_OSPATH]; if (Cmd_Argc() < 2) { Con_Printf("usage: demoinfo \n = * for currently recorded demo\n"); return; } if (!strcmp(Cmd_Argv(1), "*")) { if (!sv.mvdrecording || !demo.dest) { Con_Printf("Not recording demo!\n"); return; } // snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, demo.path, SV_MVDName2Txt(demo.name)); // FIXME: dunno is this right, just using first dest, also may be we must use demo.dest->path instead of sv_demoDir snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(demo.dest->name)); } else { name = SV_MVDTxTNum(Q_atoi(Cmd_Argv(1))); if (!name) { Con_Printf("invalid demo num\n"); return; } snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, name); } if ((f = fopen(path, "rt")) == NULL) { Con_Printf("(empty)\n"); return; } while (!feof(f)) { buf[fread (buf, 1, sizeof(buf) - 1, f)] = 0; Con_Printf("%s", Q_yelltext(buf)); } fclose(f); } #define MAXDEMOS 10 #define MAXDEMOS_RD_PACKET 100 void SV_LastScores_f (void) { int demos = MAXDEMOS, i; char buf[512]; FILE *f = NULL; char path[MAX_OSPATH]; dir_t dir; extern redirect_t sv_redirected; if (Cmd_Argc() > 2) { Con_Printf("usage: lastscores []\n = '0' for all demos\n = '' for last %i demos\n", MAXDEMOS); return; } if (Cmd_Argc() == 2) if ((demos = Q_atoi(Cmd_Argv(1))) <= 0) demos = MAXDEMOS; dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string), sv_demoRegexp.string, SORT_BY_DATE); if (!dir.numfiles) { Con_Printf("No demos.\n"); return; } if (demos > dir.numfiles) demos = dir.numfiles; if (demos > MAXDEMOS && GameStarted()) Con_Printf(" was decreased to %i: match is in progress.\n", demos = MAXDEMOS); if (demos > MAXDEMOS_RD_PACKET && sv_redirected == RD_PACKET) Con_Printf(" was decreased to %i: command from connectionless packet.\n", demos = MAXDEMOS_RD_PACKET); Con_Printf("List of %d last demos:\n", demos); for (i = dir.numfiles - demos; i < dir.numfiles; ) { snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(dir.files[i].name)); Con_Printf("%i. ", ++i); if ((f = fopen(path, "rt")) == NULL) Con_Printf("(empty)\n"); else { if (!feof(f)) { char *nl; buf[fread (buf, 1, sizeof(buf) - 1, f)] = 0; if ((nl = strchr(buf, '\n'))) nl[0] = 0; Con_Printf("%s\n", Q_yelltext((unsigned char*)buf)); } else Con_Printf("(empty)\n"); fclose(f); } } } // easyrecord helpers int Dem_CountPlayers (void) { int i, count; count = 0; for (i = 0; i < MAX_CLIENTS ; i++) { if (svs.clients[i].name[0] && !svs.clients[i].spectator) count++; } return count; } char *Dem_Team (int num) { int i; static char *lastteam[2]; qbool first = true; client_t *client; static int index1 = 0; index1 = 1 - index1; for (i = 0, client = svs.clients; num && i < MAX_CLIENTS; i++, client++) { if (!client->name[0] || client->spectator) continue; if (first || strcmp(lastteam[index1], client->team)) { first = false; num--; lastteam[index1] = client->team; } } if (num) return ""; return lastteam[index1]; } char *Dem_PlayerName (int num) { int i; client_t *client; for (i = 0, client = svs.clients; i < MAX_CLIENTS; i++, client++) { if (!client->name[0] || client->spectator) continue; if (!--num) return client->name; } return ""; } char *Dem_PlayerNameTeam (char *t) { int i; client_t *client; static char n[1024]; int sep; n[0] = 0; sep = 0; for (i = 0, client = svs.clients; i < MAX_CLIENTS; i++, client++) { if (!client->name[0] || client->spectator) continue; if (strcmp(t, client->team)==0) { if (sep >= 1) strlcat (n, "_", sizeof(n)); // snprintf (n, sizeof(n), "%s_", n); strlcat (n, client->name, sizeof(n)); // snprintf (n, sizeof(n),"%s%s", n, client->name); sep++; } } return n; } int Dem_CountTeamPlayers (char *t) { int i, count; count = 0; for (i = 0; i < MAX_CLIENTS ; i++) { if (svs.clients[i].name[0] && !svs.clients[i].spectator) if (strcmp(&svs.clients[i].team[0], t)==0) count++; } return count; } char *quote (char *str) { char *out, *s; if (!str) return NULL; if (!*str) return NULL; s = out = (char *) Q_malloc (strlen(str) * 2 + 1); while (*str) { if (!isdigit(*str) && !isalpha(*str)) *s++ = '\\'; *s++ = *str++; } *s = '\0'; return out; } #endif // !CLIENTONLY mvdsv-0.35/src/sv_demo_qtv.c000066400000000000000000001040231427146041000160600ustar00rootroot00000000000000/* 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 included (GNU.txt) 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. */ // sv_demo_qtv.c - misc QTV's code #ifndef CLIENTONLY #include "qwsvdef.h" static cvar_t qtv_streamport = {"qtv_streamport", "0"}; static cvar_t qtv_maxstreams = {"qtv_maxstreams", "1"}; static cvar_t qtv_password = {"qtv_password", ""}; static cvar_t qtv_pendingtimeout = {"qtv_pendingtimeout", "5"}; // 5 seconds must be enough static cvar_t qtv_sayenabled = {"qtv_sayenabled", "0"}; // allow mod to override GameStarted() logic cvar_t qtv_streamtimeout = {"qtv_streamtimeout", "45"}; // 45 seconds static unsigned short int listenport = 0; static double warned_time = 0; static mvddest_t *SV_InitStream (int socket1, netadr_t na, char *userinfo) { static int lastdest = 0; int count; mvddest_t *dst; char name[sizeof(dst->qtvname)]; char address[sizeof(dst->qtvaddress)]; int streamid = 0; // extract name strlcpy(name, Info_ValueForKey(userinfo, "name"), sizeof(name)); strlcpy(address, Info_ValueForKey(userinfo, "address"), sizeof(address)); streamid = atoi(Info_ValueForKey(userinfo, "streamid")); count = 0; for (dst = demo.dest; dst; dst = dst->nextdest) { if (dst->desttype == DEST_STREAM) { if (name[0] && !strcasecmp(name, dst->qtvname)) return NULL; // duplicate name, well empty names may still duplicates... count++; } } if ((int)qtv_maxstreams.value > 0 && count >= (int)qtv_maxstreams.value) return NULL; //sorry dst = (mvddest_t *) Q_malloc (sizeof(mvddest_t)); dst->desttype = DEST_STREAM; dst->socket = socket1; dst->maxcachesize = 65536; //is this too small? dst->cache = (char *) Q_malloc(dst->maxcachesize); dst->io_time = Sys_DoubleTime(); dst->id = ++lastdest; dst->na = na; strlcpy(dst->qtvname, name, sizeof(dst->qtvname)); strlcpy(dst->qtvaddress, address, sizeof(dst->qtvaddress)); dst->qtvstreamid = streamid; if (dst->qtvname[0]) Con_Printf ("Connected to QTV(%s)\n", dst->qtvname); else Con_Printf ("Connected to QTV\n"); return dst; } static void SV_MVD_InitPendingStream (int socket1, netadr_t na, qbool must_be_qizmo_tcp_connect) { mvdpendingdest_t *dst; unsigned int i; dst = (mvdpendingdest_t*) Q_malloc(sizeof(mvdpendingdest_t)); dst->socket = socket1; dst->io_time = Sys_DoubleTime(); dst->na = na; dst->must_be_qizmo_tcp_connect = must_be_qizmo_tcp_connect; strlcpy(dst->challenge, NET_AdrToString(dst->na), sizeof(dst->challenge)); for (i = strlen(dst->challenge); i < sizeof(dst->challenge)-1; i++) dst->challenge[i] = rand()%(127-33) + 33; //generate a random challenge dst->nextdest = demo.pendingdest; demo.pendingdest = dst; } void SV_MVDCloseStreams(void) { mvddest_t *d; mvdpendingdest_t *p; for (d = demo.dest; d; d = d->nextdest) if (!d->error && d->desttype == DEST_STREAM) d->error = true; // mark demo stream dest to close later for (p = demo.pendingdest; p; p = p->nextdest) if (!p->error) p->error = true; // mark pending dest to close later } static void SV_CheckQTVPort(void) { qbool changed; // so user can't specify something stupid unsigned short int streamport = bound(0, (unsigned short int)qtv_streamport.value, 65534); // if we have non zero stream port, but fail to open listen socket, repeat open listen socket after some time changed = ( streamport != listenport // port changed. || (streamport && NET_GetSocket(NS_SERVER, true) == INVALID_SOCKET && warned_time + 10 < Sys_DoubleTime()) // stream port non zero but socket still not open, lets open socket then. || (!streamport && NET_GetSocket(NS_SERVER, true) != INVALID_SOCKET) // stream port is zero but socket still open, lets close socket then. ); // port not changed if (!changed) return; SV_MVDCloseStreams(); // also close ative connects if any, this will help actually close listen socket, so later we can bind to port again warned_time = Sys_DoubleTime(); // so we repeat warning time to time // port was changed, lets remember listenport = streamport; // open/close/reopen TCP port. NET_InitServer_TCP(listenport); } void SV_MVDStream_Poll (void) { int client; qbool must_be_qizmo_tcp_connect = false; netadr_t na; struct sockaddr_storage addr; socklen_t addrlen; int count; mvddest_t *dest; unsigned long _true = true; if (sv.state != ss_active) return; SV_CheckQTVPort(); // open/close/switch qtv port if (NET_GetSocket(NS_SERVER, true) == INVALID_SOCKET) // we can't accept connection from QTV { SV_MVDCloseStreams(); // also close ative connects if any, this will help actually close listen socket, so later we can bind to port again return; } addrlen = sizeof(addr); client = accept (NET_GetSocket(NS_SERVER, true), (struct sockaddr *)&addr, &addrlen); if (client == INVALID_SOCKET) return; if (ioctlsocket (client, FIONBIO, &_true) == SOCKET_ERROR) { Con_Printf ("SV_MVDStream_Poll: ioctl FIONBIO: (%i): %s\n", qerrno, strerror (qerrno)); closesocket(client); return; } if (!TCP_Set_KEEPALIVE(client)) { Con_Printf ("SV_MVDStream_Poll: TCP_Set_KEEPALIVE: failed\n"); closesocket(client); return; } if ((int)qtv_maxstreams.value > 0) { count = 0; for (dest = demo.dest; dest; dest = dest->nextdest) { if (dest->desttype == DEST_STREAM) { count++; } } if (count >= (int)qtv_maxstreams.value) { // we use + 3 so there qizmo tcp connection have chance... if (count >= (int)qtv_maxstreams.value + 3) { //sorry, there really way too much connections char *goawaymessage = "QTVSV 1\nERROR: This server enforces a limit on the number of proxies connected at any one time. Please try again later\n\n"; send(client, goawaymessage, strlen(goawaymessage), 0); closesocket(client); return; } else { // ok, give qizmo tcp connect a chance, but only and only for tcp connect must_be_qizmo_tcp_connect = true; } } } SockadrToNetadr(&addr, &na); Con_Printf("MVD streaming client connected from %s\n", NET_AdrToString(na)); SV_MVD_InitPendingStream(client, na, must_be_qizmo_tcp_connect); } void SV_MVD_RunPendingConnections (void) { unsigned short ushort_result; char *e; int len; mvdpendingdest_t *p; mvdpendingdest_t *np; char userinfo[1024] = {0}; if (!demo.pendingdest) return; for (p = demo.pendingdest; p; p = p->nextdest) if (p->io_time + qtv_pendingtimeout.value <= Sys_DoubleTime()) { Con_Printf("Pending dest timeout\n"); p->error = true; } while (demo.pendingdest && demo.pendingdest->error) { np = demo.pendingdest->nextdest; if (demo.pendingdest->socket != -1) closesocket(demo.pendingdest->socket); Q_free(demo.pendingdest); demo.pendingdest = np; } for (p = demo.pendingdest; p && p->nextdest; p = p->nextdest) { if (p->nextdest->error) { np = p->nextdest->nextdest; if (p->nextdest->socket != -1) closesocket(p->nextdest->socket); Q_free(p->nextdest); p->nextdest = np; } } for (p = demo.pendingdest; p; p = p->nextdest) { if (p->outsize && !p->error) { len = send(p->socket, p->outbuffer, p->outsize, 0); if (len == 0) //client died { // p->error = true; // man says: The calls return the number of characters sent, or -1 if an error occurred. // so 0 is legal or what? } else if (len > 0) //we put some data through { p->io_time = Sys_DoubleTime(); // update IO activity //move up the buffer p->outsize -= len; memmove(p->outbuffer, p->outbuffer+len, p->outsize ); } else { //error of some kind. would block or something if (qerrno != EWOULDBLOCK && qerrno != EAGAIN) p->error = true; } } if (!p->error) { len = recv(p->socket, p->inbuffer + p->insize, sizeof(p->inbuffer) - p->insize - 1, 0); if (len > 0) { //fixme: cope with extra \rs char *end; p->io_time = Sys_DoubleTime(); // update IO activity p->insize += len; p->inbuffer[p->insize] = 0; // TCPCONNECT --> // kinda hack to allow both qtv and qizmo tcp connection work on the same server port // check for qizmo tcp connection if (p->insize >=6) { if (strncmp(p->inbuffer, "qizmo\n", 6)) { // no. seems it like QTV client... but if we expect qizmo so we better drop it ASAP if (p->must_be_qizmo_tcp_connect) { p->error = true; continue; } } else { // new qizmo tcpconnection extern svtcpstream_t *sv_tcp_connection_new(int sock, netadr_t from, char *buf, int buf_len, qbool link); extern int sv_tcp_connection_count(void); svtcpstream_t *st = sv_tcp_connection_new(p->socket, p->na, p->inbuffer, p->insize, true); int _true = true; // set some timeout st->timeouttime = Sys_DoubleTime() + 10; // send protocol confirmation if (send(st->socketnum, "qizmo\n", 6, 0) != 6) { st->drop = true; // failed miserable to send some chunk of data } if (sv_tcp_connection_count() >= MAX_CLIENTS) { st->drop = true; } if (setsockopt(st->socketnum, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)) == -1) { Con_DPrintf ("SV_MVD_RunPendingConnections: setsockopt: (%i): %s\n", qerrno, strerror(qerrno)); } p->error = true; p->socket = -1; //so it's not cleared wrongly. continue; } } if (p->must_be_qizmo_tcp_connect) continue; // HACK, this stream should not be allowed but just checked ONLY AND ONLY for qizmo tcp connection // <--TCPCONNECT for (end = p->inbuffer; ; end++) { if (*end == '\0') { end = NULL; break; //not enough data } if (end[0] == '\n') { if (end[1] == '\n') { end[1] = '\0'; break; } } } if (end) { //we found the end of the header char *start, *lineend; int versiontouse = 0; int raw = 0; char password[256] = ""; typedef enum { QTVAM_NONE, QTVAM_PLAIN, QTVAM_CCITT, QTVAM_MD4, QTVAM_SHA3_512, } authmethod_t; authmethod_t authmethod = QTVAM_NONE; start = p->inbuffer; lineend = strchr(start, '\n'); if (!lineend) { // char *e; // e = "This is a QTV server."; // send(p->socket, e, strlen(e), 0); p->error = true; continue; } *lineend = '\0'; COM_ParseToken(start, NULL); start = lineend+1; if (strcmp(com_token, "QTV")) { //it's an error if it's not qtv. p->error = true; lineend = strchr(start, '\n'); continue; } for(;;) { lineend = strchr(start, '\n'); if (!lineend) break; *lineend = '\0'; start = COM_ParseToken(start, NULL); if (*start == ':') { //VERSION: a list of the different qtv protocols supported. Multiple versions can be specified. The first is assumed to be the prefered version. //RAW: if non-zero, send only a raw mvd with no additional markup anywhere (for telnet use). Doesn't work with challenge-based auth, so will only be accepted when proxy passwords are not required. //AUTH: specifies an auth method, the exact specs varies based on the method // PLAIN: the password is sent as a PASSWORD line // MD4: the server responds with an "AUTH: MD4\n" line as well as a "CHALLENGE: somerandomchallengestring\n" line, the client sends a new 'initial' request with CHALLENGE: MD4\nRESPONSE: hexbasedmd4checksumhere\n" // CCITT: same as md4, but using the CRC stuff common to all quake engines. // if the supported/allowed auth methods don't match, the connection is silently dropped. //SOURCE: which stream to play from, DEFAULT is special. Without qualifiers, it's assumed to be a tcp address. //COMPRESSION: Suggests a compression method (multiple are allowed). You'll get a COMPRESSION response, and compression will begin with the binary data. start = start+1; Con_Printf("qtv, got (%s) (%s)\n", com_token, start); if (!strcmp(com_token, "VERSION")) { start = COM_ParseToken(start, NULL); if (atoi(com_token) == 1) versiontouse = 1; } else if (!strcmp(com_token, "RAW")) { start = COM_ParseToken(start, NULL); raw = atoi(com_token); } else if (!strcmp(com_token, "PASSWORD")) { start = COM_ParseToken(start, NULL); strlcpy(password, com_token, sizeof(password)); } else if (!strcmp(com_token, "AUTH")) { authmethod_t thisauth; start = COM_ParseToken(start, NULL); if (!strcmp(com_token, "NONE")) thisauth = QTVAM_PLAIN; else if (!strcmp(com_token, "PLAIN")) thisauth = QTVAM_PLAIN; else if (!strcmp(com_token, "CCITT")) thisauth = QTVAM_CCITT; else if (!strcmp(com_token, "MD4")) thisauth = QTVAM_MD4; else if (!strcmp(com_token, "SHA3_512")) thisauth = QTVAM_SHA3_512; else { thisauth = QTVAM_NONE; Con_DPrintf("qtv: received unrecognised auth method (%s)\n", com_token); } if (authmethod < thisauth) authmethod = thisauth; } else if (!strcmp(com_token, "SOURCE")) { //servers don't support source, and ignore it. //source is only useful for qtv proxy servers. } else if (!strcmp(com_token, "COMPRESSION")) { //compression not supported yet } else if (!strcmp(com_token, "USERINFO")) { start = COM_ParseToken(start, NULL); strlcpy(userinfo, com_token, sizeof(userinfo)); } else { //not recognised. } } start = lineend+1; } len = (end - p->inbuffer)+2; p->insize -= len; memmove(p->inbuffer, p->inbuffer + len, p->insize); p->inbuffer[p->insize] = 0; e = NULL; if (p->hasauthed) { } else if (!*qtv_password.string) p->hasauthed = true; //no password, no need to auth. else if (*password) { switch (authmethod) { case QTVAM_NONE: e = ("QTVSV 1\n" "PERROR: You need to provide a common auth method.\n\n"); break; case QTVAM_PLAIN: p->hasauthed = !strcmp(qtv_password.string, password); break; case QTVAM_CCITT: CRC_Init(&ushort_result); CRC_AddBlock(&ushort_result, (byte *) p->challenge, strlen(p->challenge)); CRC_AddBlock(&ushort_result, (byte *) qtv_password.string, strlen(qtv_password.string)); p->hasauthed = (ushort_result == Q_atoi(password)); break; case QTVAM_MD4: { char hash[512]; int md4sum[4]; snprintf (hash, sizeof(hash), "%s%s", p->challenge, qtv_password.string); Com_BlockFullChecksum (hash, strlen(hash), (unsigned char*)md4sum); snprintf (hash, sizeof(hash), "%X%X%X%X", md4sum[0], md4sum[1], md4sum[2], md4sum[3]); p->hasauthed = !strcmp(password, hash); } break; case QTVAM_SHA3_512: { sha3_context c; const uint8_t *byte_hash; char hash[SHA3_512_DIGEST_HEX_STR_SIZE] = {0}; sha3_Init512(&c); sha3_Update(&c, p->challenge, strlen(p->challenge)); sha3_Update(&c, qtv_password.string, strlen(qtv_password.string)); byte_hash = sha3_Finalize(&c); sha3_512_ByteToHex(hash, byte_hash); p->hasauthed = !strcmp(password, hash); } break; default: e = ("QTVSV 1\n" "PERROR: FTEQWSV bug detected.\n\n"); break; } if (!p->hasauthed && !e) { if (raw) e = ""; else e = ("QTVSV 1\n" "PERROR: Bad password.\n\n"); } } else { //no password, and not automagically authed switch (authmethod) { case QTVAM_NONE: if (raw) e = ""; else e = ("QTVSV 1\n" "PERROR: You need to provide a common auth method.\n\n"); break; case QTVAM_PLAIN: p->hasauthed = !strcmp(qtv_password.string, password); break; case QTVAM_CCITT: e = ("QTVSV 1\n" "AUTH: CCITT\n" "CHALLENGE: "); send(p->socket, e, strlen(e), 0); send(p->socket, p->challenge, strlen(p->challenge), 0); e = "\n\n"; send(p->socket, e, strlen(e), 0); continue; case QTVAM_MD4: e = ("QTVSV 1\n" "AUTH: MD4\n" "CHALLENGE: "); send(p->socket, e, strlen(e), 0); send(p->socket, p->challenge, strlen(p->challenge), 0); e = "\n\n"; send(p->socket, e, strlen(e), 0); continue; case QTVAM_SHA3_512: e = ("QTVSV 1\n" "AUTH: SHA3_512\n" "CHALLENGE: "); send(p->socket, e, strlen(e), 0); send(p->socket, p->challenge, strlen(p->challenge), 0); e = "\n\n"; send(p->socket, e, strlen(e), 0); continue; default: e = ("QTVSV 1\n" "PERROR: FTEQWSV bug detected.\n\n"); break; } } if (e) { } else if (!versiontouse) { e = ("QTVSV 1\n" "PERROR: Incompatible version (valid version is v1)\n\n"); } else if (raw) { if (p->hasauthed == false) { e = ""; } else { mvddest_t *tmpdest; if ((tmpdest = SV_InitStream(p->socket, p->na, userinfo))) { if (!SV_MVD_Record(tmpdest, false)) DestClose(tmpdest, false); // can't start record for some reason, close dest then p->socket = -1; //so it's not cleared wrongly. } else { // RAW mode, can't sent error, right? // e = ("QTVSV 1\n" // "ERROR: Can't init stream, probably server reach a limit on the number of proxies connected at any one time.\n\n"); } } p->error = true; } else { if (p->hasauthed == true) { mvddest_t *tmpdest; if ((tmpdest = SV_InitStream(p->socket, p->na, userinfo))) { e = ("QTVSV 1\n" "BEGIN\n\n"); send(p->socket, e, strlen(e), 0); e = NULL; if (!SV_MVD_Record(tmpdest, false)) DestClose(tmpdest, false); // can't start record for some reason, close dest then p->socket = -1; //so it's not cleared wrongly. } else { e = ("QTVSV 1\n" "ERROR: Can't init stream, probably server reach a limit on the number of proxies connected at any one time.\n\n"); } } else { e = ("QTVSV 1\n" "PERROR: You need to provide a password.\n\n"); } p->error = true; } if (e) { send(p->socket, e, strlen(e), 0); p->error = true; } } } else if (len == 0) p->error = true; else { //error of some kind. would block or something int err; err = qerrno; if (err != EWOULDBLOCK && err != EAGAIN) p->error = true; } } } } //============================================================ // // QTV user input // //============================================================ // { qtv commands // say [say_game] text // say_team [say_game] text // say_game text void QTVcmd_Say_f(mvddest_t *d) { qbool gameStarted; client_t *client; int j; char *p; char text[1024], text2[1024], *cmd; int sent_to = 0; if (Cmd_Argc () < 2) return; if (qtv_sayenabled.value || !strcasecmp(Info_ValueForKey(svs.info, "status"), "Countdown")) gameStarted = false; // if status is "Countdown" then game is not started yet else gameStarted = GameStarted(); p = Cmd_Args(); if (*p == '"' && (j = strlen(p)) > 2) { p[j-1] = 0; p++; } cmd = Cmd_Argv(0); // strip leading say_game but not in case of "cmd say_game say_game" if (strcmp(cmd, "say_game") && !strncasecmp(p, "say_game ", sizeof("say_game ") - 1)) { p += sizeof("say_game ") - 1; } if (!strcmp(cmd, "say_game")) cmd = "say"; // this makes qtv_%s_game looks right if (!strcmp(cmd, "say_team")) gameStarted = true; // send to specs only if (gameStarted) cmd = "say_team"; // we can accept only this command, since we will send to specs only // for clients and demo snprintf(text, sizeof(text), "#0:qtv_%s_game:#%d:%s: %s\n", cmd, d->id, d->qtvname, p); // for server console and logs snprintf(text2, sizeof(text2), "qtv: #0:qtv_%s_game:#%d:%s: %s\n", cmd, d->id, d->qtvname, p); for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) { if (client->state != cs_spawned) continue; if (gameStarted && !client->spectator) continue; // game started, don't send QTV chat to players, specs still get QTV chat SV_ClientPrintf2(client, PRINT_CHAT, "%s", text); if (!client->spectator) { sent_to |= (1 << j); } } if (sv.mvdrecording) { sizebuf_t msg; byte msg_buf[1024]; SZ_InitEx(&msg, msg_buf, sizeof(msg_buf), true); MSG_WriteByte (&msg, svc_print); MSG_WriteByte (&msg, PRINT_CHAT); MSG_WriteString (&msg, text); DemoWriteQTV(&msg); } Sys_Printf("%s", text2); SV_Write_Log(CONSOLE_LOG, 1, text2); } // } // { static qtvuser_t *QTVsv_UserById(mvddest_t *d, int id) { qtvuser_t *current; for (current = d->qtvuserlist; current; current = current->next) if (current->id == id) return current; return NULL; } static void QTVsv_SetUser(qtvuser_t *to, qtvuser_t *from) { *to = *from; } static int QTVsv_UsersCount(mvddest_t *d) { qtvuser_t *current; int c = 0; for (current = d->qtvuserlist; current; current = current->next) c++; return c; } // allocate data and set fields, perform linkage to qtvuserlist // Well, instead of QTVsv_NewUser(int id, char *name, ...) I pass params with single qtvuser_t *user struct, well its OK for current struct. static qtvuser_t *QTVsv_NewUser(mvddest_t *d, qtvuser_t *user) { // check, may be user alredy exist, so reuse it qtvuser_t *newuser = QTVsv_UserById(d, user->id); if (!newuser) { // user does't exist, alloc data if (QTVsv_UsersCount(d) > 2048) { Con_Printf("QTVsv_NewUser: too much users, haxors? Dropping dest\n"); d->error = true; // drop dest later return NULL; } newuser = Q_malloc(sizeof(*newuser)); // alloc QTVsv_SetUser(newuser, user); // perform linkage newuser->next = d->qtvuserlist; d->qtvuserlist = newuser; } else { // we do not need linkage, just save current qtvuser_t *oldnext = newuser->next; // we need save this before assign all fields QTVsv_SetUser(newuser, user); newuser->next = oldnext; } return newuser; } // free data, perform unlink if requested static void QTVsv_FreeUser(mvddest_t *d, qtvuser_t *user, qbool unlink) { if (!user) return; if (unlink) { qtvuser_t *next, *prev, *current; prev = NULL; current = d->qtvuserlist; for ( ; current; ) { next = current->next; if (user == current) { if (prev) prev->next = next; else d->qtvuserlist = next; break; } prev = current; current = next; } } Q_free(user); } // free whole qtvuserlist void QTVsv_FreeUserList(mvddest_t *d) { qtvuser_t *next, *current; current = d->qtvuserlist; for ( ; current; current = next) { next = current->next; QTVsv_FreeUser(d, current, false); } d->qtvuserlist = NULL; } #define QTV_EVENT_PREFIX "QTV: " // user join qtv void QTVsv_JoinEvent(mvddest_t *d, qtvuser_t *user) { // make it optional message // if (!qtv_event_join.string[0]) // return; // do not show "user joined" at moment of connection to QTV, it mostly QTV just spammed userlist to us. // if (cls.state <= ca_demostart) // return; if (QTVsv_UserById(d, user->id)) { // we alredy have this user, do not double trigger return; } Con_Printf("%s%s: %s%s\n", QTV_EVENT_PREFIX, d->qtvname, user->name, /*qtv_event_join.string*/ " join"); } // user leaved/left qtv void QTVsv_LeaveEvent(mvddest_t *d, qtvuser_t *user) { qtvuser_t *olduser; // make it optional message // if (!qtv_event_leave.string[0]) // return; if (!(olduser = QTVsv_UserById(d, user->id))) { // we do not have this user return; } Con_Printf("%s%s: %s%s\n", QTV_EVENT_PREFIX, d->qtvname, olduser->name, /*qtv_event_leave.string*/ " left"); } // user changed name on qtv void QTVsv_ChangeEvent(mvddest_t *d, qtvuser_t *user) { qtvuser_t *olduser; // well, too spammy, make it as option // if (!qtv_event_changename.string[0]) // return; if (!(olduser = QTVsv_UserById(d, user->id))) { // we do not have this user yet Con_DPrintf("qtv: change event without olduser\n"); return; } Con_Printf("%s%s: %s%s%s\n", QTV_EVENT_PREFIX, d->qtvname, olduser->name, /*qtv_event_changename.string*/ " changed name to ", user->name); } static void QTVcmd_QtvUserList_f(mvddest_t *d) { qtvuser_t tmpuser; qtvuserlist_t action; int cnt = 1; memset(&tmpuser, 0, sizeof(tmpuser)); // action id [\"name\"] // Cmd_TokenizeString( s ); action = atoi( Cmd_Argv( cnt++ ) ); tmpuser.id = atoi( Cmd_Argv( cnt++ ) ); strlcpy(tmpuser.name, Cmd_Argv( cnt++ ), sizeof(tmpuser.name)); // name is optional in some cases switch ( action ) { case QUL_ADD: QTVsv_JoinEvent(d, &tmpuser); QTVsv_NewUser(d, &tmpuser); break; case QUL_CHANGE: QTVsv_ChangeEvent(d, &tmpuser); QTVsv_NewUser(d, &tmpuser); break; case QUL_DEL: QTVsv_LeaveEvent(d, &tmpuser); QTVsv_FreeUser(d, QTVsv_UserById(d, tmpuser.id), true); break; default: Con_Printf("Parse_QtvUserList: unknown action %d\n", action); return; } } // this issued remotely by user void Cmd_Qtvusers_f (void) { mvddest_t *d; qbool found = false; for (d = demo.dest; d; d = d->nextdest) { qtvuser_t *current; int c; if (d->desttype != DEST_STREAM) continue; found = true; c = 0; Con_Printf ("Proxy name: %s, id: %d\n", d->qtvname, d->id); Con_Printf ("userid name\n"); Con_Printf ("------ ----\n"); for (current = d->qtvuserlist; current; current = current->next) { Con_Printf ("%6i %s\n", current->id, current->name); c++; } Con_Printf ("%i total users\n", c); } if (!found) { Con_Printf ("no QTVs connected\n"); return; } } // } char QTV_cmd[MAX_PROXY_INBUFFER]; // global so it does't allocated on stack, this save some CPU I think typedef struct { char *name; void (*func) (mvddest_t *d); } qtv_ucmd_t; static qtv_ucmd_t ucmds[] = { {"say", QTVcmd_Say_f}, {"say_team", QTVcmd_Say_f}, {"say_game", QTVcmd_Say_f}, {"qul", QTVcmd_QtvUserList_f}, {NULL, NULL} }; // { qtv utils typedef struct { unsigned int readpos; unsigned int cursize; unsigned int maxsize; char *data; unsigned int startpos; // qbool overflowed; // qbool allowoverflow; } netmsg_t; static void InitNetMsg(netmsg_t *b, char *buffer, int bufferlength) { memset(b, 0, sizeof(netmsg_t)); b->data = buffer; b->maxsize = bufferlength; } //probably not the place for these any more.. static unsigned char ReadByte(netmsg_t *b) { if (b->readpos >= b->cursize) { b->readpos = b->cursize+1; return 0; } return b->data[b->readpos++]; } static unsigned short ReadShort(netmsg_t *b) { int b1, b2; b1 = ReadByte(b); b2 = ReadByte(b); return b1 | (b2<<8); } static void ReadString(netmsg_t *b, char *string, int maxlen) { maxlen--; //for null terminator while(maxlen) { *string = ReadByte(b); if (!*string) return; string++; maxlen--; } *string++ = '\0'; //add the null } // } void QTV_ExecuteCmd(mvddest_t *d, char *cmd) { char *arg0; qbool found = false; qtv_ucmd_t *u; // Sys_Printf("qtv cmd: %s\n", cmd); Cmd_TokenizeString (cmd); arg0 = Cmd_Argv(0); // Sys_RedirectStart(???); for (u = ucmds; u->name; u++) { if (!strcmp(arg0, u->name)) { if (u->func) u->func(d); found = true; break; } } if (!found) { if (developer.value) Sys_Printf("Bad QTV command: %s\n", arg0); } // Sys_RedirectStop(); } void QTV_ReadInput( mvddest_t *d ) { int len, parse_end, clc; netmsg_t buf; if (d->error) return; len = sizeof(d->inbuffer) - d->inbuffersize - 1; // -1 since it null terminated if (len) { len = recv(d->socket, d->inbuffer + d->inbuffersize, len, 0); if (len == 0) { Sys_Printf("QTV_ReadInput: read error from QTV client, dropping\n"); d->error = true; return; } else if (len < 0) { len = 0; } d->inbuffersize += len; d->inbuffer[d->inbuffersize] = 0; // null terminated } if (d->inbuffersize < 2) return; // we need at least size InitNetMsg(&buf, d->inbuffer, d->inbuffersize); buf.cursize = d->inbuffersize; // we laredy have some data in buffer parse_end = 0; while(buf.readpos < buf.cursize) { // Sys_Printf("%d %d\n", buf.readpos, buf.cursize); if (buf.readpos > buf.cursize) { d->error = true; Sys_Printf("QTV_ReadInput: Read past end of parse buffer\n"); return; } buf.startpos = buf.readpos; if (buf.cursize - buf.startpos < 2) break; // we need at least size len = ReadShort(&buf); if (len > (int)sizeof(d->inbuffer) - 1 || len < 3) { d->error = true; Sys_Printf("QTV_ReadInput: can't handle such long/short message: %i\n", len); return; } if (len > buf.cursize - buf.startpos) break; // not enough data yet parse_end = buf.startpos + len; // so later we know which part of buffer we alredy served switch (clc = ReadByte(&buf)) { #define qtv_clc_stringcmd 1 case qtv_clc_stringcmd: QTV_cmd[0] = 0; ReadString(&buf, QTV_cmd, sizeof(QTV_cmd)); QTV_ExecuteCmd(d, QTV_cmd); break; default: d->error = true; Sys_Printf("QTV_ReadInput: can't handle clc %i\n", clc); return; } } if (parse_end) { d->inbuffersize -= parse_end; memmove(d->inbuffer, d->inbuffer + parse_end, d->inbuffersize); } } void QTV_ReadDests( void ) { mvddest_t *d; for (d = demo.dest; d; d = d->nextdest) { if (d->desttype != DEST_STREAM) continue; if (d->error) continue; QTV_ReadInput(d); } } //============================================================ /* void DemoWriteQTVTimePad (int msecs) //broadcast to all proxies { mvddest_t *d; unsigned char buffer[6]; while (msecs > 0) { //duration if (msecs > 255) buffer[0] = 255; else buffer[0] = msecs; msecs -= buffer[0]; //message type buffer[1] = dem_read; //length buffer[2] = 0; buffer[3] = 0; buffer[4] = 0; buffer[5] = 0; for (d = demo.dest; d; d = d->nextdest) { if (d->desttype == DEST_STREAM) { DemoWriteDest(buffer, sizeof(buffer), d); } } } } */ //broadcast to all proxies void DemoWriteQTV (sizebuf_t *msg) { mvddest_t *d; sizebuf_t mvdheader; byte mvdheader_buf[6]; SZ_InitEx(&mvdheader, mvdheader_buf, sizeof(mvdheader_buf), false); //duration MSG_WriteByte (&mvdheader, 0); //message type MSG_WriteByte (&mvdheader, dem_all); //length MSG_WriteLong (&mvdheader, msg->cursize); for (d = demo.dest; d; d = d->nextdest) { if (d->desttype == DEST_STREAM) { DemoWriteDest(mvdheader.data, mvdheader.cursize, d); DemoWriteDest(msg->data, msg->cursize, d); } } } void Qtv_List_f(void) { mvddest_t *d; int cnt; for (cnt = 0, d = demo.dest; d; d = d->nextdest) { if (d->desttype != DEST_STREAM) continue; // not qtv if (!cnt) // print banner Con_Printf ("QTV list:\n" "%4.4s %s\n", "#Id", "Addr"); cnt++; Con_Printf ("%4d %s\n", d->id, NET_AdrToString(d->na)); } if (!cnt) Con_Printf ("QTV list: empty\n"); } // Very similar to Qtv_list_f, but for disconnected clients. void QTV_Streams_List (void) { mvddest_t *dst; for (dst = demo.dest; dst; dst = dst->nextdest) { if (dst->desttype == DEST_STREAM) { int qtv_users = QTVsv_UsersCount (dst); if (dst->qtvaddress[0]) Con_Printf ("qtv %d \"%s\" \"%d@%s\" %d\n", dst->id, dst->qtvname, dst->qtvstreamid, dst->qtvaddress, qtv_users); else Con_Printf ("qtv %d \"%s\" \"\" %d\n", dst->id, dst->qtvname, qtv_users); } } } // Expose user list to disconnected clients. void QTV_Streams_UserList (void) { mvddest_t *dst; for (dst = demo.dest; dst; dst = dst->nextdest) { if (dst->desttype == DEST_STREAM) { qtvuser_t *current; Con_Printf ("qtvusers %d", dst->id); for (current = dst->qtvuserlist; current; current = current->next) Con_Printf (" \"%s\"", current->name); Con_Printf ("\n"); } } } void Qtv_Close_f(void) { mvddest_t *d; int id, cnt; qbool all; if (Cmd_Argc() < 2 || !*Cmd_Argv(1)) // not less than one param, first param non empty { Con_Printf ("Usage: %s <#id | all>\n", Cmd_Argv(0)); return; } for (d = demo.dest; d; d = d->nextdest) if (d->desttype == DEST_STREAM) break; // at least one qtv present if (!d) { Con_Printf ("QTV list alredy empty\n"); return; } id = atoi(Cmd_Argv(1)); all = !strcasecmp(Cmd_Argv(1), "all"); cnt = 0; for (d = demo.dest; d; d = d->nextdest) { if (d->desttype != DEST_STREAM) continue; // not qtv if (all || d->id == id) { Con_Printf ("QTV id:%d aka %s will be dropped asap\n", d->id, NET_AdrToString(d->na)); d->error = true; cnt++; } } if (!cnt) Con_Printf ("QTV id:%d not found\n", id); } void Qtv_Status_f(void) { int cnt; mvddest_t *d; mvdpendingdest_t *p; Con_Printf ("QTV status\n"); Con_Printf ("Listen socket : %s\n", NET_GetSocket(NS_SERVER, true) == INVALID_SOCKET ? "invalid" : "listen"); Con_Printf ("Port : %d\n", listenport); for (cnt = 0, d = demo.dest; d; d = d->nextdest) if (d->desttype == DEST_STREAM) cnt++; Con_Printf ("Streams : %d\n", cnt); for (cnt = 0, p = demo.pendingdest; p; p = p->nextdest) cnt++; Con_Printf ("Pending streams: %d\n", cnt); } //==================================== void SV_QTV_Init(void) { Cvar_Register (&qtv_streamport); Cvar_Register (&qtv_maxstreams); Cvar_Register (&qtv_password); Cvar_Register (&qtv_pendingtimeout); Cvar_Register (&qtv_streamtimeout); Cvar_Register (&qtv_sayenabled); Cmd_AddCommand ("qtv_list", Qtv_List_f); Cmd_AddCommand ("qtv_close", Qtv_Close_f); Cmd_AddCommand ("qtv_status", Qtv_Status_f); } #endif // !CLIENTONLY mvdsv-0.35/src/sv_ents.c000066400000000000000000000621421427146041000152200ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef CLIENTONLY #include "qwsvdef.h" //============================================================================= // because there can be a lot of nails, there is a special // network protocol for them #define MAX_NAILS 32 static edict_t *nails[MAX_NAILS]; static int numnails; static int nailcount = 0; extern int sv_nailmodel, sv_supernailmodel, sv_playermodel; cvar_t sv_nailhack = {"sv_nailhack", "1"}; // Maximum packet we will send - currently 256 if extension supported #define MAX_PACKETENTITIES_POSSIBLE 256 static qbool SV_AddNailUpdate (edict_t *ent) { if ((int)sv_nailhack.value) return false; if (ent->v->modelindex != sv_nailmodel && ent->v->modelindex != sv_supernailmodel) return false; if (msg_coordsize != 2) return false; // Do not allow nailhack in case of sv_bigcoords. if (numnails == MAX_NAILS) return true; nails[numnails] = ent; numnails++; return true; } static void SV_EmitNailUpdate (sizebuf_t *msg, qbool recorder) { int x, y, z, p, yaw, n, i; byte bits[6]; // [48 bits] xyzpy 12 12 12 4 8 edict_t *ent; if (!numnails) return; if (recorder) MSG_WriteByte (msg, svc_nails2); else MSG_WriteByte (msg, svc_nails); MSG_WriteByte (msg, numnails); for (n=0 ; nv->colormap) { if (!((++nailcount)&255)) nailcount++; ent->v->colormap = nailcount&255; } MSG_WriteByte (msg, (byte)ent->v->colormap); } x = ((int)(ent->v->origin[0] + 4096 + 1) >> 1) & 4095; y = ((int)(ent->v->origin[1] + 4096 + 1) >> 1) & 4095; z = ((int)(ent->v->origin[2] + 4096 + 1) >> 1) & 4095; p = Q_rint(ent->v->angles[0]*(16.0/360.0)) & 15; yaw = Q_rint(ent->v->angles[1]*(256.0/360.0)) & 255; bits[0] = x; bits[1] = (x>>8) | (y<<4); bits[2] = (y>>4); bits[3] = z; bits[4] = (z>>8) | (p<<4); bits[5] = yaw; for (i=0 ; i<6 ; i++) MSG_WriteByte (msg, bits[i]); } } //============================================================================= /* ================== SV_WriteDelta Writes part of a packetentities message. Can delta from either a baseline or a previous packet_entity ================== */ void SV_WriteDelta(client_t* client, entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qbool force) { int bits, i; #ifdef PROTOCOL_VERSION_FTE int evenmorebits = 0; unsigned int required_extensions = 0; unsigned int fte_extensions = client->fteprotocolextensions; #endif // send an update bits = 0; for (i = 0; i < 3; i++) if (to->origin[i] != from->origin[i]) bits |= U_ORIGIN1 << i; if (to->angles[0] != from->angles[0]) bits |= U_ANGLE1; if (to->angles[1] != from->angles[1]) bits |= U_ANGLE2; if (to->angles[2] != from->angles[2]) bits |= U_ANGLE3; if (to->colormap != from->colormap) bits |= U_COLORMAP; if (to->skinnum != from->skinnum) bits |= U_SKIN; if (to->frame != from->frame) bits |= U_FRAME; if (to->effects != from->effects) bits |= U_EFFECTS; if (to->modelindex != from->modelindex) { bits |= U_MODEL; #ifdef FTE_PEXT_ENTITYDBL if (to->modelindex > 255) { evenmorebits |= U_FTE_MODELDBL; required_extensions |= FTE_PEXT_MODELDBL; } #endif } if (bits & U_CHECKMOREBITS) bits |= U_MOREBITS; if (to->flags & U_SOLID) bits |= U_SOLID; // Taken from FTE if (msg->cursize + 40 > msg->maxsize && !MSG_HasOverflowHandler(msg)) { // not enough space in the buffer, don't send the entity this frame. (not sending means nothing changes, and it takes no bytes!!) int oldnum = to->number; *to = *from; if (oldnum && !from->number) { to->number = oldnum; } return; } // // write the message // if (!to->number) SV_Error("Unset entity number"); #ifdef PROTOCOL_VERSION_FTE if (to->number >= 512) { if (to->number >= 1024) { if (to->number >= 1024 + 512) { evenmorebits |= U_FTE_ENTITYDBL; required_extensions |= FTE_PEXT_ENTITYDBL; } evenmorebits |= U_FTE_ENTITYDBL2; required_extensions |= FTE_PEXT_ENTITYDBL2; if (to->number >= 2048) SV_Error ("Entity number >= 2048"); } else { evenmorebits |= U_FTE_ENTITYDBL; required_extensions |= FTE_PEXT_ENTITYDBL; } } if (evenmorebits&0xff00) evenmorebits |= U_FTE_YETMORE; if (evenmorebits&0x00ff) bits |= U_FTE_EVENMORE; if (bits & 511) bits |= U_MOREBITS; #endif if (to->number >= sv.max_edicts) { /*SV_Error*/ Con_Printf("Entity number >= MAX_EDICTS (%d), set to MAX_EDICTS - 1\n", sv.max_edicts); to->number = sv.max_edicts - 1; } #ifdef PROTOCOL_VERSION_FTE if (evenmorebits && (fte_extensions & required_extensions) != required_extensions) { return; } #endif if (!bits && !force) { return; // nothing to send! } i = (to->number & U_CHECKMOREBITS) | (bits&~U_CHECKMOREBITS); if (i & U_REMOVE) Sys_Error("U_REMOVE"); MSG_WriteShort(msg, i); if (bits & U_MOREBITS) MSG_WriteByte(msg, bits & 255); #ifdef PROTOCOL_VERSION_FTE if (bits & U_FTE_EVENMORE) MSG_WriteByte (msg, evenmorebits&255); if (evenmorebits & U_FTE_YETMORE) MSG_WriteByte (msg, (evenmorebits>>8)&255); #endif if (bits & U_MODEL) MSG_WriteByte(msg, to->modelindex & 255); if (bits & U_FRAME) MSG_WriteByte(msg, to->frame); if (bits & U_COLORMAP) MSG_WriteByte(msg, to->colormap); if (bits & U_SKIN) MSG_WriteByte(msg, to->skinnum); if (bits & U_EFFECTS) MSG_WriteByte(msg, to->effects); if (bits & U_ORIGIN1) { if (client->mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) { MSG_WriteLongCoord(msg, to->origin[0]); } else { MSG_WriteCoord(msg, to->origin[0]); } } if (bits & U_ANGLE1) { MSG_WriteAngle(msg, to->angles[0]); } if (bits & U_ORIGIN2) { if (client->mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) { MSG_WriteLongCoord(msg, to->origin[1]); } else { MSG_WriteCoord(msg, to->origin[1]); } } if (bits & U_ANGLE2) { MSG_WriteAngle(msg, to->angles[1]); } if (bits & U_ORIGIN3) { if (client->mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) { MSG_WriteLongCoord(msg, to->origin[2]); } else { MSG_WriteCoord(msg, to->origin[2]); } } if (bits & U_ANGLE3) { MSG_WriteAngle(msg, to->angles[2]); } } /* ============= SV_EmitPacketEntities Writes a delta update of a packet_entities_t to the message. ============= */ static void SV_EmitPacketEntities (client_t *client, packet_entities_t *to, sizebuf_t *msg) { int oldindex, newindex, oldnum, newnum, oldmax; client_frame_t *fromframe; packet_entities_t *from1; edict_t *ent; // this is the frame that we are going to delta update from if (client->delta_sequence != -1) { fromframe = &client->frames[client->delta_sequence & UPDATE_MASK]; from1 = &fromframe->entities; oldmax = from1->num_entities; MSG_WriteByte (msg, svc_deltapacketentities); MSG_WriteByte (msg, client->delta_sequence); } else { oldmax = 0; // no delta update from1 = NULL; MSG_WriteByte (msg, svc_packetentities); } newindex = 0; oldindex = 0; //Con_Printf ("---%i to %i ----\n", client->delta_sequence & UPDATE_MASK // , client->netchan.outgoing_sequence & UPDATE_MASK); while (newindex < to->num_entities || oldindex < oldmax) { newnum = newindex >= to->num_entities ? 9999 : to->entities[newindex].number; oldnum = oldindex >= oldmax ? 9999 : from1->entities[oldindex].number; if (newnum == oldnum) { // delta update from old position //Con_Printf ("delta %i\n", newnum); SV_WriteDelta (client, &from1->entities[oldindex], &to->entities[newindex], msg, false); oldindex++; newindex++; continue; } if (newnum < oldnum) { // this is a new entity, send it from the baseline if (newnum == 9999) { Sys_Printf("LOL, %d, %d, %d, %d %d %d\n", // nice message newnum, oldnum, to->num_entities, oldmax, client->netchan.incoming_sequence & UPDATE_MASK, client->delta_sequence & UPDATE_MASK); if (client->edict == NULL) Sys_Printf("demo\n"); } ent = EDICT_NUM(newnum); //Con_Printf ("baseline %i\n", newnum); SV_WriteDelta (client, &ent->e.baseline, &to->entities[newindex], msg, true); newindex++; continue; } if (newnum > oldnum) { // the old entity isn't present in the new message if (oldnum >= 512) { //yup, this is expensive. MSG_WriteShort (msg, oldnum | U_REMOVE | U_MOREBITS); MSG_WriteByte (msg, U_FTE_EVENMORE); if (oldnum >= 1024) { if (oldnum >= 1024 + 512) { MSG_WriteByte(msg, U_FTE_ENTITYDBL | U_FTE_ENTITYDBL2); } else { MSG_WriteByte(msg, U_FTE_ENTITYDBL2); } } else { MSG_WriteByte(msg, U_FTE_ENTITYDBL); } } else { MSG_WriteShort(msg, oldnum | U_REMOVE); } oldindex++; continue; } } MSG_WriteShort (msg, 0); // end of packetentities } static int TranslateEffects (edict_t *ent) { int fx = (int)ent->v->effects; if (pr_nqprogs) fx &= ~EF_MUZZLEFLASH; if (pr_nqprogs && (fx & EF_DIMLIGHT)) { if ((int)ent->v->items & IT_QUAD) fx |= EF_BLUE; if ((int)ent->v->items & IT_INVULNERABILITY) fx |= EF_RED; } return fx; } /* ============= SV_MVD_WritePlayersToClient ============= */ static void SV_MVD_WritePlayersToClient ( void ) { int j; demo_frame_t *demo_frame; demo_client_t *dcl; client_t *cl; edict_t *ent; if ( !sv.mvdrecording ) return; demo_frame = &demo.frames[demo.parsecount&UPDATE_MASK]; for (j = 0, cl = svs.clients, dcl = demo_frame->clients; j < MAX_CLIENTS; j++, cl++, dcl++) { if ( cl->state != cs_spawned || cl->spectator ) continue; ent = cl->edict; dcl->parsecount = demo.parsecount; VectorCopy(ent->v->origin, dcl->origin); VectorCopy(ent->v->angles, dcl->angles); dcl->angles[0] *= -3; #ifdef USE_PR2 if( cl->isBot ) VectorCopy(ent->v->v_angle, dcl->angles); #endif dcl->angles[2] = 0; // no roll angle if (ent->v->health <= 0) { // don't show the corpse looking around... dcl->angles[0] = 0; dcl->angles[1] = ent->v->angles[1]; dcl->angles[2] = 0; } dcl->weaponframe = ent->v->weaponframe; dcl->frame = ent->v->frame; dcl->skinnum = ent->v->skin; dcl->model = ent->v->modelindex; dcl->effects = TranslateEffects(ent); dcl->flags = 0; dcl->fixangle = demo.fixangle[j]; demo.fixangle[j] = 0; dcl->sec = sv.time - cl->localtime; if (ent->v->health <= 0) dcl->flags |= DF_DEAD; if (ent->v->mins[2] != -24) dcl->flags |= DF_GIB; continue; } } /* ============= SV_PlayerVisibleToClient ============= */ qbool SV_PlayerVisibleToClient (client_t* client, int j, byte* pvs, edict_t* self_ent, edict_t* ent) { client_t* cl = &svs.clients[j]; // ZOID visibility tracking if (ent != self_ent && !(client->spec_track && client->spec_track - 1 == j)) { int i; if (cl->spectator) return false; if (pvs && ent->e.num_leafs >= 0) { // ignore if not touching a PV leaf for (i = 0; i < ent->e.num_leafs; i++) { if (pvs[ent->e.leafnums[i] >> 3] & (1 << (ent->e.leafnums[i] & 7))) { break; } } if (i == ent->e.num_leafs) { return false; // not visible } } } return true; } /* ============= SV_WritePlayersToClient ============= */ int SV_PMTypeForClient (client_t *cl); static void SV_WritePlayersToClient (client_t *client, client_frame_t *frame, byte *pvs, qbool disable_updates, sizebuf_t *msg) { int msec, pflags, pm_type = 0, pm_code = 0, i, j; usercmd_t cmd; int hideent = 0; int trackent = 0; qbool hide_players = fofs_hide_players && ((eval_t *)((byte *)(client->edict)->v + fofs_hide_players))->_int; if (fofs_hideentity) hideent = ((eval_t *)((byte *)(client->edict)->v + fofs_hideentity))->_int / pr_edict_size; if (fofs_trackent) { trackent = ((eval_t *)((byte *)(client->edict)->v + fofs_trackent))->_int; if (trackent < 1 || trackent > MAX_CLIENTS || svs.clients[trackent - 1].state != cs_spawned) trackent = 0; } frame->sv_time = sv.time; for (j = 0; j < MAX_CLIENTS; j++) { client_t* cl = &svs.clients[j]; edict_t* ent = NULL; edict_t* self_ent = NULL; edict_t* track_ent = NULL; if (fofs_visibility) { // Presume not visible ((eval_t *)((byte *)(cl->edict)->v + fofs_visibility))->_int &= ~(1 << (client - svs.clients)); } if (cl->state != cs_spawned) continue; if (client != cl && hide_players) continue; if (trackent && cl == client) { cl = &svs.clients[trackent - 1]; // fakenicking. track_ent = svs.clients[trackent - 1].edict; self_ent = track_ent; ent = track_ent; } else { self_ent = client->edict; ent = cl->edict; } // set up edicts. if (!SV_PlayerVisibleToClient (client, j, pvs, self_ent, ent)) continue; if (fofs_visibility) { // Update flags so mods can tell what was visible ((eval_t *)((byte *)(ent)->v + fofs_visibility))->_int |= (1 << (client - svs.clients)); } if (j == hideent - 1) continue; if (j == trackent - 1) continue; if (disable_updates && ent != self_ent) { // Vladis continue; } //==================================================== // OK, seems we must send info about this player below pflags = PF_MSEC | PF_COMMAND; if (ent->v->modelindex != sv_playermodel) pflags |= PF_MODEL; for (i=0 ; i<3 ; i++) if (ent->v->velocity[i]) pflags |= PF_VELOCITY1<v->effects) pflags |= PF_EFFECTS; if (ent->v->skin || ent->v->modelindex >= 256) pflags |= PF_SKINNUM; if (ent->v->health <= 0) pflags |= PF_DEAD; if (ent->v->mins[2] != -24) pflags |= PF_GIB; if (cl->spectator) { // only sent origin and velocity to spectators pflags &= PF_VELOCITY1 | PF_VELOCITY2 | PF_VELOCITY3; } else if (ent == self_ent) { // don't send a lot of data on personal entity pflags &= ~(PF_MSEC|PF_COMMAND); if (ent->v->weaponframe) pflags |= PF_WEAPONFRAME; } // Z_EXT_PM_TYPE protocol extension // encode pm_type and jump_held into pm_code pm_type = track_ent ? PM_LOCK : SV_PMTypeForClient (cl); switch (pm_type) { case PM_DEAD: pm_code = PMC_NORMAL; // plus PF_DEAD break; case PM_NORMAL: pm_code = PMC_NORMAL; if (cl->jump_held) pm_code = PMC_NORMAL_JUMP_HELD; break; case PM_OLD_SPECTATOR: pm_code = PMC_OLD_SPECTATOR; break; case PM_SPECTATOR: pm_code = PMC_SPECTATOR; break; case PM_FLY: pm_code = PMC_FLY; break; case PM_NONE: pm_code = PMC_NONE; break; case PM_LOCK: pm_code = PMC_LOCK; break; default: assert (false); } pflags |= pm_code << PF_PMC_SHIFT; // Z_EXT_PF_ONGROUND protocol extension if ((int)ent->v->flags & FL_ONGROUND) pflags |= PF_ONGROUND; // Z_EXT_PF_SOLID protocol extension if (ent->v->solid == SOLID_BBOX || ent->v->solid == SOLID_SLIDEBOX) pflags |= PF_SOLID; if (pm_type == PM_LOCK && ent == self_ent) pflags |= PF_COMMAND; // send forced view angles if (client->spec_track && client->spec_track - 1 == j && ent->v->weaponframe) pflags |= PF_WEAPONFRAME; MSG_WriteByte (msg, svc_playerinfo); MSG_WriteByte (msg, j); MSG_WriteShort (msg, pflags); if (client->mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) { MSG_WriteLongCoord(msg, ent->v->origin[0]); MSG_WriteLongCoord(msg, ent->v->origin[1]); MSG_WriteLongCoord(msg, ent->v->origin[2]); } else { MSG_WriteCoord(msg, ent->v->origin[0]); MSG_WriteCoord(msg, ent->v->origin[1]); MSG_WriteCoord(msg, ent->v->origin[2]); } MSG_WriteByte (msg, ent->v->frame); if (pflags & PF_MSEC) { msec = 1000*(sv.time - cl->localtime); if (msec > 255) msec = 255; MSG_WriteByte (msg, msec); } if (pflags & PF_COMMAND) { cmd = cl->lastcmd; if (ent->v->health <= 0) { // don't show the corpse looking around... cmd.angles[0] = 0; cmd.angles[1] = ent->v->angles[1]; cmd.angles[0] = 0; } cmd.buttons = 0; // never send buttons cmd.impulse = 0; // never send impulses if (ent == self_ent) { // this is PM_LOCK, we only want to send view angles VectorCopy(ent->v->v_angle, cmd.angles); cmd.forwardmove = 0; cmd.sidemove = 0; cmd.upmove = 0; } if ((client->extensions & Z_EXT_VWEP) && sv.vw_model_name[0] && fofs_vw_index) { cmd.impulse = EdictFieldFloat (ent, fofs_vw_index); } MSG_WriteDeltaUsercmd (msg, &nullcmd, &cmd, 0); } for (i=0 ; i<3 ; i++) if (pflags & (PF_VELOCITY1<v->velocity[i]); if (pflags & PF_MODEL) MSG_WriteByte (msg, ent->v->modelindex); if (pflags & PF_SKINNUM) MSG_WriteByte (msg, (int)ent->v->skin | (((pflags & PF_MODEL)&&(ent->v->modelindex >= 256))<<7)); if (pflags & PF_EFFECTS) MSG_WriteByte (msg, TranslateEffects(ent)); if (pflags & PF_WEAPONFRAME) MSG_WriteByte (msg, ent->v->weaponframe); } } /* ============= SV_EntityVisibleToClient ============= */ qbool SV_EntityVisibleToClient (client_t* client, int e, byte* pvs) { edict_t* ent = EDICT_NUM (e); if (pr_nqprogs) { // don't send the player's model to himself if (e < MAX_CLIENTS + 1 && svs.clients[e-1].state != cs_free) return false; } // ignore ents without visible models if (!ent->v->modelindex || !*PR_GetEntityString(ent->v->model)) return false; if ( pvs && ent->e.num_leafs >= 0 ) { int i; // ignore if not touching a PV leaf for (i=0 ; i < ent->e.num_leafs ; i++) if (pvs[ent->e.leafnums[i] >> 3] & (1 << (ent->e.leafnums[i]&7) )) break; if (i == ent->e.num_leafs) return false; // not visible } return true; } /* ============= SV_WriteEntitiesToClient Encodes the current state of the world as a svc_packetentities messages and possibly a svc_nails message and svc_playerinfo messages ============= */ void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg, qbool recorder) { qbool disable_updates; // disables sending entities to the client int e, i, max_packet_entities; packet_entities_t *pack; client_frame_t *frame; entity_state_t *state; edict_t *ent; byte *pvs; int hideent; unsigned int client_flag = (1 << (client - svs.clients)); edict_t *clent = client->edict; float distances[MAX_PACKETENTITIES_POSSIBLE] = { 0 }; float distance; int position; vec3_t org; if ( recorder ) { if ( !sv.mvdrecording ) return; } // this is the frame we are creating frame = &client->frames[client->netchan.incoming_sequence & UPDATE_MASK]; // find the client's PVS if ( recorder ) {// demo hideent = 0; pvs = NULL; // ignore PVS for demos max_packet_entities = MAX_MVD_PACKET_ENTITIES; disable_updates = false; // updates always allowed in demos } else {// normal client vec3_t org; int trackent = 0; if (fofs_hideentity) hideent = ((eval_t *)((byte *)(client->edict)->v + fofs_hideentity))->_int / pr_edict_size; else hideent = 0; if (fofs_trackent) { trackent = ((eval_t *)((byte *)(client->edict)->v + fofs_trackent))->_int; if (trackent < 1 || trackent > MAX_CLIENTS || svs.clients[trackent - 1].state != cs_spawned) trackent = 0; } // we should use org of tracked player in case or trackent. if (trackent) { VectorAdd (svs.clients[trackent - 1].edict->v->origin, svs.clients[trackent - 1].edict->v->view_ofs, org); } else { VectorAdd (client->edict->v->origin, client->edict->v->view_ofs, org); } pvs = CM_FatPVS (org); // search some PVS max_packet_entities = (client->fteprotocolextensions & FTE_PEXT_256PACKETENTITIES) ? MAX_PEXT256_PACKET_ENTITIES : MAX_PACKET_ENTITIES; if (client->disable_updates_stop > realtime) { #define ISUNDERWATER(x) ((x) == CONTENTS_WATER || (x) == CONTENTS_SLIME || (x) == CONTENTS_LAVA) // server flash should not work underwater int content = CM_HullPointContents(&sv.worldmodel->hulls[0], 0, client->edict->v->origin); disable_updates = !ISUNDERWATER(content); #undef ISUNDERWATER } else { disable_updates = false; } } // send over the players in the PVS if ( recorder ) SV_MVD_WritePlayersToClient (); // nice, no params at all! else SV_WritePlayersToClient (client, frame, pvs, disable_updates, msg); // put other visible entities into either a packet_entities or a nails message pack = &frame->entities; pack->num_entities = 0; numnails = 0; if (!disable_updates) {// Vladis, server flash // QW protocol can only handle 512 entities. Any entity with number >= 512 will be invisible // from ZQuake unless using protocol extensions. // max_edicts = min(sv.num_edicts, MAX_EDICTS); for (e = pr_nqprogs ? 1 : MAX_CLIENTS + 1, ent = EDICT_NUM(e); e < sv.num_edicts; e++, ent = NEXT_EDICT(ent)) { if (!SV_EntityVisibleToClient(client, e, pvs)) { if (fofs_visibility) { ((eval_t *)((byte *)(ent)->v + fofs_visibility))->_int &= ~client_flag; } continue; } if (fofs_visibility) { // Don't include other filters in logic for setting this field ((eval_t *)((byte *)(ent)->v + fofs_visibility))->_int |= (1 << (client - svs.clients)); } if (e == hideent) { continue; } if (SV_AddNailUpdate (ent)) continue; // added to the special update list if (clent) { VectorAdd(ent->v->absmin, ent->v->absmax, org); VectorMA(clent->v->origin, -0.5, org, org); distance = DotProduct(org, org); //Length // add to the packetentities if (pack->num_entities == max_packet_entities) { // replace the furthest entity float furthestdist = -1; int best = -1; for (i = 0; i < max_packet_entities; i++) { if (furthestdist < distances[i]) { furthestdist = distances[i]; best = i; } } if (furthestdist <= distance || best == -1) { continue; } // shuffle other entities down, add to end if (best < pack->num_entities - 1) { memmove(&pack->entities[best], &pack->entities[best + 1], sizeof(pack->entities[0]) * (pack->num_entities - 1 - best)); memmove(&distances[best], &distances[best + 1], sizeof(distances[0]) * (pack->num_entities - 1 - best)); } position = pack->num_entities - 1; } else { position = pack->num_entities++; } distances[position] = distance; } else { if (pack->num_entities == max_packet_entities) { continue; } position = pack->num_entities++; } state = &pack->entities[position]; memset(state, 0, sizeof(*state)); state->number = e; state->flags = 0; VectorCopy (ent->v->origin, state->origin); VectorCopy (ent->v->angles, state->angles); state->modelindex = ent->v->modelindex; state->frame = ent->v->frame; state->colormap = ent->v->colormap; state->skinnum = ent->v->skin; state->effects = TranslateEffects(ent); } } // server flash // encode the packet entities as a delta from the // last packetentities acknowledged by the client SV_EmitPacketEntities (client, pack, msg); // now add the specialized nail update SV_EmitNailUpdate (msg, recorder); // Translate NQ progs' EF_MUZZLEFLASH to svc_muzzleflash if (pr_nqprogs) { for (e=1, ent=EDICT_NUM(e) ; e < sv.num_edicts ; e++, ent = NEXT_EDICT(ent)) { // ignore ents without visible models if (!ent->v->modelindex || !*PR_GetEntityString(ent->v->model)) continue; // ignore if not touching a PV leaf (meag: this does nothing... complete or remove?) if (pvs && ent->e.num_leafs >= 0) { for (i = 0; i < ent->e.num_leafs; i++) if (pvs[ent->e.leafnums[i] >> 3] & (1 << (ent->e.leafnums[i] & 7))) break; } if ((int)ent->v->effects & EF_MUZZLEFLASH) { ent->v->effects = (int)ent->v->effects & ~EF_MUZZLEFLASH; MSG_WriteByte (msg, svc_muzzleflash); MSG_WriteShort (msg, e); } } } } /* ============ SV_SetVisibleEntitiesForBot ============ */ void SV_SetVisibleEntitiesForBot (client_t* client) { int j = 0; int e = 0; unsigned int client_flag = 1 << (client - svs.clients); vec3_t org; byte* pvs = NULL; if (!fofs_visibility) return; VectorAdd (client->edict->v->origin, client->edict->v->view_ofs, org); pvs = CM_FatPVS (org); // search some PVS // players first for (j = 0; j < MAX_CLIENTS; j++) { if (SV_PlayerVisibleToClient(client, j, pvs, client->edict, svs.clients[j].edict)) { ((eval_t *)((byte *)(svs.clients[j].edict)->v + fofs_visibility))->_int |= client_flag; } else { ((eval_t *)((byte *)(svs.clients[j].edict)->v + fofs_visibility))->_int &= ~client_flag; } } // Other entities for (e = pr_nqprogs ? 1 : MAX_CLIENTS + 1; e < sv.num_edicts; e++) { edict_t* ent = EDICT_NUM (e); if (SV_EntityVisibleToClient(client, e, pvs)) { ((eval_t *)((byte *)(ent)->v + fofs_visibility))->_int |= client_flag; } else { ((eval_t *)((byte *)(ent)->v + fofs_visibility))->_int &= ~client_flag; } } } qbool SV_SkipCommsBotMessage(client_t* client) { extern cvar_t sv_serveme_fix; return sv_serveme_fix.value && client->spectator && !strcmp(client->name, "[ServeMe]"); } #endif // !CLIENTONLY mvdsv-0.35/src/sv_init.c000066400000000000000000000407101427146041000152070ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef CLIENTONLY #include "qwsvdef.h" server_static_t svs; // persistent server info server_t sv; // local server demo_t demo; // server demo struct char localmodels[MAX_MODELS][5]; // inline model names for precache //char localinfo[MAX_LOCALINFO_STRING+1]; // local game info ctxinfo_t _localinfo_; int fofs_items2; int fofs_maxspeed, fofs_gravity; int fofs_movement; int fofs_vw_index; int fofs_hideentity; int fofs_trackent; int fofs_visibility; int fofs_hide_players; int fofs_teleported; /* ================ SV_ModelIndex ================ */ int SV_ModelIndex (char *name) { int i; if (!name || !name[0]) return 0; for (i=0 ; ie.free) continue; // create baselines for all player slots, // and any other edict that has a visible model if (entnum > MAX_CLIENTS && !svent->v->modelindex) continue; // // create entity baseline // svent->e.baseline.number = entnum; VectorCopy (svent->v->origin, svent->e.baseline.origin); VectorCopy (svent->v->angles, svent->e.baseline.angles); svent->e.baseline.frame = svent->v->frame; svent->e.baseline.skinnum = svent->v->skin; if (entnum > 0 && entnum <= MAX_CLIENTS) { svent->e.baseline.colormap = entnum; svent->e.baseline.modelindex = SV_ModelIndex("progs/player.mdl"); } else { svent->e.baseline.colormap = 0; svent->e.baseline.modelindex = svent->v->modelindex; } } sv.num_baseline_edicts = sv.num_edicts; } /* ================ SV_SaveSpawnparms Grabs the current state of the progs serverinfo flags and each client for saving across the transition to another level ================ */ static void SV_SaveSpawnparms (void) { int i, j; if (!sv.state) return; // no progs loaded yet // serverflags is the only game related thing maintained svs.serverflags = PR_GLOBAL(serverflags); for (i=0, sv_client = svs.clients ; istate != cs_spawned) continue; // needs to reconnect sv_client->state = cs_connected; // call the progs to get default spawn parms for the new client pr_global_struct->self = EDICT_TO_PROG(sv_client->edict); PR_GameSetChangeParms(); for (j=0 ; jspawn_parms[j] = (&PR_GLOBAL(parm1))[j]; } } static unsigned SV_CheckModel(char *mdl) { unsigned char *buf; unsigned short crc; int filesize; int mark; mark = Hunk_LowMark (); buf = (byte *) FS_LoadHunkFile (mdl, &filesize); if (!buf) { if (!strcmp (mdl, "progs/player.mdl")) return 33168; else if (!strcmp (mdl, "progs/newplayer.mdl")) return 62211; else if (!strcmp (mdl, "progs/eyes.mdl")) return 6967; else SV_Error ("SV_CheckModel: could not load %s\n", mdl); } crc = CRC_Block (buf, filesize); Hunk_FreeToLowMark (mark); return crc; } /* ================ SV_SpawnServer Change the server to a new map, taking all connected clients along with it. This is called from the SV_Map_f() function, and when loading .sav files ================ */ void SV_SpawnServer(char *mapname, qbool devmap, char* entityfile, qbool loading_savegame) { extern func_t ED_FindFunctionOffset (char *name); edict_t *ent; int i; int skill_level = current_skill; extern cvar_t sv_loadentfiles, sv_loadentfiles_dir; char *entitystring; char oldmap[MAP_NAME_LEN]; extern qbool sv_allow_cheats; extern cvar_t sv_cheats, sv_paused, sv_bigcoords; #ifndef SERVERONLY extern void CL_ClearState (void); #endif // store old map name snprintf (oldmap, MAP_NAME_LEN, "%s", sv.mapname); Con_DPrintf ("SpawnServer: %s\n",mapname); #ifndef SERVERONLY // As client+server we do it here. // As serveronly we do it in NET_Init(). NET_InitServer(); #endif SV_SaveSpawnparms (); SV_LoadAccounts(); #ifdef USE_PR2 // remove bot clients for (i = 0; i < MAX_CLIENTS; i++) { if( sv_vm && svs.clients[i].isBot ) { svs.clients[i].old_frags = 0; svs.clients[i].edict->v->frags = 0.0; svs.clients[i].name[0] = 0; svs.clients[i].state = cs_free; Info_RemoveAll(&svs.clients[i]._userinfo_ctx_); Info_RemoveAll(&svs.clients[i]._userinfoshort_ctx_); SV_FullClientUpdate(&svs.clients[i], &sv.reliable_datagram); svs.clients[i].isBot = 0; } } #endif // Shutdown game. PR_GameShutDown(); PR_UnLoadProgs(); svs.spawncount++; // any partially connected client will be restarted #ifndef SERVERONLY com_serveractive = false; #endif sv.state = ss_dead; sv.paused = false; Cvar_SetROM(&sv_paused, "0"); Host_ClearMemory(); #ifndef SERVERONLY if (!oldmap[0]) { Cbuf_InsertTextEx(&cbuf_server, "exec server.cfg\n"); Cbuf_ExecuteEx(&cbuf_server); } #endif if (loading_savegame) { Cvar_SetValue(&skill, skill_level); Cvar_SetValue(&deathmatch, 0); Cvar_SetValue(&coop, 0); Cvar_SetValue(&teamplay, 0); Cvar_SetValue(&maxclients, 1); Cvar_Set(&sv_progsname, "spprogs"); // force progsname #ifdef USE_PR2 Cvar_SetValue(&sv_progtype, 0); // force .dat #endif } #ifdef FTE_PEXT_FLOATCOORDS if (sv_bigcoords.value) { msg_coordsize = 4; msg_anglesize = 2; } else { msg_coordsize = 2; msg_anglesize = 1; } #endif if ((int)coop.value) Cvar_Set (&deathmatch, "0"); current_skill = (int) (skill.value + 0.5); if (current_skill < 0) current_skill = 0; Cvar_Set (&skill, va("%d", current_skill)); if (current_skill > 3) current_skill = 3; if ((sv_cheats.value || devmap) && !sv_allow_cheats) { sv_allow_cheats = true; Info_SetValueForStarKey (svs.info, "*cheats", "ON", MAX_SERVERINFO_STRING); } else if ((!sv_cheats.value && !devmap) && sv_allow_cheats) { sv_allow_cheats = false; Info_SetValueForStarKey (svs.info, "*cheats", "", MAX_SERVERINFO_STRING); } // wipe the entire per-level structure // NOTE: this also set sv.mvdrecording to false, so calling SV_MVD_Record() at end of function memset (&sv, 0, sizeof(sv)); sv.max_edicts = MAX_EDICTS_SAFE; sv.datagram.maxsize = sizeof(sv.datagram_buf); sv.datagram.data = sv.datagram_buf; sv.datagram.allowoverflow = true; sv.reliable_datagram.maxsize = sizeof(sv.reliable_datagram_buf); sv.reliable_datagram.data = sv.reliable_datagram_buf; sv.multicast.maxsize = sizeof(sv.multicast_buf); sv.multicast.data = sv.multicast_buf; sv.signon.maxsize = sizeof(sv.signon_buffers[0]); sv.signon.data = sv.signon_buffers[0]; sv.num_signon_buffers = 1; sv.time = 1.0; // load progs to get entity field count // which determines how big each edict is // and allocate edicts PR_LoadProgs (); #ifdef WITH_NQPROGS PR_InitPatchTables(); #endif PR_InitProg(); for (i = 0; i < sv.max_edicts; i++) { sv.edicts[i].v = (entvars_t *)((byte *)sv.game_edicts + i * pr_edict_size); sv.edicts[i].e.entnum = i; sv.edicts[i].e.area.ed = &sv.edicts[i]; // yeah, pretty funny, but this help to find which edict_t own this area (link_t) PR_ClearEdict(&sv.edicts[i]); } fofs_items2 = ED_FindFieldOffset ("items2"); // ZQ_ITEMS2 extension fofs_maxspeed = ED_FindFieldOffset ("maxspeed"); fofs_gravity = ED_FindFieldOffset ("gravity"); fofs_movement = ED_FindFieldOffset ("movement"); fofs_vw_index = ED_FindFieldOffset ("vw_index"); fofs_hideentity = ED_FindFieldOffset ("hideentity"); fofs_trackent = ED_FindFieldOffset ("trackent"); fofs_visibility = ED_FindFieldOffset ("visclients"); fofs_hide_players = ED_FindFieldOffset ("hideplayers"); fofs_teleported = ED_FindFieldOffset ("teleported"); #ifdef MVD_PEXT1_HIGHLAGTELEPORT if (fofs_teleported) { svs.mvdprotocolextension1 |= MVD_PEXT1_HIGHLAGTELEPORT; } else { svs.mvdprotocolextension1 &= ~MVD_PEXT1_HIGHLAGTELEPORT; } #endif #ifdef MVD_PEXT1_SERVERSIDEWEAPON { extern cvar_t sv_pext_mvdsv_serversideweapon; // Cheap 'ktx' detection if (sv_pext_mvdsv_serversideweapon.value && strstr(Cvar_String("qwm_name"), "KTX")) { svs.mvdprotocolextension1 |= MVD_PEXT1_SERVERSIDEWEAPON; #ifdef MVD_PEXT1_SERVERSIDEWEAPON2 svs.mvdprotocolextension1 |= MVD_PEXT1_SERVERSIDEWEAPON2; #endif } else { svs.mvdprotocolextension1 &= ~MVD_PEXT1_SERVERSIDEWEAPON; #ifdef MVD_PEXT1_SERVERSIDEWEAPON2 svs.mvdprotocolextension1 &= ~MVD_PEXT1_SERVERSIDEWEAPON2; #endif } } #endif #ifdef MVD_PEXT1_DEBUG_ANTILAG { extern cvar_t sv_debug_antilag; if (sv_debug_antilag.value) { svs.mvdprotocolextension1 |= MVD_PEXT1_DEBUG_ANTILAG; } else { svs.mvdprotocolextension1 &= ~MVD_PEXT1_DEBUG_ANTILAG; } } #endif #ifdef MVD_PEXT1_DEBUG_WEAPON { extern cvar_t sv_debug_weapons; if (sv_debug_weapons.value) { svs.mvdprotocolextension1 |= MVD_PEXT1_DEBUG_WEAPON; } else { svs.mvdprotocolextension1 &= ~MVD_PEXT1_DEBUG_WEAPON; } } #endif // find optional QC-exported functions. // we have it here, so we set it to NULL in case of PR2 progs. mod_SpectatorConnect = ED_FindFunctionOffset ("SpectatorConnect"); mod_SpectatorThink = ED_FindFunctionOffset ("SpectatorThink"); mod_SpectatorDisconnect = ED_FindFunctionOffset ("SpectatorDisconnect"); mod_ChatMessage = ED_FindFunctionOffset ("ChatMessage"); mod_UserInfo_Changed = ED_FindFunctionOffset ("UserInfo_Changed"); mod_ConsoleCmd = ED_FindFunctionOffset ("ConsoleCmd"); mod_UserCmd = ED_FindFunctionOffset ("UserCmd"); mod_localinfoChanged = ED_FindFunctionOffset ("localinfoChanged"); GE_ClientCommand = ED_FindFunctionOffset ("GE_ClientCommand"); GE_PausedTic = ED_FindFunctionOffset ("GE_PausedTic"); GE_ShouldPause = ED_FindFunctionOffset ("GE_ShouldPause"); // leave slots at start for clients only sv.num_edicts = MAX_CLIENTS+1; for (i=0 ; iv->netname, svs.clients[i].name); // reserve edict. svs.clients[i].edict = ent; //ZOID - make sure we update frags right svs.clients[i].old_frags = 0; } // fill sv.mapname and sv.modelname with new map name strlcpy (sv.mapname, mapname, sizeof(sv.mapname)); snprintf (sv.modelname, sizeof(sv.modelname), "maps/%s.bsp", sv.mapname); #ifndef SERVERONLY // set cvar Cvar_ForceSet (&host_mapname, mapname); #endif if (!(sv.worldmodel = CM_LoadMap (sv.modelname, false, &sv.map_checksum, &sv.map_checksum2))) // true if bad map { Con_Printf ("Cant load map %s, falling back to %s\n", mapname, oldmap); // fill mapname, sv.mapname and sv.modelname with old map name strlcpy (sv.mapname, oldmap, sizeof(sv.mapname)); snprintf (sv.modelname, sizeof(sv.modelname), "maps/%s.bsp", sv.mapname); mapname = oldmap; // and re-load old map sv.worldmodel = CM_LoadMap (sv.modelname, false, &sv.map_checksum, &sv.map_checksum2); // this should never happen if (!sv.worldmodel) SV_Error ("CM_LoadMap: bad map"); } { extern cvar_t sv_extlimits, sv_bspversion; if (sv_extlimits.value == 0 || (sv_extlimits.value == 2 && sv_bspversion.value < 2)) { sv.max_edicts = min(sv.max_edicts, MAX_EDICTS_SAFE); } } sv.map_checksum2 = Com_TranslateMapChecksum (sv.mapname, sv.map_checksum2); sv.static_entity_count = 0; SV_ClearWorld (); // clear physics interaction links #ifdef USE_PR2 if ( sv_vm ) { sv.sound_precache[0] = ""; sv.model_precache[0] = ""; } else #endif { sv.sound_precache[0] = pr_strings; sv.model_precache[0] = pr_strings; } sv.model_precache[1] = sv.modelname; sv.models[1] = sv.worldmodel; for (i = 1; i < CM_NumInlineModels(); i++) { sv.model_precache[1+i] = localmodels[i]; sv.models[i+1] = CM_InlineModel (localmodels[i]); } //check player/eyes models for hacks sv.model_player_checksum = SV_CheckModel("progs/player.mdl"); sv.model_newplayer_checksum = SV_CheckModel("progs/newplayer.mdl"); sv.eyes_player_checksum = SV_CheckModel("progs/eyes.mdl"); // // spawn the rest of the entities on the map // // precache and static commands can be issued during // map initialization sv.state = ss_loading; #ifndef SERVERONLY com_serveractive = true; #endif ent = EDICT_NUM(0); ent->e.free = false; PR_SetEntityString(ent, ent->v->model, sv.modelname); ent->v->modelindex = 1; // world model ent->v->solid = SOLID_BSP; ent->v->movetype = MOVETYPE_PUSH; // information about the server PR_SetEntityString(ent, ent->v->netname, VersionStringFull()); PR_SetEntityString(ent, ent->v->targetname, SERVER_NAME); ent->v->impulse = VERSION_NUM; ent->v->items = pr_numbuiltins - 1; PR_SetGlobalString(PR_GLOBAL(mapname), sv.mapname); // serverflags are for cross level information (sigils) PR_GLOBAL(serverflags) = svs.serverflags; if (pr_nqprogs) { pr_globals[35] = deathmatch.value; pr_globals[36] = coop.value; pr_globals[37] = teamplay.value; NQP_Reset (); } if (pr_nqprogs) { // register the cvars that NetQuake provides for mod use const char **var, *nqcvars[] = {"gamecfg", "scratch1", "scratch2", "scratch3", "scratch4", "saved1", "saved2", "saved3", "saved4", "savedgamecfg", "temp1", NULL}; for (var = nqcvars; *var; var++) Cvar_Create((char *)/*stupid const warning*/ *var, "0", 0); } // run the frame start qc function to let progs check cvars if (!pr_nqprogs) SV_ProgStartFrame (false); // ********* External Entity support (.ent file(s) in gamedir/maps) pinched from ZQuake ********* // load and spawn all other entities entitystring = NULL; if ((int)sv_loadentfiles.value) { char ent_path[1024] = {0}; if (!entityfile || !entityfile[0]) entityfile = sv.mapname; // first try maps/sv_loadentfiles_dir/ if (sv_loadentfiles_dir.string[0]) { snprintf(ent_path, sizeof(ent_path), "maps/%s/%s.ent", sv_loadentfiles_dir.string, entityfile); entitystring = (char *) FS_LoadHunkFile(ent_path, NULL); } // try maps/ if not loaded yet. if (!entitystring) { snprintf(ent_path, sizeof(ent_path), "maps/%s.ent", entityfile); entitystring = (char *) FS_LoadHunkFile(ent_path, NULL); } if (entitystring) { Con_DPrintf ("Using entfile %s\n", ent_path); } } if (!entitystring) { entitystring = CM_EntityString(); } PR_LoadEnts(entitystring); // ********* End of External Entity support code ********* // look up some model indexes for specialized message compression SV_FindModelNumbers (); // all spawning is completed, any further precache statements // or prog writes to the signon message are errors sv.state = ss_active; // run two frames to allow everything to settle SV_Physics (); sv.time += 0.1; SV_Physics (); sv.time += 0.1; sv.old_time = sv.time; // save movement vars SV_SetMoveVars(); // create a baseline for more efficient communications SV_CreateBaseline (); sv.signon_buffer_size[sv.num_signon_buffers-1] = sv.signon.cursize; Info_SetValueForKey (svs.info, "map", sv.mapname, MAX_SERVERINFO_STRING); // calltimeofday. { extern void PF_calltimeofday (void); pr_global_struct->time = sv.time; pr_global_struct->self = 0; PF_calltimeofday(); } Con_DPrintf ("Server spawned.\n"); // we change map - clear whole demo struct and sent initial state to all dest if any (for QTV only I thought) SV_MVD_Record(NULL, true); #ifndef SERVERONLY CL_ClearState (); #endif } #endif // !CLIENTONLY mvdsv-0.35/src/sv_login.c000066400000000000000000000465361427146041000153700ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef CLIENTONLY #include "qwsvdef.h" #ifdef WEBSITE_LOGIN_SUPPORT #undef WEBSITE_LOGIN_SUPPORT #endif #if defined(SERVERONLY) && defined(WWW_INTEGRATION) #define WEBSITE_LOGIN_SUPPORT #include "central.h" #endif #define MAX_ACCOUNTS 1000 #define MAX_FAILURES 10 #define MAX_LOGINNAME (DIGEST_SIZE * 2 + 1) #define ACC_FILE "accounts" #define ACC_DIR "users" cvar_t sv_login = { "sv_login", "0" }; // if enabled, login required #ifdef WEBSITE_LOGIN_SUPPORT cvar_t sv_login_web = { "sv_login_web", "1" }; // 0=local files, 1=auth via website (bans can be in local files), 2=mandatory auth (must have account in local files) #define LoginModeFileBased() ((int)sv_login_web.value == 0) #define LoginModeOptionalWeb() ((int)sv_login_web.value == 1) #define LoginModeMandatoryWeb() ((int)sv_login_web.value == 2) #define LoginMustHaveLocalAccount() (LoginModeMandatoryWeb() || LoginModeFileBased()) #define WebLoginsEnabled() (!LoginModeFileBased()) #else #define LoginModeFileBased() (1) #define LoginModeOptionalWeb() (0) #define LoginModeMandatoryWeb() (0) #define LoginMustHaveLocalAccount() (1) #define WebLoginsEnabled() (0) #endif extern cvar_t sv_hashpasswords; static void SV_SuccessfulLogin(client_t* cl); static void SV_BlockedLogin(client_t* cl); static void SV_ForceClientName(client_t* cl, const char* forced_name); typedef enum { a_free, a_ok, a_blocked } acc_state_t; typedef enum { use_log, use_ip } quse_t; typedef struct { char login[MAX_LOGINNAME]; char pass[MAX_LOGINNAME]; int failures; int inuse; ipfilter_t address; acc_state_t state; quse_t use; } account_t; static account_t accounts[MAX_ACCOUNTS]; static int num_accounts = 0; static qbool validAcc(char* acc) { char* s = acc; for (; *acc; acc++) { if (*acc < 'a' || *acc > 'z') if (*acc < 'A' || *acc > 'Z') if (*acc < '0' || *acc > '9') if (*acc != '.' && *acc != '_') return false; } return acc - s <= MAX_LOGINNAME && acc - s >= 3; } /* ================= WriteAccounts Writes account list to disk ================= */ static void WriteAccounts(void) { int c; FILE* f; account_t* acc; //Sys_mkdir(ACC_DIR); if ((f = fopen(va("%s/" ACC_FILE, fs_gamedir), "wt")) == NULL) { Con_Printf("Warning: couldn't open for writing " ACC_FILE "\n"); return; } for (acc = accounts, c = 0; c < num_accounts; acc++) { if (acc->state == a_free) continue; if (acc->use == use_log) fprintf(f, "%s %s %d %d\n", acc->login, acc->pass, acc->state, acc->failures); else fprintf(f, "%s %s %d\n", acc->login, acc->pass, acc->state); c++; } fclose(f); // force cache rebuild. FS_FlushFSHash(); } /* ================= SV_LoadAccounts loads account list from disk ================= */ qbool StringToFilter(char* s, ipfilter_t* f); void SV_LoadAccounts(void) { int i; FILE* f; account_t* acc = accounts; client_t* cl; if ((f = fopen(va("%s/" ACC_FILE, fs_gamedir), "rt")) == NULL) { Con_DPrintf("couldn't open " ACC_FILE "\n"); // logout num_accounts = 0; for (cl = svs.clients; cl - svs.clients < MAX_CLIENTS; cl++) { if (cl->logged > 0) { cl->logged = 0; } if (!cl->logged_in_via_web) { cl->login[0] = 0; } } return; } while (!feof(f)) { memset(acc, 0, sizeof(account_t)); // Is realy safe to use 'fscanf(f, "%s", s)'? FIXME! if (fscanf(f, "%s", acc->login) != 1) { Con_Printf("Error reading account data\n"); break; } if (StringToFilter(acc->login, &acc->address)) { strlcpy(acc->pass, acc->login, MAX_LOGINNAME); acc->use = use_ip; if (fscanf(f, "%s %d\n", acc->pass, (int*)&acc->state) != 2) { Con_Printf("Error reading account data\n"); break; } } else { if (fscanf(f, "%s %d %d\n", acc->pass, (int*)&acc->state, &acc->failures) != 3) { Con_Printf("Error reading account data\n"); break; } } if (acc->state != a_free) // lol? acc++; } num_accounts = acc - accounts; fclose(f); // for every connected client check if their login is still valid for (cl = svs.clients; cl - svs.clients < MAX_CLIENTS; cl++) { if (cl->state < cs_connected) continue; if (cl->logged <= 0) continue; if (cl->logged_in_via_web) continue; for (i = 0, acc = accounts; i < num_accounts; i++, acc++) if ((acc->use == use_log && !strncmp(acc->login, cl->login, CLIENT_LOGIN_LEN)) || (acc->use == use_ip && !strcmp(acc->login, va("%d.%d.%d.%d", cl->realip.ip[0], cl->realip.ip[1], cl->realip.ip[2], cl->realip.ip[3])))) break; if (i < num_accounts && acc->state == a_ok) { // login again if possible if (!acc->inuse || acc->use == use_ip) { cl->logged = i + 1; if (acc->use == use_ip) strlcpy(cl->login, acc->pass, CLIENT_LOGIN_LEN); acc->inuse++; continue; } } // login is not valid anymore, logout cl->logged = 0; cl->login[0] = 0; } } /* ================= SV_CreateAccount_f acc_create [] if password is not given, login will be used for password login/pass has to be max 16 chars and at least 3, only regular chars are acceptable ================= */ void SV_CreateAccount_f(void) { int i, spot, c; ipfilter_t adr; quse_t use; if (Cmd_Argc() < 2) { Con_Printf("usage: acc_create []\n acc_create
\nmaximum %d characters for login/pass\n", MAX_LOGINNAME - 1); //bliP: address typo return; } if (num_accounts == MAX_ACCOUNTS) { Con_Printf("MAX_ACCOUNTS reached\n"); return; } if (StringToFilter(Cmd_Argv(1), &adr)) { use = use_ip; if (Cmd_Argc() < 3) { Con_Printf("usage: acc_create
\nmaximum %d characters for username\n", MAX_LOGINNAME - 1); //bliP; address typo return; } } else { use = use_log; // validate user login/pass if (!validAcc(Cmd_Argv(1))) { Con_Printf("Invalid login!\n"); return; } if (Cmd_Argc() == 4 && !validAcc(Cmd_Argv(2))) { Con_Printf("Invalid pass!\n"); return; } } // find free spot, check if login exist; for (i = 0, c = 0, spot = -1; c < num_accounts; i++) { if (accounts[i].state == a_free) { if (spot == -1) spot = i; continue; } if (!strcasecmp(accounts[i].login, Cmd_Argv(1)) || (use == use_ip && !strcasecmp(accounts[i].login, Cmd_Argv(2)))) break; c++; } if (c < num_accounts) { Con_Printf("Login already in use\n"); return; } if (spot == -1) spot = i; // create an account num_accounts++; strlcpy(accounts[spot].login, Cmd_Argv(1), MAX_LOGINNAME); if (Cmd_Argc() == 3) i = 2; else i = 1; strlcpy(accounts[spot].pass, (int)sv_hashpasswords.value && use == use_log ? SHA1(Cmd_Argv(i)) : Cmd_Argv(i), MAX_LOGINNAME); accounts[spot].state = a_ok; accounts[spot].use = use; Con_Printf("login %s created\n", Cmd_Argv(1)); WriteAccounts(); } /* ================= SV_RemoveAccount_f acc_remove removes the login ================= */ void SV_RemoveAccount_f(void) { int i, c, j; if (Cmd_Argc() < 2) { Con_Printf("usage: acc_remove \n"); return; } for (i = 0, c = 0; c < num_accounts; i++) { if (accounts[i].state == a_free) continue; if (!strcasecmp(accounts[i].login, Cmd_Argv(1))) { // Logout anyone using this login if ((int)sv_login.value == 1) { // Mandatory web logins, or using local files if (LoginMustHaveLocalAccount()) { for (j = 0; j < MAX_CLIENTS; ++j) { client_t* cl = &svs.clients[j]; if (!strcasecmp(cl->login, Cmd_Argv(1))) { SV_Logout(cl); SV_DropClient(cl); } } } } // Update 'logged' pointers back to accounts list if (i != num_accounts - 1) { memcpy(&accounts[i], &accounts[num_accounts - 1], sizeof(accounts[i])); memset(&accounts[num_accounts - 1], 0, sizeof(accounts[num_accounts - 1])); // Update references from the last account which we just moved for (j = 0; j < MAX_CLIENTS; ++j) { client_t* cl = &svs.clients[j]; if (cl->logged == num_accounts) { cl->logged = i + 1; } } } num_accounts--; Con_Printf("login %s removed\n", Cmd_Argv(1)); WriteAccounts(); return; } c++; } Con_Printf("account for %s not found\n", Cmd_Argv(1)); } /* ================= SV_ListAccount_f shows the list of accounts ================= */ void SV_ListAccount_f(void) { int i, c; if (!num_accounts) { Con_Printf("account list is empty\n"); return; } Con_Printf("account list:\n"); for (i = 0, c = 0; c < num_accounts; i++) { if (accounts[i].state != a_free) { Con_Printf("%.16s %s\n", accounts[i].login, accounts[i].state == a_ok ? "" : "blocked"); c++; } } Con_Printf("%d login(s) found\n", num_accounts); } /* ================= SV_blockAccount blocks/unblocks an account ================= */ void SV_blockAccount(qbool block) { int i, j; for (i = 0; i < num_accounts; i++) { if (accounts[i].state == a_free) continue; if (!strcasecmp(accounts[i].login, Cmd_Argv(1))) { if (block) { accounts[i].state = a_blocked; Con_Printf("account %s blocked\n", Cmd_Argv(1)); for (j = 0; j < MAX_CLIENTS; ++j) { if (!strcasecmp(svs.clients[j].login, accounts[i].login)) { SV_DropClient(&svs.clients[j]); break; } } return; } if (accounts[i].state != a_blocked) { Con_Printf("account %s not blocked\n", Cmd_Argv(1)); } else { accounts[i].state = a_ok; accounts[i].failures = 0; Con_Printf("account %s unblocked\n", Cmd_Argv(1)); } return; } } Con_Printf("account %s not found\n", Cmd_Argv(1)); } void SV_UnblockAccount_f(void) { if (Cmd_Argc() < 2) { Con_Printf("usage: acc_unblock \n"); return; } SV_blockAccount(false); WriteAccounts(); } void SV_BlockAccount_f(void) { if (Cmd_Argc() < 2) { Con_Printf("usage: acc_block \n"); return; } SV_blockAccount(true); WriteAccounts(); } /* ================= checklogin returns positive value if login/pass are valid values <= 0 indicates a failure ================= */ static int checklogin(char* log1, char* pass, quse_t use) { int i, c; for (i = 0, c = 0; c < num_accounts; i++) { if (accounts[i].state == a_free) continue; if (use == accounts[i].use && /*use == use_log && accounts[i].use == use_log && */ !strcasecmp(log1, accounts[i].login)) { if (accounts[i].state == a_blocked) return -2; // Only do logins/failures if using file-based login list if (LoginMustHaveLocalAccount()) { if (accounts[i].inuse && accounts[i].use == use_log) { return -1; } if (use == use_ip || (!(int)sv_hashpasswords.value && !strcasecmp(pass, accounts[i].pass)) || ( (int)sv_hashpasswords.value && !strcasecmp(SHA1(pass), accounts[i].pass))) { accounts[i].failures = 0; accounts[i].inuse++; return i + 1; } if (++accounts[i].failures >= MAX_FAILURES) { Sys_Printf("account %s blocked after %d failed login attempts\n", accounts[i].login, accounts[i].failures); accounts[i].state = a_blocked; } WriteAccounts(); } else { return i + 1; } return 0; } c++; } return 0; } void Login_Init(void) { Cvar_Register(&sv_login); #ifdef WEBSITE_LOGIN_SUPPORT Cvar_Register(&sv_login_web); #endif Cmd_AddCommand("acc_create", SV_CreateAccount_f); Cmd_AddCommand("acc_remove", SV_RemoveAccount_f); Cmd_AddCommand("acc_list", SV_ListAccount_f); Cmd_AddCommand("acc_unblock", SV_UnblockAccount_f); Cmd_AddCommand("acc_block", SV_BlockAccount_f); // load account list //SV_LoadAccounts(); } /* =============== SV_Login called on connect after cmd new is issued =============== */ qbool SV_Login(client_t* cl) { extern cvar_t sv_registrationinfo; char* ip; // is sv_login is disabled, login is not necessery if (!(int)sv_login.value) { // If using local files then logout if (!cl->logged_in_via_web) { SV_Logout(cl); cl->logged = -1; } return true; } // if we're already logged return (probably map change) if (cl->logged > 0 || cl->logged_in_via_web) { return true; } // sv_login == 1 -> spectators don't login if ((int)sv_login.value == 1 && cl->spectator) { SV_Logout(cl); cl->logged = -1; return true; } // check for account for ip ip = va("%d.%d.%d.%d", cl->realip.ip[0], cl->realip.ip[1], cl->realip.ip[2], cl->realip.ip[3]); if ((cl->logged = checklogin(ip, ip, use_ip)) > 0) { strlcpy(cl->login, accounts[cl->logged - 1].pass, CLIENT_LOGIN_LEN); return true; } // need to login before connecting cl->logged = 0; cl->login[0] = 0; if (sv_registrationinfo.string[0]) SV_ClientPrintf2(cl, PRINT_HIGH, "%s\n", sv_registrationinfo.string); if (WebLoginsEnabled()) { char buffer[128]; strlcpy(buffer, "//authprompt\n", sizeof(buffer)); ClientReliableWrite_Begin(cl, svc_stufftext, 2 + strlen(buffer)); ClientReliableWrite_String(cl, buffer); SV_ClientPrintf2(cl, PRINT_HIGH, "Enter username:\n"); } else { SV_ClientPrintf2(cl, PRINT_HIGH, "Enter login & password:\n"); } return false; } void SV_Logout(client_t* cl) { if (cl->logged > 0 && cl->logged <= sizeof(accounts) / sizeof(accounts[0])) { accounts[cl->logged - 1].inuse--; } Info_SetStar(&cl->_userinfo_ctx_, "*auth", ""); Info_SetStar(&cl->_userinfo_ctx_, "*flag", ""); ProcessUserInfoChange(cl, "*auth", cl->login); ProcessUserInfoChange(cl, "*flag", cl->login_flag); memset(cl->login, 0, sizeof(cl->login)); memset(cl->login_alias, 0, sizeof(cl->login_alias)); memset(cl->login_flag, 0, sizeof(cl->login_flag)); memset(cl->login_challenge, 0, sizeof(cl->login_challenge)); memset(cl->login_confirmation, 0, sizeof(cl->login_confirmation)); cl->logged = 0; cl->logged_in_via_web = false; } #ifdef WEBSITE_LOGIN_SUPPORT void SV_ParseWebLogin(client_t* cl) { char parameter[128] = { 0 }; char* p; strlcpy(parameter, Cmd_Argv(1), sizeof(parameter)); for (p = parameter; *p > 32; ++p) { } *p = '\0'; if (!parameter[0]) { return; } if (cl->login_challenge[0]) { // This is response to challenge, treat as password Central_VerifyChallengeResponse(cl, cl->login_challenge, parameter); SV_ClientPrintf2(cl, PRINT_HIGH, "Challenge received, please wait...\n"); } else if (curtime - cl->login_request_time < LOGIN_MIN_RETRY_TIME) { SV_ClientPrintf2(cl, PRINT_HIGH, "Please wait and try again\n"); } else { // Treat as username Central_GenerateChallenge(cl, parameter, true); SV_ClientPrintf2(cl, PRINT_HIGH, "Generating challenge, please wait...\n"); } } #else void SV_ParseWebLogin(client_t* cl) { } #endif void SV_ParseLogin(client_t* cl) { char *log1, *pass; if (WebLoginsEnabled()) { SV_ParseWebLogin(cl); return; } if (Cmd_Argc() > 2) { log1 = Cmd_Argv(1); pass = Cmd_Argv(2); } else { // bah usually whole text in 'say' is put into "" log1 = pass = Cmd_Argv(1); while (*pass && *pass != ' ') pass++; if (*pass) *pass++ = 0; while (*pass == ' ') pass++; } // if login is parsed, we read just a password if (cl->login[0]) { pass = log1; log1 = cl->login; } else { strlcpy(cl->login, log1, CLIENT_LOGIN_LEN); } if (!*pass) { strlcpy(cl->login, log1, CLIENT_LOGIN_LEN); SV_ClientPrintf2(cl, PRINT_HIGH, "Enter password for %s:\n", cl->login); return; } cl->logged = checklogin(log1, pass, use_log); switch (cl->logged) { case -2: SV_BlockedLogin(cl); break; case -1: SV_ClientPrintf2(cl, PRINT_HIGH, "Login in use!\ntry again:\n"); cl->logged = 0; cl->login[0] = 0; break; case 0: SV_ClientPrintf2(cl, PRINT_HIGH, "Access denied\nPassword for %s:\n", cl->login); break; default: strlcpy(cl->login_alias, cl->login, sizeof(cl->login_alias)); SV_SuccessfulLogin(cl); break; } } static void SV_BlockedLogin(client_t* cl) { SV_ClientPrintf2(cl, PRINT_HIGH, "Login blocked\n"); SV_DropClient(cl); } static void SV_SuccessfulLogin(client_t* cl) { extern cvar_t sv_forcenick; if (!cl->spectator || !GameStarted()) { SV_BroadcastPrintf(PRINT_HIGH, "%s logged in as %s\n", cl->name, cl->login); } if (cl->state < cs_spawned) { SV_ClientPrintf2(cl, PRINT_HIGH, "Welcome %s\n", cl->login); } //VVD: forcenick -> if ((int)sv_forcenick.value) { const char* forced_name = cl->login_alias[0] ? cl->login_alias : cl->login; if (forced_name[0]) { SV_ForceClientName(cl, forced_name); } } //<- if (cl->state < cs_spawned) { MSG_WriteByte(&cl->netchan.message, svc_stufftext); MSG_WriteString(&cl->netchan.message, "cmd new\n"); } } static void SV_ForceClientName(client_t* cl, const char* forced_name) { char oldval[MAX_EXT_INFO_STRING]; int i; // If any other clients are using this name, kick them for (i = 0; i < MAX_CLIENTS; ++i) { client_t* other = &svs.clients[i]; if (!other->state) continue; if (other == cl) { continue; } if (!Q_namecmp(other->name, forced_name)) { SV_KickClient(other, " (using authenticated user's name)"); } } // Set server-side name: allow colors/case changes if (!Q_namecmp(cl->name, forced_name)) { return; } strlcpy(oldval, cl->name, MAX_EXT_INFO_STRING); Info_Set(&cl->_userinfo_ctx_, "name", forced_name); ProcessUserInfoChange(cl, "name", oldval); // Change name cvar in client MSG_WriteByte(&cl->netchan.message, svc_stufftext); MSG_WriteString(&cl->netchan.message, va("name %s\n", forced_name)); } void SV_LoginCheckTimeOut(client_t* cl) { double connected = SV_ClientConnectedTime(cl); if (connected && connected > 60) { Sys_Printf("Login time out for %s\n", cl->name); SV_ClientPrintf2(cl, PRINT_HIGH, "Login timeout expired\n"); SV_DropClient(cl); } } void SV_LoginWebCheck(client_t* cl) { int status = checklogin(cl->login, cl->login, use_log); if (status < 0) { // Server admin explicitly blocked this account SV_BlockedLogin(cl); } else if (status == 0 && LoginMustHaveLocalAccount()) { // Server admin needs to create accounts for people to use SV_BlockedLogin(cl); } else { // Continue logging in SV_SuccessfulLogin(cl); } } void SV_LoginWebFailed(client_t* cl) { memset(cl->login_challenge, 0, sizeof(cl->login_challenge)); cl->login_request_time = 0; SV_ClientPrintf2(cl, PRINT_HIGH, "Challenge response failed.\n"); if (cl->state < cs_spawned) { SV_BlockedLogin(cl); } } qbool SV_LoginRequired(client_t* cl) { int login = (int)sv_login.value; if (login == 2 || (login == 1 && !cl->spectator)) { if (WebLoginsEnabled()) { return !cl->logged_in_via_web; } else { return !cl->logged; } } return false; } qbool SV_LoginBlockJoinRequest(client_t* cl) { if (WebLoginsEnabled()) { if (!cl->logged_in_via_web && (int)sv_login.value) { SV_ClientPrintf(cl, PRINT_HIGH, "This server requires users to login. Please authenticate first (/cmd login ).\n"); return true; } } else if (cl->logged <= 0 && (int)sv_login.value) { SV_ClientPrintf(cl, PRINT_HIGH, "This server requires users to login. Please disconnect and reconnect as a player.\n"); return true; } // Allow return false; } #endif // !CLIENTONLY mvdsv-0.35/src/sv_main.c000066400000000000000000002741501427146041000151770ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef CLIENTONLY #include "qwsvdef.h" #ifdef SERVERONLY qbool host_initialized; qbool host_everything_loaded; // true if OnChange() applied to every var, end of Host_Init() double curtime; // not bounded or scaled, shared by local client and server. double realtime; // affected by pause, you should not use it unless it something like physics and such. static int host_hunklevel; #else qbool server_cfg_done = false; double realtime; // affected by pause, you should not use it unless it something like physics and such. #endif int current_skill; // for entity spawnflags checking client_t *sv_client; // current client char master_rcon_password[128] = ""; //bliP: password for remote server commands cvar_t sv_mintic = {"sv_mintic","0.013"}; // bound the size of the cvar_t sv_maxtic = {"sv_maxtic","0.1"}; // physics time tic cvar_t sv_maxfps = {"maxfps", "77", CVAR_SERVERINFO}; // It actually should be called maxpps (max packets per second). // It was serverinfo variable for quite long time, lets legolize it as cvar. // Sad part is what we can't call it like sv_maxfps since clients relay on its name 'maxfps' already. void OnChange_sysselecttimeout_var (cvar_t *var, char *value, qbool *cancel); cvar_t sys_select_timeout = {"sys_select_timeout", "10000", 0, OnChange_sysselecttimeout_var}; // microseconds. cvar_t sys_restart_on_error = {"sys_restart_on_error", "0"}; cvar_t sv_mod_extensions = { "sv_mod_extensions", "2", CVAR_ROM }; #ifdef SERVERONLY cvar_t sys_simulation = { "sys_simulation", "0" }; cvar_t developer = {"developer", "0"}; // show extra messages cvar_t version = {"version", "", CVAR_ROM}; #endif cvar_t timeout = {"timeout", "65"}; // seconds without any message cvar_t zombietime = {"zombietime", "2"}; // seconds to sink messages // after disconnect #ifdef SERVERONLY cvar_t rcon_password = {"rcon_password", ""}; // password for remote server commands cvar_t password = {"password", ""}; // password for entering the game #else // client already have such variables. extern cvar_t rcon_password; extern cvar_t password; #endif cvar_t sv_hashpasswords = {"sv_hashpasswords", "1"}; // 0 - plain passwords; 1 - hashed passwords cvar_t sv_crypt_rcon = {"sv_crypt_rcon", "1"}; // use SHA1 for encryption of rcon_password and using timestamps // Time in seconds during which in rcon command this encryption is valid (change only with master_rcon_password). cvar_t sv_timestamplen = {"sv_timestamplen", "60"}; cvar_t sv_rconlim = {"sv_rconlim", "10"}; // rcon bandwith limit: requests per second //bliP: telnet log level void OnChange_telnetloglevel_var (cvar_t *var, char *string, qbool *cancel); cvar_t telnet_log_level = {"telnet_log_level", "0", 0, OnChange_telnetloglevel_var}; //<- cvar_t frag_log_type = {"frag_log_type", "0"}; // frag log type: // 0 - old style ( qwsv - v0.165) // 1 - new style (v0.168 - v0.172) void OnChange_qconsolelogsay_var (cvar_t *var, char *string, qbool *cancel); cvar_t qconsole_log_say = {"qconsole_log_say", "0", 0, OnChange_qconsolelogsay_var}; // logging "say" and "say_team" messages to the qconsole_PORT.log file cvar_t sys_command_line = {"sys_command_line", "", CVAR_ROM}; cvar_t sv_use_dns = {"sv_use_dns", "0"}; // 1 - use DNS lookup in status command, 0 - don't use cvar_t spectator_password = {"spectator_password", ""}; // password for entering as a sepctator cvar_t vip_password = {"vip_password", ""}; // password for entering as a VIP sepctator cvar_t vip_values = {"vip_values", ""}; cvar_t allow_download = {"allow_download", "1"}; cvar_t allow_download_skins = {"allow_download_skins", "1"}; cvar_t allow_download_models = {"allow_download_models", "1"}; cvar_t allow_download_sounds = {"allow_download_sounds", "1"}; cvar_t allow_download_maps = {"allow_download_maps", "1"}; cvar_t allow_download_pakmaps = {"allow_download_pakmaps", "1"}; cvar_t allow_download_demos = {"allow_download_demos", "1"}; cvar_t allow_download_other = {"allow_download_other", "0"}; //bliP: init -> cvar_t download_map_url = {"download_map_url", ""}; cvar_t sv_specprint = {"sv_specprint", "0"}; cvar_t sv_reconnectlimit = {"sv_reconnectlimit", "0"}; void OnChange_admininfo_var (cvar_t *var, char *string, qbool *cancel); cvar_t sv_admininfo = {"sv_admininfo", "", 0, OnChange_admininfo_var}; cvar_t sv_unfake = {"sv_unfake", "1"}; //bliP: 24/9 kickfake to unfake cvar_t sv_kicktop = {"sv_kicktop", "1"}; cvar_t sv_allowlastscores = {"sv_allowlastscores", "1"}; cvar_t sv_maxlogsize = {"sv_maxlogsize", "0"}; //bliP: 24/9 -> void OnChange_logdir_var (cvar_t *var, char *string, qbool *cancel); cvar_t sv_logdir = {"sv_logdir", ".", 0, OnChange_logdir_var}; cvar_t sv_speedcheck = {"sv_speedcheck", "1"}; //<- //<- //cvar_t sv_highchars = {"sv_highchars", "1"}; cvar_t sv_phs = {"sv_phs", "1"}; cvar_t pausable = {"pausable", "0"}; cvar_t sv_maxrate = {"sv_maxrate", "0"}; cvar_t sv_getrealip = {"sv_getrealip", "1"}; cvar_t sv_serverip = {"sv_serverip", ""}; cvar_t sv_forcespec_onfull = {"sv_forcespec_onfull", "2"}; cvar_t sv_maxdownloadrate = {"sv_maxdownloadrate", "0"}; cvar_t sv_loadentfiles = {"sv_loadentfiles", "1"}; //loads .ent files by default if there cvar_t sv_loadentfiles_dir = {"sv_loadentfiles_dir", ""}; // check for .ent file in maps/sv_loadentfiles_dir first then just maps/ cvar_t sv_default_name = {"sv_default_name", "unnamed"}; void sv_mod_msg_file_OnChange(cvar_t *cvar, char *value, qbool *cancel); cvar_t sv_mod_msg_file = {"sv_mod_msg_file", "", CVAR_NONE, sv_mod_msg_file_OnChange}; cvar_t sv_reliable_sound = {"sv_reliable_sound", "0"}; // // game rules mirrored in svs.info // cvar_t fraglimit = {"fraglimit","0",CVAR_SERVERINFO}; cvar_t timelimit = {"timelimit","0",CVAR_SERVERINFO}; cvar_t teamplay = {"teamplay","0",CVAR_SERVERINFO}; cvar_t maxclients = {"maxclients","24",CVAR_SERVERINFO}; cvar_t maxspectators = {"maxspectators","8",CVAR_SERVERINFO}; cvar_t maxvip_spectators = {"maxvip_spectators","0"/*,CVAR_SERVERINFO*/}; cvar_t deathmatch = {"deathmatch","3",CVAR_SERVERINFO}; cvar_t watervis = {"watervis","0",CVAR_SERVERINFO}; cvar_t serverdemo = {"serverdemo","",CVAR_SERVERINFO | CVAR_ROM}; cvar_t samelevel = {"samelevel","1"}; // dont delete this variable - it used by mods cvar_t skill = {"skill", "1"}; // dont delete this variable - it used by mods cvar_t coop = {"coop", "0"}; // dont delete this variable - it used by mods cvar_t sv_paused = {"sv_paused", "0", CVAR_ROM}; cvar_t hostname = {"hostname", "unnamed", CVAR_SERVERINFO}; cvar_t sv_forcenick = {"sv_forcenick", "0"}; //0 - don't force; 1 - as login; cvar_t sv_registrationinfo = {"sv_registrationinfo", ""}; // text shown before "enter login" // We need this cvar, because some mods didn't allow us to go at some placeses of, for example, start map. cvar_t registered = {"registered", "1", CVAR_ROM}; cvar_t sv_halflifebsp = {"halflifebsp", "0", CVAR_ROM}; cvar_t sv_bspversion = {"sv_bspversion", "1", CVAR_ROM}; // If set, don't send broadcast messages, entities or player info to ServeMe bot cvar_t sv_serveme_fix = { "sv_serveme_fix", "1", CVAR_ROM }; #ifdef FTE_PEXT_FLOATCOORDS cvar_t sv_bigcoords = {"sv_bigcoords", "", CVAR_SERVERINFO}; #endif #ifdef MVD_PEXT1_SERVERSIDEWEAPON // Only enabled on KTX mod (see sv_init) cvar_t sv_pext_mvdsv_serversideweapon = { "sv_pext_mvdsv_serversideweapon", "1" }; #endif cvar_t sv_extlimits = { "sv_extlimits", "2" }; qbool sv_error = false; client_t *WatcherId = NULL; // QW262 //============================================================================ qbool GameStarted(void) { mvddest_t *d; for (d = demo.dest; d; d = d->nextdest) if (d->desttype != DEST_STREAM) // oh, its not stream, treat as "game is started" break; return (d || strncasecmp(Info_ValueForKey(svs.info, "status"), "Standby", 8)); } /* ================ SV_Shutdown Quake calls this before calling Sys_Quit or Sys_Error ================ */ void SV_Shutdown (char *finalmsg) { int i; if (!sv.state) return; // already shutdown. FIXME: what about error during SV_SpawnServer() ? SV_FinalMessage(finalmsg); Master_Shutdown (); for (i = MIN_LOG; i < MAX_LOG; ++i) { if (logs[i].sv_logfile) { fclose (logs[i].sv_logfile); logs[i].sv_logfile = NULL; } } if (sv.mvdrecording) SV_MVDStop_f(); #ifndef SERVER_ONLY NET_CloseServer (); #endif #if defined(SERVERONLY) && defined(WWW_INTEGRATION) Central_Shutdown(); #endif // Shutdown game. PR_GameShutDown(); PR_UnLoadProgs(); memset (&sv, 0, sizeof(sv)); sv.state = ss_dead; #ifndef SERVERONLY com_serveractive = false; { extern ctxinfo_t _localinfo_; Info_RemoveAll(&_localinfo_); for (i = 0; i < MAX_CLIENTS; ++i) { Info_RemoveAll(&svs.clients[i]._userinfo_ctx_); Info_RemoveAll(&svs.clients[i]._userinfoshort_ctx_); } } #endif memset (svs.clients, 0, sizeof(svs.clients)); svs.lastuserid = 0; svs.serverflags = 0; } /* ================ SV_Error Sends a datagram to all the clients informing them of the server crash, then exits ================ */ void SV_Error (char *error, ...) { static qbool inerror = false; static char string[1024]; va_list argptr; sv_error = true; if (inerror) Sys_Error ("SV_Error: recursively entered (%s)", string); inerror = true; va_start (argptr, error); vsnprintf (string, sizeof (string), error, argptr); va_end (argptr); SV_Shutdown (va ("SV_Error: %s\n", string)); Sys_Error ("SV_Error: %s", string); } static void SV_FreeHeadDelayedPacket(client_t *cl) { if (cl->packets) { packet_t *next = cl->packets->next; cl->packets->next = svs.free_packets; svs.free_packets = cl->packets; cl->packets = next; } } void SV_FreeDelayedPackets (client_t *cl) { while (cl->packets) SV_FreeHeadDelayedPacket(cl); } /* ================== SV_FinalMessage Used by SV_Error and SV_Quit_f to send a final message to all connected clients before the server goes down. The messages are sent immediately, not just stuck on the outgoing message list, because the server is going to totally exit after returning from this function. ================== */ void SV_FinalMessage (const char *message) { client_t *cl; int i; SZ_Clear (&net_message); MSG_WriteByte (&net_message, svc_print); MSG_WriteByte (&net_message, PRINT_HIGH); MSG_WriteString (&net_message, message); MSG_WriteByte (&net_message, svc_disconnect); for (i=0, cl = svs.clients ; istate >= cs_spawned #ifdef USE_PR2 && !cl->isBot #endif ) { Netchan_Transmit(&cl->netchan, net_message.cursize , net_message.data); } } /* ===================== SV_DropClient Called when the player is totally leaving the server, either willingly or unwillingly. This is NOT called if the entire server is quiting or crashing. ===================== */ void SV_DropClient(client_t* drop) { //bliP: cuff, mute -> SV_SavePenaltyFilter (drop, ft_mute, drop->lockedtill); SV_SavePenaltyFilter (drop, ft_cuff, drop->cuff_time); //<- //bliP: player logging if (drop->name[0]) SV_LogPlayer(drop, "disconnect", 1); //<- // add the disconnect #ifdef USE_PR2 if( drop->isBot ) { extern void RemoveBot(client_t *cl); RemoveBot(drop); return; } #endif MSG_WriteByte (&drop->netchan.message, svc_disconnect); if (drop->state == cs_spawned) { // call the prog function for removing a client // this will set the body to a dead frame, among other things pr_global_struct->self = EDICT_TO_PROG(drop->edict); PR_GameClientDisconnect(drop->spectator); } if (drop->spectator) Con_Printf ("Spectator %s removed\n",drop->name); else Con_Printf ("Client %s removed\n",drop->name); if (drop->download) { VFS_CLOSE(drop->download); drop->download = NULL; } if (drop->upload) { fclose (drop->upload); drop->upload = NULL; } *drop->uploadfn = 0; SV_Logout(drop); drop->state = cs_zombie; // become free in a few seconds SV_SetClientConnectionTime(drop); // for zombie timeout // MD --> if (drop == WatcherId) WatcherId = NULL; // <-- MD drop->old_frags = 0; drop->edict->v->frags = 0.0; drop->name[0] = 0; Info_RemoveAll(&drop->_userinfo_ctx_); Info_RemoveAll(&drop->_userinfoshort_ctx_); // send notification to all remaining clients SV_FullClientUpdate(drop, &sv.reliable_datagram); } //==================================================================== /* =================== SV_CalcPing =================== */ int SV_CalcPing (client_t *cl) { register client_frame_t *frame; int count, i; float ping; //bliP: 999 ping for connecting players if (cl->state != cs_spawned) return 999; //<- ping = 0; count = 0; #ifdef USE_PR2 if (cl->isBot) { return 10; } #endif for (frame = cl->frames, i=0 ; iping_time > 0) { ping += frame->ping_time; count++; } } if (!count) return 9999; ping /= count; return ping*1000; } /* =================== SV_FullClientUpdate Writes all update values to a sizebuf =================== */ void SV_FullClientUpdate (client_t *client, sizebuf_t *buf) { char info[MAX_EXT_INFO_STRING]; int i; i = client - svs.clients; //Sys_Printf("SV_FullClientUpdate: Updated frags for client %d\n", i); MSG_WriteByte (buf, svc_updatefrags); MSG_WriteByte (buf, i); MSG_WriteShort (buf, client->old_frags); MSG_WriteByte (buf, svc_updateping); MSG_WriteByte (buf, i); MSG_WriteShort (buf, SV_CalcPing (client)); MSG_WriteByte (buf, svc_updatepl); MSG_WriteByte (buf, i); MSG_WriteByte (buf, client->lossage); MSG_WriteByte (buf, svc_updateentertime); MSG_WriteByte (buf, i); MSG_WriteFloat (buf, SV_ClientGameTime(client)); Info_ReverseConvert(&client->_userinfoshort_ctx_, info, sizeof(info)); Info_RemovePrefixedKeys (info, '_'); // server passwords, etc MSG_WriteByte (buf, svc_updateuserinfo); MSG_WriteByte (buf, i); MSG_WriteLong (buf, client->userid); MSG_WriteString (buf, info); } /* =================== SV_FullClientUpdateToClient Writes all update values to a client's reliable stream =================== */ void SV_FullClientUpdateToClient (client_t *client, client_t *cl) { char info[MAX_EXT_INFO_STRING]; Info_ReverseConvert(&client->_userinfoshort_ctx_, info, sizeof(info)); ClientReliableCheckBlock(cl, 24 + strlen(info)); if (cl->num_backbuf) { SV_FullClientUpdate (client, &cl->backbuf); ClientReliable_FinishWrite(cl); } else SV_FullClientUpdate (client, &cl->netchan.message); } //Returns a unique userid in [1..MAXUSERID] range #define MAXUSERID 99 int SV_GenerateUserID (void) { client_t *cl; int i; do { svs.lastuserid++; if (svs.lastuserid == 1 + MAXUSERID) svs.lastuserid = 1; for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) if (cl->state != cs_free && cl->userid == svs.lastuserid) break; } while (i != MAX_CLIENTS); return svs.lastuserid; } /* ============================================================================== CONNECTIONLESS COMMANDS ============================================================================== */ /* SVC_QTVStreams Responds with info on connected QTV users */ static void SVC_QTVUsers (void) { SV_BeginRedirect (RD_PACKET); QTV_Streams_UserList (); SV_EndRedirect (); } /* ================ SVC_Status Responds with all the info that qplug or qspy can see This message can be up to around 5k with worst case string lengths. ================ */ #define STATUS_OLDSTYLE 0 #define STATUS_SERVERINFO 1 #define STATUS_PLAYERS 2 #define STATUS_SPECTATORS 4 #define STATUS_SPECTATORS_AS_PLAYERS 8 //for ASE - change only frags: show as "S" #define STATUS_SHOWTEAMS 16 #define STATUS_SHOWQTV 32 #define STATUS_SHOWFLAGS 64 static void SVC_Status (void) { int top, bottom, ping, i, opt = 0; char *name, *frags; client_t *cl; if (Cmd_Argc() > 1) opt = Q_atoi(Cmd_Argv(1)); SV_BeginRedirect (RD_PACKET); if (opt == STATUS_OLDSTYLE || (opt & STATUS_SERVERINFO)) Con_Printf ("%s\n", svs.info); if (opt == STATUS_OLDSTYLE || (opt & (STATUS_PLAYERS | STATUS_SPECTATORS))) for (i = 0; i < MAX_CLIENTS; i++) { cl = &svs.clients[i]; if ( (cl->state >= cs_preconnected/* || cl->state == cs_spawned */) && ( (!cl->spectator && ((opt & STATUS_PLAYERS) || opt == STATUS_OLDSTYLE)) || ( cl->spectator && ( opt & STATUS_SPECTATORS)) ) ) { top = Q_atoi(Info_Get (&cl->_userinfo_ctx_, "topcolor")); bottom = Q_atoi(Info_Get (&cl->_userinfo_ctx_, "bottomcolor")); top = (top < 0) ? 0 : ((top > 13) ? 13 : top); bottom = (bottom < 0) ? 0 : ((bottom > 13) ? 13 : bottom); ping = SV_CalcPing (cl); name = cl->name; if (cl->spectator) { if (opt & STATUS_SPECTATORS_AS_PLAYERS) frags = "S"; else { ping = -ping; frags = "-9999"; name = va("\\s\\%s", name); } } else frags = va("%i", cl->old_frags); Con_Printf ("%i %s %i %i \"%s\" \"%s\" %i %i", cl->userid, frags, (int)(SV_ClientConnectedTime(cl) / 60.0f), ping, name, Info_Get (&cl->_userinfo_ctx_, "skin"), top, bottom); if (opt & STATUS_SHOWTEAMS) { Con_Printf(" \"%s\"", cl->team); } if (opt & STATUS_SHOWFLAGS) { if (cl->login_flag[0]) { Con_Printf(" \"%s\"", cl->login_flag); } else if (cl->logged_in_via_web || cl->logged > 0) { Con_Printf(" \"none\""); } else { Con_Printf(" \"\""); } } Con_Printf("\n"); } } if (opt & STATUS_SHOWQTV) QTV_Streams_List (); SV_EndRedirect (); } /* =================== SVC_LastScores =================== */ void SV_LastScores_f (void); static void SVC_LastScores (void) { if(!(int)sv_allowlastscores.value) return; SV_BeginRedirect (RD_PACKET); SV_LastScores_f (); SV_EndRedirect (); } /* =================== SVC_DemoList SVC_DemoListRegex =================== */ void SV_DemoList_f (void); static void SVC_DemoList (void) { SV_BeginRedirect (RD_PACKET); SV_DemoList_f (); SV_EndRedirect (); } void SV_DemoListRegex_f (void); static void SVC_DemoListRegex (void) { SV_BeginRedirect (RD_PACKET); SV_DemoListRegex_f (); SV_EndRedirect (); } /* =================== SV_CheckLog =================== */ #define LOG_HIGHWATER (MAX_DATAGRAM - 128) #define LOG_FLUSH 10*60 static void SV_CheckLog (void) { sizebuf_t *sz; if (sv.state != ss_active) return; sz = &svs.log[svs.logsequence&1]; // bump sequence if allmost full, or ten minutes have passed and // there is something still sitting there if (sz->cursize > LOG_HIGHWATER || (realtime - svs.logtime > LOG_FLUSH && sz->cursize) ) { // swap buffers and bump sequence svs.logtime = realtime; svs.logsequence++; sz = &svs.log[svs.logsequence&1]; sz->cursize = 0; Con_DPrintf ("beginning fraglog sequence %i\n", svs.logsequence); } } /* ================ SVC_Log Responds with all the logged frags for ranking programs. If a sequence number is passed as a parameter and it is the same as the current sequence, an A2A_NACK will be returned instead of the data. ================ */ static void SVC_Log (void) { char data[MAX_DATAGRAM+64]; int seq; if (Cmd_Argc() == 2) seq = Q_atoi(Cmd_Argv(1)); else seq = -1; if (seq == svs.logsequence-1 || !logs[FRAG_LOG].sv_logfile) { // they already have this data, or we aren't logging frags data[0] = A2A_NACK; NET_SendPacket (NS_SERVER, 1, data, net_from); return; } Con_DPrintf ("sending log %i to %s\n", svs.logsequence-1, NET_AdrToString(net_from)); snprintf (data, MAX_DATAGRAM + 64, "stdlog %i\n", svs.logsequence-1); strlcat (data, (char *)svs.log_buf[((svs.logsequence-1)&1)], MAX_DATAGRAM + 64); NET_SendPacket (NS_SERVER, strlen(data)+1, data, net_from); } /* ================ SVC_Ping Just responds with an acknowledgement ================ */ static void SVC_Ping (void) { char data = A2A_ACK; NET_SendPacket (NS_SERVER, 1, &data, net_from); } /* ================= SVC_GetChallenge Returns a challenge number that can be used in a subsequent client_connect command. We do this to prevent denial of service attacks that flood the server with invalid connection IPs. With a challenge, they must give a valid IP address. ================= */ static void SVC_GetChallenge (void) { int oldestTime, oldest, i; char buf[256], *over; oldest = 0; oldestTime = 0x7fffffff; // see if we already have a challenge for this ip for (i = 0 ; i < MAX_CHALLENGES ; i++) { if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr)) break; if (svs.challenges[i].time < oldestTime) { oldestTime = svs.challenges[i].time; oldest = i; } } if (i == MAX_CHALLENGES) { // overwrite the oldest svs.challenges[oldest].challenge = (rand() << 16) ^ rand(); svs.challenges[oldest].adr = net_from; svs.challenges[oldest].time = realtime; i = oldest; } // send it back snprintf(buf, sizeof(buf), "%c%i", S2C_CHALLENGE, svs.challenges[i].challenge); over = buf + strlen(buf) + 1; #ifdef PROTOCOL_VERSION_FTE //tell the client what fte extensions we support if (svs.fteprotocolextensions) { int lng; lng = LittleLong(PROTOCOL_VERSION_FTE); memcpy(over, &lng, sizeof(int)); over += 4; lng = LittleLong(svs.fteprotocolextensions); memcpy(over, &lng, sizeof(int)); over += 4; } #endif // PROTOCOL_VERSION_FTE #ifdef PROTOCOL_VERSION_FTE2 //tell the client what fte extensions2 we support if (svs.fteprotocolextensions2) { int lng; lng = LittleLong(PROTOCOL_VERSION_FTE2); memcpy(over, &lng, sizeof(int)); over += 4; lng = LittleLong(svs.fteprotocolextensions2); memcpy(over, &lng, sizeof(int)); over += 4; } #endif // PROTOCOL_VERSION_FTE2 #ifdef PROTOCOL_VERSION_MVD1 // tell the client what mvdsv extensions we support if (svs.mvdprotocolextension1) { int lng; lng = LittleLong(PROTOCOL_VERSION_MVD1); memcpy(over, &lng, sizeof(int)); over += 4; lng = LittleLong(svs.mvdprotocolextension1); memcpy(over, &lng, sizeof(int)); over += 4; } #endif Netchan_OutOfBand(NS_SERVER, net_from, over-buf, (byte*) buf); } static qbool ValidateUserInfo (char *userinfo) { if (strstr(userinfo, "&c") || strstr(userinfo, "&r")) return false; while (*userinfo) { if (*userinfo == '\\') userinfo++; if (*userinfo++ == '\\') return false; while (*userinfo && *userinfo != '\\') userinfo++; } return true; } //============================================== void FixMaxClientsCvars(void) { if ((int)maxclients.value > MAX_CLIENTS) Cvar_SetValue (&maxclients, MAX_CLIENTS); if ((int)maxspectators.value > MAX_CLIENTS) Cvar_SetValue (&maxspectators, MAX_CLIENTS); if ((int)maxvip_spectators.value > MAX_CLIENTS) Cvar_SetValue (&maxvip_spectators, MAX_CLIENTS); if ((int)maxspectators.value + maxclients.value > MAX_CLIENTS) Cvar_SetValue (&maxspectators, MAX_CLIENTS - (int)maxclients.value); if ((int)maxspectators.value + maxclients.value + maxvip_spectators.value > MAX_CLIENTS) Cvar_SetValue (&maxvip_spectators, MAX_CLIENTS - (int)maxclients.value - (int)maxspectators.value); } //============================================== // see if the challenge is valid qbool CheckChallange( int challenge ) { int i; if (net_from.type == NA_LOOPBACK) return true; // local client do not need challenge for (i = 0; i < MAX_CHALLENGES; i++) { if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr)) { if (challenge == svs.challenges[i].challenge) break; // good Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c\nBad challenge.\n", A2C_PRINT); return false; } } if (i == MAX_CHALLENGES) { Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c\nNo challenge for address.\n", A2C_PRINT); return false; } return true; } //============================================== qbool CheckProtocol( int ver ) { if (ver != PROTOCOL_VERSION) { Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c\nServer is version " QW_VERSION ".\n", A2C_PRINT); Con_Printf ("* rejected connect from version %i\n", ver); return false; } return true; } //============================================== qbool CheckUserinfo( char *userinfobuf, unsigned int bufsize, char *userinfo ) { strlcpy (userinfobuf, userinfo, bufsize); // and now validate userinfo if ( !ValidateUserInfo( userinfobuf ) ) { Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c\nInvalid userinfo, perhaps &c sequences. Restart your qwcl\n", A2C_PRINT); return false; } return true; } //============================================== int SV_VIPbyIP(netadr_t adr); int SV_VIPbyPass (char *pass); qbool CheckPasswords( char *userinfo, int userinfo_size, qbool *spass_ptr, qbool *vip_ptr, int *spectator_ptr ) { int spectator; qbool spass, vip; char *s = Info_ValueForKey (userinfo, "spectator"); char *pwd; spass = vip = spectator = false; if (s[0] && strcmp(s, "0")) { spass = true; // first the pass, then ip if ( !( vip = SV_VIPbyPass( s ) ) ) { if ( !( vip = SV_VIPbyPass( Info_ValueForKey( userinfo, "password") ) ) ) { vip = SV_VIPbyIP( net_from ); } } pwd = spectator_password.string; if (pwd[0] && strcasecmp(pwd, "none") && strcmp(pwd, s)) { spass = false; // failed } if (!vip && !spass) { Con_Printf ("%s:spectator password failed\n", NET_AdrToString (net_from)); Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c\nrequires a spectator password\n\n", A2C_PRINT); return false; } Info_RemoveKey (userinfo, "spectator"); // remove passwd Info_SetValueForStarKey (userinfo, "*spectator", "1", userinfo_size); spectator = Q_atoi(s); if (!spectator) spectator = true; } else { s = Info_ValueForKey (userinfo, "password"); // first the pass, then ip if (!(vip = SV_VIPbyPass(s))) { vip = SV_VIPbyIP(net_from); } pwd = password.string; if (!vip && pwd[0] && strcasecmp(pwd, "none") && strcmp(pwd, s)) { Con_Printf ("%s:password failed\n", NET_AdrToString (net_from)); Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c\nserver requires a password\n\n", A2C_PRINT); return false; } Info_RemoveKey (userinfo, "spectator"); // remove "spectator 0" for example spectator = false; } Info_RemoveKey (userinfo, "password"); // remove passwd // copy *spass_ptr = spass; *vip_ptr = vip; *spectator_ptr = spectator; return true; } //============================================== qbool CheckReConnect( netadr_t adr, int qport ) { int i; client_t *cl; for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (cl->state == cs_free) continue; if (NET_CompareBaseAdr (adr, cl->netchan.remote_address) && (cl->netchan.qport == qport || adr.port == cl->netchan.remote_address.port)) { if (SV_ClientConnectedTime(cl) < sv_reconnectlimit.value) { Con_Printf ("%s:reconnect rejected: too soon\n", NET_AdrToString (adr)); return false; } switch ( cl->state ) { case cs_zombie: // zombie already dropped. break; case cs_preconnected: case cs_connected: case cs_spawned: SV_DropClient (cl); SV_ClearReliable (cl); // don't send the disconnect break; default: return false; // unknown state, should not be the case. } cl->state = cs_free; Con_Printf ("%s:reconnect\n", NET_AdrToString (adr)); break; } } return true; } //============================================== void CountPlayersSpecsVips(int *clients_ptr, int *spectators_ptr, int *vips_ptr, client_t **newcl_ptr) { client_t *cl = NULL, *newcl = NULL; int clients = 0, spectators = 0, vips = 0; int i; for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (cl->state == cs_free) { if (!newcl) newcl = cl; // grab first available slot continue; } if (cl->spectator) { if (cl->vip) vips++; else spectators++; } else { clients++; } } if (clients_ptr) *clients_ptr = clients; if (spectators_ptr) *spectators_ptr = spectators; if (vips_ptr) *vips_ptr = vips; if (newcl_ptr) *newcl_ptr = newcl; } //============================================== qbool SpectatorCanConnect(int vip, int spass, int spectators, int vips) { FixMaxClientsCvars(); // not a bad idea if (vip) { if (spass && (spectators < (int)maxspectators.value || vips < (int)maxvip_spectators.value)) return true; } else { if (spass && spectators < (int)maxspectators.value) return true; } return false; } qbool PlayerCanConnect(int clients) { FixMaxClientsCvars(); // not a bad idea if (clients < (int)maxclients.value) return true; return false; } /* ================== SVC_DirectConnect A connection request that did not come from the master ================== */ extern void MVD_PlayerReset(int player); extern char *shortinfotbl[]; static void SVC_DirectConnect (void) { int spectator; qbool spass, vip, rip_vip; int clients, spectators, vips; int qport, i, edictnum; client_t *newcl; char userinfo[1024]; char *s; netadr_t adr; edict_t *ent; #ifdef PROTOCOL_VERSION_FTE unsigned int protextsupported = 0; #endif // PROTOCOL_VERSION_FTE #ifdef PROTOCOL_VERSION_FTE2 unsigned int protextsupported2 = 0; #endif // PROTOCOL_VERSION_FTE2 #ifdef PROTOCOL_VERSION_MVD1 unsigned int mvdext_supported1 = 0; #endif // check version/protocol if ( !CheckProtocol( Q_atoi( Cmd_Argv( 1 ) ) ) ) return; // wrong protocol number // get qport qport = Q_atoi( Cmd_Argv( 2 ) ); // see if the challenge is valid if ( !CheckChallange( Q_atoi( Cmd_Argv( 3 ) ) ) ) return; // wrong challange // and now validate userinfo if ( !CheckUserinfo( userinfo, sizeof( userinfo ), Cmd_Argv( 4 ) ) ) return; // wrong userinfo // // WARNING: WARNING: WARNING: using Cmd_TokenizeString() so do all Cmd_Argv() above. // while( !msg_badread ) { Cmd_TokenizeString( MSG_ReadStringLine() ); switch( Q_atoi( Cmd_Argv( 0 ) ) ) { #ifdef PROTOCOL_VERSION_FTE case PROTOCOL_VERSION_FTE: protextsupported = Q_atoi( Cmd_Argv( 1 ) ); Con_DPrintf("Client supports 0x%x fte extensions\n", protextsupported); break; #endif // PROTOCOL_VERSION_FTE #ifdef PROTOCOL_VERSION_FTE2 case PROTOCOL_VERSION_FTE2: protextsupported2 = Q_atoi( Cmd_Argv( 1 ) ); Con_DPrintf("Client supports 0x%x fte extensions2\n", protextsupported2); break; #endif // PROTOCOL_VERSION_FTE2 #ifdef PROTOCOL_VERSION_MVD1 case PROTOCOL_VERSION_MVD1: mvdext_supported1 = Q_atoi( Cmd_Argv( 1 ) ); Con_DPrintf("Client supports 0x%x mvdsv extensions\n", mvdext_supported1); break; #endif } } msg_badread = false; spass = vip = rip_vip = spectator = false; // check for password or spectator_password if ( !CheckPasswords( userinfo, sizeof(userinfo), &spass, &vip, &spectator) ) return; // pass was wrong adr = net_from; // if there is already a slot for this ip, reuse (changed from drop) it if ( !CheckReConnect( adr, qport ) ) return; // can't do that for some reason // count up the clients and spectators CountPlayersSpecsVips(&clients, &spectators, &vips, &newcl); FixMaxClientsCvars(); // if at server limits, refuse connection if ((spectator && !SpectatorCanConnect(vip, spass, spectators, vips)) || (!spectator && !PlayerCanConnect(clients)) || !newcl) { Sys_Printf ("%s:full connect\n", NET_AdrToString (adr)); // no way to connect does't matter VIP or whatever, just no free slots if (!newcl) { Netchan_OutOfBandPrint (NS_SERVER, adr, "%c\nserver is full\n\n", A2C_PRINT); return; } // !!! SPECTATOR 2 FEATURE !!! if (spectator == 2 && !vip && vips < (int)maxvip_spectators.value) { vip = rip_vip = 1; // yet can be connected if realip is on vip list } else if ( !spectator && spectators < (int)maxspectators.value && ( ( (int)sv_forcespec_onfull.value == 2 && (Q_atoi(Info_ValueForKey(userinfo, "svf")) & SVF_SPEC_ONFULL) ) || ( (int)sv_forcespec_onfull.value == 1 && !(Q_atoi(Info_ValueForKey(userinfo, "svf")) & SVF_NO_SPEC_ONFULL) ) ) ) { Netchan_OutOfBandPrint (NS_SERVER, adr, "%c\nserver is full: connecting as spectator\n", A2C_PRINT); Info_SetValueForStarKey (userinfo, "*spectator", "1", sizeof(userinfo)); spectator = true; } else { Netchan_OutOfBandPrint (NS_SERVER, adr, "%c\nserver is full\n\n", A2C_PRINT); return; } } // build a new connection // accept the new client // this is the only place a client_t is ever initialized memset (newcl, 0, sizeof(*newcl)); newcl->userid = SV_GenerateUserID(); #ifdef PROTOCOL_VERSION_FTE newcl->fteprotocolextensions = protextsupported; #endif // PROTOCOL_VERSION_FTE #ifdef PROTOCOL_VERSION_FTE2 newcl->fteprotocolextensions2 = protextsupported2; #endif // PROTOCOL_VERSION_FTE2 #ifdef PROTOCOL_VERSION_MVD1 newcl->mvdprotocolextensions1 = mvdext_supported1; #endif newcl->_userinfo_ctx_.max = MAX_CLIENT_INFOS; newcl->_userinfoshort_ctx_.max = MAX_CLIENT_INFOS; Info_Convert(&newcl->_userinfo_ctx_, userinfo); // request protocol extensions. if (*Info_Get(&newcl->_userinfo_ctx_, "Qizmo") || *Info_Get(&newcl->_userinfo_ctx_, "*qtv") ) { newcl->process_pext = false; // this whould not work over such proxies. } else { newcl->process_pext = true; } Netchan_OutOfBandPrint (NS_SERVER, adr, "%c", S2C_CONNECTION); Netchan_Setup (NS_SERVER, &newcl->netchan, adr, qport, Q_atoi(Info_Get(&newcl->_userinfo_ctx_, "mtu"))); newcl->state = cs_preconnected; newcl->datagram.allowoverflow = true; newcl->datagram.data = newcl->datagram_buf; newcl->datagram.maxsize = sizeof(newcl->datagram_buf); // spectator mode can ONLY be set at join time newcl->spectator = spectator; newcl->vip = vip; newcl->rip_vip = rip_vip; // extract extensions mask newcl->extensions = Q_atoi(Info_Get(&newcl->_userinfo_ctx_, "*z_ext")); Info_Remove (&newcl->_userinfo_ctx_, "*z_ext"); edictnum = (newcl-svs.clients)+1; ent = EDICT_NUM(edictnum); ent->e.free = false; newcl->edict = ent; // restore client name. PR_SetEntityString(ent, ent->v->netname, newcl->name); s = ( vip ? va("%d", vip) : "" ); Info_SetStar (&newcl->_userinfo_ctx_, "*VIP", s); // copy the most important userinfo into userinfoshort // { // parse some info from the info strings SV_ExtractFromUserinfo (newcl, true); for (i = 0; shortinfotbl[i] != NULL; i++) { s = Info_Get(&newcl->_userinfo_ctx_, shortinfotbl[i]); Info_SetStar (&newcl->_userinfoshort_ctx_, shortinfotbl[i], s); } // move star keys to infoshort Info_CopyStar( &newcl->_userinfo_ctx_, &newcl->_userinfoshort_ctx_ ); // } // JACK: Init the floodprot stuff. memset(newcl->whensaid, 0, sizeof(newcl->whensaid)); newcl->whensaidhead = 0; newcl->lockedtill = 0; newcl->disable_updates_stop = -1.0; // Vladis newcl->realip_num = rand(); //bliP: init newcl->spec_print = (int)sv_specprint.value; newcl->logincount = 0; //<- #ifdef FTE_PEXT2_VOICECHAT SV_VoiceInitClient(newcl); #endif // call the progs to get default spawn parms for the new client PR_GameSetNewParms(); for (i=0 ; ispawn_parms[i] = (&PR_GLOBAL(parm1))[i]; // mvd/qtv related stuff // Well, here is a chance what player connect after demo recording started, // so demo.info[edictnum - 1].model == player_model so SV_MVDWritePackets() will not wrote player model index, // so client during playback this demo will got invisible model, because model index will be 0. // Fixing that. // Btw, struct demo contain different client specific structs, may be they need clearing too, not sure. // Also, we have Cmd_Join_f()/Cmd_Observe_f() which have close behaviour to SVC_DirectConnect(), // so I put same demo fix in mentioned functions too. MVD_PlayerReset(NUM_FOR_EDICT(newcl->edict) - 1); newcl->sendinfo = true; } static int char2int (int c) { if (c <= '9' && c >= '0') return c - '0'; else if (c <= 'f' && c >= 'a') return c - 'a' + 10; else if (c <= 'F' && c >= 'A') return c - 'A' + 10; return 0; } /* * rcon_bandlim() - check for rcon requests bandwidth limit * * From kernel of the FreeBSD 4.10 release: * sys/netinet/ip_icmp.c(846): int badport_bandlim(int which); * * Return false if it is ok to check rcon_password, true if we have * hit our bandwidth limit and it is not ok. * * If sv_rconlim.value is <= 0, the feature is disabled and false is returned. * * Note that the printing of the error message is delayed so we can * properly print the rcon limit error rate that the system was trying to do * (i.e. 22000/100 rcon pps, etc...). This can cause long delays in printing * the 'final' error, but it doesn't make sense to solve the printing * delay with more complex code. */ static qbool rcon_bandlim (void) { static double lticks = 0; static int lpackets = 0; /* * Return ok status if feature disabled or argument out of * ranage. */ if ((int)sv_rconlim.value <= 0) return false; /* * reset stats when cumulative dt exceeds one second. */ if (realtime - lticks > 1.0) { if (lpackets > (int)sv_rconlim.value) Sys_Printf("WARNING: Limiting rcon response from %d to %d rcon pequests per second from %s\n", lpackets, (int)sv_rconlim.value, NET_AdrToString(net_from)); lticks = realtime; lpackets = 0; } /* * bump packet count */ if (++lpackets > (int)sv_rconlim.value) return true; return false; } //bliP: master rcon/logging -> int Rcon_Validate (char *client_string, char *password1) { unsigned int i; if (rcon_bandlim()) { return 0; } if (!strlen(password1)) { return 0; } if ((int)sv_crypt_rcon.value) { const char* digest = Cmd_Argv(1); const char* time_start = Cmd_Argv(1) + DIGEST_SIZE * 2; if (strlen(digest) < DIGEST_SIZE * 2 + sizeof(time_t) * 2) { return 0; } if ((int)sv_timestamplen.value) { time_t server_time, client_time = 0; double difftime_server_client; time(&server_time); for (i = 0; i < sizeof(client_time) * 2; i += 2) { client_time += (char2int((unsigned char)time_start[i]) << (4 + i * 4)) + (char2int((unsigned char)time_start[i + 1]) << (i * 4)); } difftime_server_client = difftime(server_time, client_time); if (difftime_server_client > (double)sv_timestamplen.value || difftime_server_client < -(double)sv_timestamplen.value) { return 0; } } SHA1_Init(); SHA1_Update((unsigned char*)Cmd_Argv(0)); SHA1_Update((unsigned char*)" "); SHA1_Update((unsigned char*)password1); SHA1_Update((unsigned char*)time_start); SHA1_Update((unsigned char*)" "); for (i = 2; (int) i < Cmd_Argc(); i++) { SHA1_Update((unsigned char*)Cmd_Argv(i)); SHA1_Update((unsigned char*)" "); } if (strncmp(digest, SHA1_Final(), DIGEST_SIZE * 2)) { return 0; } } else if (strcmp(Cmd_Argv(1), password1)) { return 0; } return 1; } int Master_Rcon_Validate (void) { int i, client_string_len = Cmd_Argc() + 1; char *client_string; for (i = 0; i < Cmd_Argc(); ++i) { client_string_len += strlen(Cmd_Argv(i)); } client_string = (char *) Q_malloc (client_string_len); *client_string = 0; for (i = 0; i < Cmd_Argc(); ++i) { strlcat(client_string, Cmd_Argv(i), client_string_len); strlcat(client_string, " ", client_string_len); } i = Rcon_Validate (client_string, master_rcon_password); Q_free(client_string); return i; } // QW262 --> void SV_Admin_f (void) { client_t *cl; int i = 0; if (Cmd_Argc () == 2 && !strcmp (Cmd_Argv (1), "off") && WatcherId && NET_CompareAdr (WatcherId->netchan.remote_address, net_from)) { Con_Printf ("Rcon Watch stopped\n"); WatcherId = NULL; return; } if (WatcherId) Con_Printf ("Rcon Watch is already being made by %s\n", WatcherId->name); else { for (cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (cl->state != cs_spawned) continue; if (NET_CompareAdr (cl->netchan.remote_address, net_from)) break; } if (i == MAX_CLIENTS) { Con_Printf ("You are not connected to server!\n"); return; } WatcherId = cl; Con_Printf ("Rcon Watch started for %s\n", cl->name); } } // <-- QW262 /* =============== SVC_RemoteCommand A client issued an rcon command. Shift down the remaining args Redirect all printfs =============== */ static void SVC_RemoteCommand (char *remote_command) { int i; char str[1024]; char plain[32]; char *p; unsigned char *hide; client_t *cl; qbool admin_cmd = false; qbool do_cmd = false; qbool bad_cmd = false; qbool banned = false; if (Rcon_Validate (remote_command, master_rcon_password)) { if (SV_FilterPacket()) //banned players can't use rcon, but we log it banned = true; else do_cmd = true; } else if (Rcon_Validate (remote_command, rcon_password.string)) { admin_cmd = true; if (SV_FilterPacket()) //banned players can't use rcon, but we log it { bad_cmd = true; banned = true; } else { // // the following line prevents exploits like: // coop rm // $coop . * // which expands to: // rm . * Cmd_ExpandString (remote_command, str); // check *expanded* command // // since the execution parser is not case sensitive, we // must check not only for chmod, but also CHMOD, ChmoD, etc. // so we lowercase the whole temporary line before checking // VVD: strcmp => strcasecmp and we don't need to do this (yes?) //for(i = 0; str[i]; i++) // str[i] = (char)tolower(str[i]); Cmd_TokenizeString (str); // must check *all* tokens, because // a command/var may not be the first // token -- example: "" ls . // // normal rcon can't use these commands // // NOTE: this would still be vulnerable to semicolons if // they were still allowed, so keep that in mind before // re-enabling them for (i = 2; i < Cmd_Argc(); i++) { const char *tstr = Cmd_Argv(i); if(!tstr[0]) // skip leading empty tokens continue; if (!strcasecmp(tstr, "rm") || !strcasecmp(tstr, "rmdir") || !strcasecmp(tstr, "ls") || !strcasecmp(tstr, "chmod") || !strcasecmp(tstr, "sv_admininfo") || !strcasecmp(tstr, "if") || !strcasecmp(tstr, "localcommand") || !strcasecmp(tstr, "sv_crypt_rcon") || !strcasecmp(tstr, "sv_timestamplen") || !strncasecmp(tstr, "log", 3) || !strcasecmp(tstr, "sys_command_line") ) { bad_cmd = true; } break; // stop after first non-empty token } Cmd_TokenizeString (remote_command); // restore original tokens } do_cmd = !bad_cmd; } //find player name if rcon came from someone on server plain[0] = '\0'; for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (cl->state == cs_free) continue; #ifdef USE_PR2 if (cl->isBot) continue; #endif if (!NET_CompareBaseAdr(net_from, cl->netchan.remote_address)) continue; if (cl->netchan.remote_address.port != net_from.port) continue; strlcpy(plain, cl->name, sizeof(plain)); Q_normalizetext(plain); // we found what we need break; } if (do_cmd) { if (!(int)sv_crypt_rcon.value) { hide = net_message.data + 9; p = admin_cmd ? rcon_password.string : master_rcon_password; while (*p) { p++; *hide++ = '*'; } } if (plain[0]) SV_Write_Log(RCON_LOG, 1, va("Rcon from %s (%s): %s\n", NET_AdrToString(net_from), plain, net_message.data + 4)); else SV_Write_Log(RCON_LOG, 1, va("Rcon from %s: %s\n", NET_AdrToString(net_from), net_message.data + 4)); Con_Printf("Rcon from %s:\n%s\n", NET_AdrToString(net_from), net_message.data + 4); SV_BeginRedirect(RD_PACKET); str[0] = '\0'; for (i = 2; i < Cmd_Argc(); i++) { strlcat(str, Cmd_Argv(i), sizeof(str)); strlcat(str, " ", sizeof(str)); } Cmd_ExecuteString(str); } else { if (admin_cmd && !(int)sv_crypt_rcon.value) { hide = net_message.data + 9; p = admin_cmd ? rcon_password.string : master_rcon_password; while (*p) { p++; *hide++ = '*'; } } Con_Printf ("Bad rcon from %s: %s\n", NET_AdrToString(net_from), net_message.data + 4); if (!banned) { if (plain[0]) SV_Write_Log(RCON_LOG, 1, va("Bad rcon from %s (%s):\n%s\n", NET_AdrToString(net_from), plain, net_message.data + 4)); else SV_Write_Log(RCON_LOG, 1, va("Bad rcon from %s:\n%s\n", NET_AdrToString (net_from), net_message.data + 4)); } else { SV_Write_Log(RCON_LOG, 1, va("Rcon from banned IP: %s: %s\n", NET_AdrToString(net_from), net_message.data + 4)); SV_SendBan(); return; } SV_BeginRedirect (RD_PACKET); if (admin_cmd) Con_Printf ("Command not valid.\n"); else Con_Printf ("Bad rcon_password.\n"); } SV_EndRedirect (); } //<- static void SVC_IP(void) { int num; client_t *client; if (Cmd_Argc() < 3) return; num = Q_atoi(Cmd_Argv(1)); if (num < 0 || num >= MAX_CLIENTS) return; client = &svs.clients[num]; if (client->state != cs_preconnected) return; // prevent cheating if (client->realip_num != Q_atoi(Cmd_Argv(2))) return; // don't override previously set ip if (client->realip.ip[0]) return; client->realip = net_from; // if banned drop if (SV_FilterPacket()/* && !client->vip*/) SV_DropClient(client); } /* ================= SV_ConnectionlessPacket A connectionless packet has four leading 0xff characters to distinguish it from a game channel. Clients that are in the game can still send connectionless packets. ================= */ static void SV_ConnectionlessPacket (void) { char *s; char *c; MSG_BeginReading (); MSG_ReadLong (); // skip the -1 marker s = MSG_ReadStringLine (); Cmd_TokenizeString (s); c = Cmd_Argv(0); if (!strcmp(c, "ping") || ( c[0] == A2A_PING && (c[1] == 0 || c[1] == '\n')) ) SVC_Ping (); else if (c[0] == A2A_ACK && (c[1] == 0 || c[1] == '\n') ) Con_Printf ("A2A_ACK from %s\n", NET_AdrToString (net_from)); else if (!strcmp(c,"status")) SVC_Status (); else if (!strcmp(c,"log")) SVC_Log (); else if (!strcmp(c, "rcon")) SVC_RemoteCommand (s); else if (!strcmp(c, "ip")) SVC_IP(); else if (!strcmp(c,"connect")) SVC_DirectConnect (); else if (!strcmp(c,"getchallenge")) SVC_GetChallenge (); else if (!strcmp(c,"lastscores")) SVC_LastScores (); else if (!strcmp(c,"dlist")) SVC_DemoList (); else if (!strcmp(c,"dlistr")) SVC_DemoListRegex (); else if (!strcmp(c,"dlistregex")) SVC_DemoListRegex (); else if (!strcmp(c,"demolist")) SVC_DemoList (); else if (!strcmp(c,"demolistr")) SVC_DemoListRegex (); else if (!strcmp(c,"demolistregex")) SVC_DemoListRegex (); else if (!strcmp(c,"qtvusers")) SVC_QTVUsers (); else Con_Printf ("bad connectionless packet from %s:\n%s\n" , NET_AdrToString (net_from), s); } /* ============================================================================== PACKET FILTERING You can add or remove addresses from the filter list with: addip removeip The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire class C network with "addip 192.246.40". Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host. listip Prints the current list of filters. writeip Dumps "addip " commands to listip.cfg so it can be execed at a later date. The filter lists are not saved and restored by default, because I beleive it would cause too much confusion. filterban <0 or 1> If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting. If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network. ============================================================================== */ /*typedef struct { unsigned mask; unsigned compare; int level; } ipfilter_t; */ #define MAX_IPFILTERS 1024 ipfilter_t ipfilters[MAX_IPFILTERS]; int numipfilters; ipfilter_t ipvip[MAX_IPFILTERS]; int numipvips; //bliP: cuff, mute -> penfilter_t penfilters[MAX_PENFILTERS]; int numpenfilters; //<- cvar_t filterban = {"filterban", "1"}; /* ================= StringToFilter ================= */ qbool StringToFilter (char *s, ipfilter_t *f) { char num[128]; int i, j; byte b[4]; byte m[4]; for (i=0 ; i<4 ; i++) { b[i] = 0; m[i] = 0; } for (i=0 ; i<4 ; i++) { if (*s < '0' || *s > '9') { //Con_Printf ("Bad filter address: %s\n", s); return false; } j = 0; while (*s >= '0' && *s <= '9') { num[j++] = *s++; } num[j] = 0; b[i] = Q_atoi(num); if (b[i] != 0) m[i] = 255; if (!*s) break; s++; } f->mask = *(unsigned *)m; f->compare = *(unsigned *)b; return true; } /* ================= SV_AddIPVIP_f ================= */ static void SV_AddIPVIP_f (void) { int i, l; ipfilter_t f; if (!StringToFilter (Cmd_Argv(1), &f)) { Con_Printf ("Bad filter address: %s\n", Cmd_Argv(1)); return; } l = Q_atoi(Cmd_Argv(2)); if (l < 1) l = 1; for (i=0 ; i 0) { if (*s == '+') // "addip 127.0.0.1 ban +10" will ban for 10 seconds from current time s++; else long_time = 0; // "addip 127.0.0.1 ban 1234567" will ban for some seconds since 00:00:00 GMT, January 1, 1970 t = (sscanf(s, "%lf", &t) == 1) ? t + long_time : 0; } f.time = t; f.type = ipft; for (i=0 ; icompare == 0) return false; for (i=0 ; imask && ipfilters[i].compare == f->compare && ipfilters[i].type == ipft_safe) return false; // can't add filter f because present "safe" filter return true; } void SV_RemoveBansIPFilter (int i) { for (; i + 1 < numipfilters; i++) ipfilters[i] = ipfilters[i + 1]; numipfilters--; } void SV_CleanBansIPList (void) { time_t long_time = time(NULL); int i; if (sv.state != ss_active) return; for (i = 0; i < numipfilters;) { if (ipfilters[i].time && ipfilters[i].time <= long_time) { SV_RemoveBansIPFilter (i); } else i++; } } void SV_Cmd_Ban_f(void) { edict_t *ent; eval_t *val; double d; int i, j, t; client_t *cl; ipfilter_t f; int uid; int c; char reason[80] = "", arg2[32], arg2c[sizeof(arg2)], *s; // set up the edict ent = sv_client->edict; // ============ // get ADMIN rights from MOD via "mod_admin" field, mod MUST export such field if wanna server ban support // ============ val = PR_GetEdictFieldValue(ent, "mod_admin"); if (!val || !(val->_int & AF_REAL_ADMIN) ) { Con_Printf("You are not an admin\n"); return; } c = Cmd_Argc (); if (c < 3) { Con_Printf("usage: cmd ban > [reason]\n"); return; } uid = Q_atoi(Cmd_Argv(1)); strlcpy(arg2, Cmd_Argv(2), sizeof(arg2)); // sscanf safe here since sizeof(arg2) == sizeof(arg2c), right? if (sscanf(arg2, "%d%s", &t, arg2c) != 2 || strlen(arg2c) != 1) { Con_Printf("ban: wrong time arg\n"); return; } d = t = bound(0, t, 999); switch(arg2c[0]) { case 's': break; // seconds is seconds case 'm': d *= 60; break; // 60 seconds per minute case 'h': d *= 60*60; break; // 3600 seconds per hour case 'd': d *= 60*60*24; break; // 86400 seconds per day default: Con_Printf("ban: wrong time arg\n"); return; } for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) { if (!cl->state) continue; if (cl->userid == uid || !strcmp(Cmd_Argv(1), cl->name)) { if (c > 3) // serve reason arguments { strlcpy (reason, " (", sizeof(reason)); for (j=3 ; jnetchan.remote_address); if (!StringToFilter (s, &f)) { Con_Printf ("ban: bad ip address: %s\n", s); return; } if (!SV_CanAddBan(&f)) { Con_Printf ("ban: can't ban such ip: %s\n", s); return; } SV_BroadcastPrintf (PRINT_HIGH, "%s was banned for %d%s%s\n", cl->name, t, arg2c, reason); Cbuf_AddText(va("addip %s ban %s%.0lf\n", s, d ? "+" : "", d)); Cbuf_AddText("writeip\n"); return; } } Con_Printf ("Couldn't find user %s\n", Cmd_Argv(1)); } void SV_Cmd_Banip_f(void) { edict_t *ent; eval_t *val; byte b[4]; double d; int c, t; ipfilter_t f; char arg2[32], arg2c[sizeof(arg2)]; // set up the edict ent = sv_client->edict; // ============ // get ADMIN rights from MOD via "mod_admin" field, mod MUST export such field if wanna server ban support // ============ val = PR_GetEdictFieldValue(ent, "mod_admin"); if (!val || !(val->_int & AF_REAL_ADMIN) ) { Con_Printf("You are not an admin\n"); return; } c = Cmd_Argc (); if (c < 3) { Con_Printf("usage: cmd banip >\n"); return; } if (!StringToFilter (Cmd_Argv(1), &f)) { Con_Printf ("ban: bad ip address: %s\n", Cmd_Argv(1)); return; } if (!SV_CanAddBan(&f)) { Con_Printf ("ban: can't ban such ip: %s\n", Cmd_Argv(1)); return; } strlcpy(arg2, Cmd_Argv(2), sizeof(arg2)); // sscanf safe here since sizeof(arg2) == sizeof(arg2c), right? if (sscanf(arg2, "%d%s", &t, arg2c) != 2 || strlen(arg2c) != 1) { Con_Printf("ban: wrong time arg\n"); return; } d = t = bound(0, t, 999); switch(arg2c[0]) { case 's': break; // seconds is seconds case 'm': d *= 60; break; // 60 seconds per minute case 'h': d *= 60*60; break; // 3600 seconds per hour case 'd': d *= 60*60*24; break; // 86400 seconds per day default: Con_Printf("ban: wrong time arg\n"); return; } *(unsigned *)b = f.compare; SV_BroadcastPrintf (PRINT_HIGH, "%3i.%3i.%3i.%3i was banned for %d%s\n", b[0], b[1], b[2], b[3], t, arg2c); Cbuf_AddText(va("addip %i.%i.%i.%i ban %s%.0lf\n", b[0], b[1], b[2], b[3], d ? "+" : "", d)); Cbuf_AddText("writeip\n"); } void SV_Cmd_Banremove_f(void) { edict_t *ent; eval_t *val; byte b[4]; int id; // set up the edict ent = sv_client->edict; // ============ // get ADMIN rights from MOD via "mod_admin" field, mod MUST export such field if wanna server ban support // ============ val = PR_GetEdictFieldValue(ent, "mod_admin"); if (!val || !(val->_int & AF_REAL_ADMIN) ) { Con_Printf("You are not an admin\n"); return; } if (Cmd_Argc () < 2) { Con_Printf("usage: cmd banrem [banid]\n"); SV_BanList(); return; } id = Q_atoi(Cmd_Argv(1)); if (id < 0 || id >= numipfilters) { Con_Printf ("Wrong ban id: %d\n", id); return; } if (ipfilters[id].type == ipft_safe) { Con_Printf ("Can't remove such ban with id: %d\n", id); return; } *(unsigned *)b = ipfilters[id].compare; SV_BroadcastPrintf (PRINT_HIGH, "%3i.%3i.%3i.%3i was unbanned\n", b[0], b[1], b[2], b[3]); SV_RemoveBansIPFilter (id); Cbuf_AddText("writeip\n"); } // } server internal BAN support /* ================= SV_VIPbyIP ================= */ int SV_VIPbyIP (netadr_t adr) { int i; unsigned in; in = *(unsigned *)adr.ip; for (i=0 ; i 32) *s++ = *args++; *s = 0; if ((value = Info_ValueForKey (svs.info, key)) == NULL || !*value) value = Info_Get(&_localinfo_, key); if (ch == '$' && value) { strlcpy(tmp_value, value, sizeof(tmp_value)); Q_normalizetext(tmp_value); value = tmp_value; } *p++ = '\"'; if (value) { while (*value) *p++ = *value++; } *p++ = '\"'; } else { while (*args > 32) { *p++ = *args++; } } } *p = 0; return string; } void SV_Script_f (void) { char *path, *p; extern redirect_t sv_redirected; if (Cmd_Argc() < 2) { Con_Printf("usage: script []\n"); return; } path = Cmd_Argv(1); //bliP: 24/9 need subdirs here -> if (!strncmp(path, "../", 3) || !strncmp(path, "..\\", 3)) path += 3; if (strstr(path, "..")) { Con_Printf("Invalid path.\n"); return; } //<- path = Cmd_Argv(1); p = Cmd_Args(); while (*p > 32) p++; while (*p && *p <= 32) p++; p = DecodeArgs(p); if (sv_redirected != RD_MOD) Sys_Printf("Running %s.qws\n", path); Sys_Script(path, va("%d %s", sv_redirected, p)); } //============================================================================ //bliP: cuff, mute -> void SV_RemoveIPFilter (int i) { for (; i + 1 < numpenfilters; i++) penfilters[i] = penfilters[i + 1]; numpenfilters--; } static void SV_CleanIPList (void) { int i; if (sv.state != ss_active) return; for (i = 0; i < numpenfilters;) { if (penfilters[i].time && (penfilters[i].time <= realtime)) { SV_RemoveIPFilter (i); } else i++; } } static qbool SV_IPCompare (byte *a, byte *b) { int i; for (i = 0; i < 1; i++) if (((unsigned int *)a)[i] != ((unsigned int *)b)[i]) return false; return true; } static void SV_IPCopy (byte *dest, byte *src) { int i; for (i = 0; i < 1; i++) ((unsigned int *)dest)[i] = ((unsigned int *)src)[i]; } void SV_SavePenaltyFilter (client_t *cl, filtertype_t type, double pentime) { int i; if (pentime < curtime) // no point return; for (i = 0; i < numpenfilters; i++) if (SV_IPCompare (penfilters[i].ip, cl->realip.ip) && penfilters[i].type == type) { return; } if (numpenfilters == MAX_IPFILTERS) { return; } SV_IPCopy (penfilters[numpenfilters].ip, cl->realip.ip); penfilters[numpenfilters].time = pentime; penfilters[numpenfilters].type = type; numpenfilters++; } double SV_RestorePenaltyFilter (client_t *cl, filtertype_t type) { int i; double time1 = 0.0; // search for existing penalty filter of same type for (i = 0; i < numpenfilters; i++) { if (type == penfilters[i].type && SV_IPCompare (cl->realip.ip, penfilters[i].ip)) { time1 = penfilters[i].time; SV_RemoveIPFilter (i); return time1; } } return time1; } //<- //============================================================================ /* ================= SV_ReadPackets ================= */ static void SV_ReadPackets (void) { client_t *cl; int qport; int i; if (sv.state != ss_active) return; // first deal with delayed packets from connected clients for (i = 0, cl=svs.clients; i < MAX_CLIENTS; i++, cl++) { if (cl->state == cs_free) continue; net_from = cl->netchan.remote_address; while (cl->packets && (realtime - cl->packets->time >= cl->delay || sv.paused)) { SZ_Clear(&net_message); SZ_Write(&net_message, cl->packets->msg.data, cl->packets->msg.cursize); SV_ExecuteClientMessage(cl); SV_FreeHeadDelayedPacket(cl); } } // now deal with new packets while (NET_GetPacket(NS_SERVER)) { if (SV_FilterPacket ()) { SV_SendBan (); // tell them we aren't listening... continue; } // check for connectionless packet (0xffffffff) first if (*(int *)net_message.data == -1) { SV_ConnectionlessPacket (); continue; } // read the qport out of the message so we can fix up // stupid address translating routers MSG_BeginReading (); MSG_ReadLong (); // sequence number MSG_ReadLong (); // sequence number qport = MSG_ReadShort () & 0xffff; // check which client sent this packet for (i=0, cl=svs.clients ; istate == cs_free) continue; if (!NET_CompareBaseAdr (net_from, cl->netchan.remote_address)) continue; if (cl->netchan.qport != qport) continue; if (cl->netchan.remote_address.port != net_from.port) { Con_DPrintf ("SV_ReadPackets: fixing up a translated port\n"); cl->netchan.remote_address.port = net_from.port; } break; } if (i == MAX_CLIENTS) continue; // ok, we know who sent this packet, but do we need to delay executing it? if (cl->delay > 0) { if (!svs.free_packets) // packet has to be dropped.. break; // insert at end of list if (!cl->packets) { cl->last_packet = cl->packets = svs.free_packets; } else { // this works because '=' associates from right to left cl->last_packet = cl->last_packet->next = svs.free_packets; } svs.free_packets = svs.free_packets->next; cl->last_packet->next = NULL; cl->last_packet->time = realtime; SZ_Clear(&cl->last_packet->msg); SZ_Write(&cl->last_packet->msg, net_message.data, net_message.cursize); } else { SV_ExecuteClientMessage (cl); } } } /* ================== SV_CheckTimeouts If a packet has not been received from a client in timeout.value seconds, drop the conneciton. When a client is normally dropped, the client_t goes into a zombie state for a few seconds to make sure any final reliable message gets resent if necessary ================== */ static void SV_CheckTimeouts (void) { int i, nclients; float droptime; client_t *cl; if (sv.state != ss_active) return; droptime = curtime - timeout.value; nclients = 0; for (i=0,cl=svs.clients ; iisBot ) continue; #endif if (cl->state >= cs_preconnected /*|| cl->state == cs_spawned*/) { if (!cl->spectator) nclients++; if (cl->netchan.last_received < droptime) { SV_BroadcastPrintf (PRINT_HIGH, "%s timed out\n", cl->name); SV_DropClient (cl); cl->state = cs_free; // don't bother with zombie state } if (!cl->logged && !cl->logged_in_via_web) { SV_LoginCheckTimeOut(cl); } } if (cl->state == cs_zombie && SV_ClientConnectedTime(cl) > zombietime.value) { cl->state = cs_free; // can now be reused } } if ((sv.paused & 1) && !nclients) { // nobody left, unpause the server if (GE_ShouldPause) { pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv.edicts); G_FLOAT(OFS_PARM0) = 0 /* newstate = false */; PR_ExecuteProgram (GE_ShouldPause); if (!G_FLOAT(OFS_RETURN)) return; // progs said don't unpause } SV_TogglePause("Pause released since no players are left.\n", 1); } } #ifdef SERVERONLY /* =================== SV_GetConsoleCommands Add them exactly as if they had been typed at the console =================== */ static void SV_GetConsoleCommands (void) { char *cmd; while (1) { cmd = Sys_ConsoleInput (); if (!cmd) break; Cbuf_AddText (cmd); Cbuf_AddText ("\n"); } } #endif /* =================== SV_BoundRate =================== */ int SV_BoundRate (qbool dl, int rate) { if (!rate) rate = 2500; if (dl) { if (!(int)sv_maxdownloadrate.value && (int)sv_maxrate.value && rate > (int)sv_maxrate.value) rate = (int)sv_maxrate.value; if (sv_maxdownloadrate.value && rate > sv_maxdownloadrate.value) rate = (int)sv_maxdownloadrate.value; } else if ((int)sv_maxrate.value && rate > (int) sv_maxrate.value) rate = (int)sv_maxrate.value; if (rate < 500) rate = 500; if (rate > 100000 * MAX_DUPLICATE_PACKETS) rate = 100000 * MAX_DUPLICATE_PACKETS; return rate; } /* =================== SV_CheckVars =================== */ static void SV_CheckVars (void) { static char pw[MAX_KEY_STRING] = {0}, spw[MAX_KEY_STRING] = {0}, vspw[MAX_KEY_STRING]= {0}; static float old_maxrate = 0, old_maxdlrate = 0; int v; if (sv.state != ss_active) return; // check password and spectator_password if (strcmp(password.string, pw) || strcmp(spectator_password.string, spw) || strcmp(vip_password.string, vspw)) { strlcpy (pw, password.string, sizeof(pw)); strlcpy (spw, spectator_password.string, sizeof(spw)); strlcpy (vspw, vip_password.string, sizeof(vspw)); Cvar_Set (&password, pw); Cvar_Set (&spectator_password, spw); Cvar_Set (&vip_password, vspw); v = 0; if (pw[0] && strcmp(pw, "none")) v |= 1; if (spw[0] && strcmp(spw, "none")) v |= 2; if (vspw[0] && strcmp(vspw, "none")) v |= 4; Con_DPrintf ("Updated needpass.\n"); if (!v) Info_SetValueForKey (svs.info, "needpass", "", MAX_SERVERINFO_STRING); else Info_SetValueForKey (svs.info, "needpass", va("%i",v), MAX_SERVERINFO_STRING); } // check sv_maxrate if ((int)sv_maxrate.value != old_maxrate || (int)sv_maxdownloadrate.value != old_maxdlrate ) { client_t *cl; int i; char *val; old_maxrate = (int)sv_maxrate.value; old_maxdlrate = (int)sv_maxdownloadrate.value; for (i=0, cl = svs.clients ; istate < cs_preconnected) continue; val = Info_Get (&cl->_userinfo_ctx_, cl->download ? "drate" : "rate"); cl->netchan.rate = 1.0 / SV_BoundRate (cl->download != NULL, Q_atoi(*val ? val : "99999")); } } } static void PausedTic (void) { if (sv.state != ss_active) return; PR_PausedTic(Sys_DoubleTime() - sv.pausedsince); } /* ================== SV_Frame ================== */ void SV_Map (qbool now); void SV_Frame (double time1) { static double start, end; double demo_start, demo_end; start = Sys_DoubleTime (); svs.stats.idle += start - end; // keep the random time dependent rand (); // decide the simulation time if (!sv.paused) { realtime += time1; sv.time += time1; } // check timeouts SV_CheckTimeouts (); //bliP: cuff, mute -> // clean out expired cuffs/mutes SV_CleanIPList (); //<- // clean out bans SV_CleanBansIPList (); // toggle the log buffer if full SV_CheckLog (); SV_MVDStream_Poll(); #ifdef SERVERONLY // check for commands typed to the host SV_GetConsoleCommands (); // process console commands Cbuf_Execute (); #endif // check for map change; SV_Map(true); SV_CheckVars (); // get packets SV_ReadPackets (); // move autonomous things around if enough time has passed if (!sv.paused) { SV_Physics(); #ifdef USE_PR2 SV_RunBots(); #endif } else PausedTic (); // send messages back to the clients that had packets read this frame SV_SendClientMessages (); #if defined(SERVERONLY) && defined(WWW_INTEGRATION) Central_ProcessResponses(); #endif demo_start = Sys_DoubleTime (); SV_SendDemoMessage(); demo_end = Sys_DoubleTime (); svs.stats.demo += demo_end - demo_start; // send a heartbeat to the master if needed Master_Heartbeat (); // collect timing statistics end = Sys_DoubleTime (); svs.stats.active += end-start; if (++svs.stats.count == STATFRAMES) { svs.stats.latched_active = svs.stats.active; svs.stats.latched_idle = svs.stats.idle; svs.stats.latched_packets = svs.stats.packets; svs.stats.latched_demo = svs.stats.demo; svs.stats.active = 0; svs.stats.idle = 0; svs.stats.packets = 0; svs.stats.count = 0; svs.stats.demo = 0; } } /* =============== SV_InitLocal =============== */ void SV_InitLocal (void) { int i; char cmd_line[1024] = {0}; extern cvar_t sv_maxvelocity; extern cvar_t sv_gravity; extern cvar_t sv_stopspeed; extern cvar_t sv_spectatormaxspeed; extern cvar_t sv_accelerate; extern cvar_t sv_airaccelerate; extern cvar_t sv_wateraccelerate; extern cvar_t sv_friction; extern cvar_t sv_waterfriction; extern cvar_t sv_nailhack; extern cvar_t sv_maxpitch; extern cvar_t sv_minpitch; extern cvar_t pm_airstep; extern cvar_t pm_pground; extern cvar_t pm_rampjump; extern cvar_t pm_slidefix; extern cvar_t pm_ktjump; //extern cvar_t pm_bunnyspeedcap; // qws = QuakeWorld Server information static cvar_t qws_name = { "qws_name", SERVER_NAME, CVAR_ROM }; static cvar_t qws_fullname = { "qws_fullname", SERVER_FULLNAME, CVAR_ROM }; static cvar_t qws_version = { "qws_version", SERVER_VERSION, CVAR_ROM }; static cvar_t qws_buildnum = { "qws_buildnum", "unknown", CVAR_ROM }; static cvar_t qws_platform = { "qws_platform", QW_PLATFORM_SHORT, CVAR_ROM }; static cvar_t qws_builddate = { "qws_builddate", BUILD_DATE, CVAR_ROM }; static cvar_t qws_homepage = { "qws_homepage", SERVER_HOME_URL, CVAR_ROM }; // qwm = QuakeWorld Mod information placeholders static cvar_t qwm_name = { "qwm_name", "" }; static cvar_t qwm_fullname = { "qwm_fullname", "" }; static cvar_t qwm_version = { "qwm_version", "" }; static cvar_t qwm_buildnum = { "qwm_buildnum", "" }; static cvar_t qwm_platform = { "qwm_platform", "" }; static cvar_t qwm_builddate = { "qwm_builddate", "" }; static cvar_t qwm_homepage = { "qwm_homepage", "" }; packet_t *packet_freeblock; // initialise delayed packet free block SV_InitOperatorCommands (); SV_UserInit (); Cvar_Register (&sv_getrealip); Cvar_Register (&sv_maxdownloadrate); Cvar_Register (&sv_serverip); Cvar_Register (&sv_forcespec_onfull); #ifdef SERVERONLY Cvar_Register (&rcon_password); Cvar_Register (&password); #endif Cvar_Register (&sv_hashpasswords); //Added by VVD { Cvar_Register (&sv_crypt_rcon); Cvar_Register (&sv_timestamplen); Cvar_Register (&sv_rconlim); Cvar_Register (&telnet_log_level); Cvar_Register (&frag_log_type); Cvar_Register (&qconsole_log_say); Cvar_Register (&sv_use_dns); for (i = 0; i < COM_Argc(); i++) { if (i) strlcat(cmd_line, " ", sizeof(cmd_line)); strlcat(cmd_line, COM_Argv(i), sizeof(cmd_line)); } Cvar_Register (&sys_command_line); Cvar_SetROM(&sys_command_line, cmd_line); //Added by VVD } Cvar_Register (&spectator_password); Cvar_Register (&vip_password); Cvar_Register (&vip_values); Cvar_Register (&sv_nailhack); Cvar_Register (&sv_mintic); Cvar_Register (&sv_maxtic); Cvar_Register (&sv_maxfps); Cvar_Register (&sys_select_timeout); Cvar_Register (&sys_restart_on_error); Cvar_Register (&sv_maxpitch); Cvar_Register (&sv_minpitch); Cvar_Register (&skill); Cvar_Register (&coop); Cvar_Register (&fraglimit); Cvar_Register (&timelimit); Cvar_Register (&teamplay); Cvar_Register (&samelevel); Cvar_Register (&maxclients); Cvar_Register (&maxspectators); Cvar_Register (&maxvip_spectators); Cvar_Register (&hostname); Cvar_Register (&deathmatch); Cvar_Register (&watervis); Cvar_Register (&serverdemo); Cvar_Register (&sv_paused); Cvar_Register (&timeout); Cvar_Register (&zombietime); Cvar_Register (&sv_maxvelocity); Cvar_Register (&sv_gravity); Cvar_Register (&sv_stopspeed); Cvar_Register (&sv_maxspeed); Cvar_Register (&sv_spectatormaxspeed); Cvar_Register (&sv_accelerate); Cvar_Register (&sv_airaccelerate); Cvar_Register (&sv_wateraccelerate); Cvar_Register (&sv_friction); Cvar_Register (&sv_waterfriction); Cvar_Register (&sv_antilag); Cvar_Register (&sv_antilag_no_pred); Cvar_Register (&sv_antilag_projectiles); //Cvar_Register (&pm_bunnyspeedcap); Cvar_Register (&pm_ktjump); Cvar_Register (&pm_slidefix); Cvar_Register (&pm_pground); Cvar_Register (&pm_airstep); Cvar_Register (&pm_rampjump); Cvar_Register (&filterban); Cvar_Register (&allow_download); Cvar_Register (&allow_download_skins); Cvar_Register (&allow_download_models); Cvar_Register (&allow_download_sounds); Cvar_Register (&allow_download_maps); Cvar_Register (&allow_download_pakmaps); Cvar_Register (&allow_download_demos); Cvar_Register (&allow_download_other); //bliP: init -> Cvar_Register (&download_map_url); Cvar_Register (&sv_specprint); Cvar_Register (&sv_admininfo); Cvar_Register (&sv_reconnectlimit); Cvar_Register (&sv_maxlogsize); //bliP: 24/9 -> Cvar_Register (&sv_logdir); Cvar_Register (&sv_speedcheck); Cvar_Register (&sv_unfake); // kickfake to unfake //<- Cvar_Register (&sv_kicktop); //<- Cvar_Register (&sv_allowlastscores); // Cvar_Register (&sv_highchars); Cvar_Register (&sv_phs); Cvar_Register (&pausable); Cvar_Register (&sv_maxrate); Cvar_Register (&sv_loadentfiles); Cvar_Register (&sv_loadentfiles_dir); Cvar_Register (&sv_default_name); Cvar_Register (&sv_mod_msg_file); Cvar_Register (&sv_forcenick); Cvar_Register (&sv_registrationinfo); Cvar_Register (®istered); Cvar_Register (&sv_halflifebsp); Cvar_Register (&sv_bspversion); Cvar_Register (&sv_serveme_fix); #ifdef FTE_PEXT_FLOATCOORDS Cvar_Register (&sv_bigcoords); #endif Cvar_Register (&sv_extlimits); Cvar_Register (&sv_pext_mvdsv_serversideweapon); Cvar_Register (&sv_reliable_sound); Cvar_Register(&qws_name); Cvar_Register(&qws_fullname); Cvar_Register(&qws_version); if (GIT_COMMIT[0]) { qws_buildnum.string = GIT_COMMIT; } Cvar_Register(&qws_buildnum); Cvar_Register(&qws_platform); Cvar_Register(&qws_builddate); Cvar_Register(&qws_homepage); Cvar_Register(&qwm_name); Cvar_Register(&qwm_fullname); Cvar_Register(&qwm_version); Cvar_Register(&qwm_buildnum); Cvar_Register(&qwm_platform); Cvar_Register(&qwm_builddate); Cvar_Register(&qwm_homepage); Cvar_Register(&sv_mod_extensions); // QW262 --> Cmd_AddCommand ("svadmin", SV_Admin_f); // <-- QW262 Cmd_AddCommand ("addip", SV_AddIP_f); Cmd_AddCommand ("removeip", SV_RemoveIP_f); Cmd_AddCommand ("listip", SV_ListIP_f); Cmd_AddCommand ("writeip", SV_WriteIP_f); Cmd_AddCommand ("vip_addip", SV_AddIPVIP_f); Cmd_AddCommand ("vip_removeip", SV_RemoveIPVIP_f); Cmd_AddCommand ("vip_listip", SV_ListIPVIP_f); Cmd_AddCommand ("vip_writeip", SV_WriteIPVIP_f); for (i=0 ; i_userinfo_ctx_, "name"); // trim user name strlcpy (newname, val, sizeof(newname)); for (p = val; *p; p++) { if ((*p & 127) == '\\' || *p == '\r' || *p == '\n' || *p == '$' || *p == '#' || *p == '"' || *p == ';') { // illegal characters in name, set some default strlcpy(newname, sv_default_name.string, sizeof(newname)); break; } } for (p = newname; *p && (*p & 127) == ' '; p++) ; // empty operator if (p != newname) // skip prefixed spaces, if any, even whole string of spaces strlcpy(newname, p, sizeof(newname)); for (p = newname + strlen(newname) - 1; p >= newname; p--) { if (*p && (*p & 127) != ' ') // skip spaces in suffix, if any { p[1] = 0; break; } } if (strcmp(val, newname)) { Info_Set (&cl->_userinfo_ctx_, "name", newname); val = Info_Get (&cl->_userinfo_ctx_, "name"); } if (!val[0] || !Q_namecmp(val, "console") || strstr(val, "&c") || strstr(val, "&r")) { Info_Set (&cl->_userinfo_ctx_, "name", sv_default_name.string); val = Info_Get (&cl->_userinfo_ctx_, "name"); } // check to see if another user by the same name exists while ( 1 ) { for (i = 0, client = svs.clients ; istate != cs_spawned || client == cl) continue; if (!Q_namecmp(client->name, val)) break; } if (i != MAX_CLIENTS) { // dup name if (strlen(val) > CLIENT_NAME_LEN - 1) val[CLIENT_NAME_LEN - 4] = 0; p = val; if (val[0] == '(') { if (val[2] == ')') p = val + 3; else if (val[3] == ')') p = val + 4; } snprintf(newname, sizeof(newname), "(%d)%-.10s", dupc++, p); Info_Set (&cl->_userinfo_ctx_, "name", newname); val = Info_Get (&cl->_userinfo_ctx_, "name"); } else break; } if (strncmp(val, cl->name, strlen(cl->name) + 1)) { if (!cl->lastnametime || curtime - cl->lastnametime > 5) { cl->lastnamecount = 0; cl->lastnametime = curtime; } else if (cl->lastnamecount++ > 4) { SV_BroadcastPrintf(PRINT_HIGH, "%s was kicked for name spamming\n", cl->name); SV_ClientPrintf(cl, PRINT_HIGH, "You were kicked from the game for name spamming\n"); SV_LogPlayer(cl, "name spam", 1); //bliP: player logging SV_DropClient(cl); return; } if (cl->state >= cs_spawned && !cl->spectator) { SV_BroadcastPrintf(PRINT_HIGH, "%s changed name to %s\n", cl->name, val); } } strlcpy(cl->name, val, CLIENT_NAME_LEN); if (cl->state >= cs_spawned) //bliP: player logging SV_LogPlayer(cl, "name change", 1); } // team val = Info_Get (&cl->_userinfo_ctx_, "team"); if (strstr(val, "&c") || strstr(val, "&r")) Info_Set (&cl->_userinfo_ctx_, "team", "none"); strlcpy (cl->team, Info_Get (&cl->_userinfo_ctx_, "team"), sizeof(cl->team)); // rate val = Info_Get (&cl->_userinfo_ctx_, cl->download ? "drate" : "rate"); cl->netchan.rate = 1.0 / SV_BoundRate (cl->download != NULL, Q_atoi(*val ? val : "99999")); // s2c packet dupes val = Info_Get(&cl->_userinfo_ctx_, "dupe"); cl->dupe = atoi(val); cl->dupe = (int)bound(0, cl->dupe, MAX_DUPLICATE_PACKETS); // 0=1 packet (aka: no dupes) cl->netchan.dupe = (cl->download ? 0 : cl->dupe); // message level val = Info_Get (&cl->_userinfo_ctx_, "msg"); if (val[0]) cl->messagelevel = Q_atoi(val); //spectator print val = Info_Get(&cl->_userinfo_ctx_, "sp"); if (val[0]) cl->spec_print = Q_atoi(val); } //============================================================================ void OnChange_sysselecttimeout_var (cvar_t *var, char *value, qbool *cancel) { int t = Q_atoi (value); if (t < 1000 || t > 1000000) { Con_Printf("WARNING: sys_select_timeout can't be less then 1000 (1 millisecond) and more then 1 000 000 (1 second).\n"); *cancel = true; return; } } //bliP: 24/9 logdir -> void OnChange_logdir_var (cvar_t *var, char *value, qbool *cancel) { if (strstr(value, "..")) { *cancel = true; return; } if (value[0]) Sys_mkdir (value); } //<- //bliP: admininfo -> void OnChange_admininfo_var (cvar_t *var, char *value, qbool *cancel) { if (value[0]) Info_SetValueForStarKey (svs.info, "*admin", value, MAX_SERVERINFO_STRING); else Info_RemoveKey (svs.info, "*admin"); } //<- //bliP: telnet log level -> void OnChange_telnetloglevel_var (cvar_t *var, char *value, qbool *cancel) { logs[TELNET_LOG].log_level = Q_atoi(value); } //<- void OnChange_qconsolelogsay_var (cvar_t *var, char *value, qbool *cancel) { logs[CONSOLE_LOG].log_level = Q_atoi(value); } #ifdef SERVERONLY void COM_Init (void) { Cvar_Register (&developer); Cvar_Register (&version); Cvar_Register (&sys_simulation); Cvar_SetROM(&version, SERVER_NAME " " SERVER_VERSION); } //Free hunk memory up to host_hunklevel //Can only be called when changing levels! void Host_ClearMemory (void) { if (!host_initialized) Sys_Error ("Host_ClearMemory before host initialized"); CM_InvalidateMap (); // any data previously allocated on hunk is no longer valid Hunk_FreeToLowMark (host_hunklevel); } //memsize is the recommended amount of memory to use for hunk void Host_InitMemory (int memsize) { int t; if (SV_CommandLineUseMinimumMemory()) memsize = MINIMUM_MEMORY; if ((t = SV_CommandLineHeapSizeMemoryKB()) != 0 && t + 1 < COM_Argc()) memsize = Q_atoi (COM_Argv(t + 1)) * 1024; if ((t = SV_CommandLineHeapSizeMemoryMB()) != 0 && t + 1 < COM_Argc()) memsize = Q_atoi (COM_Argv(t + 1)) * 1024 * 1024; if (memsize < MINIMUM_MEMORY) Sys_Error ("Only %4.1f megs of memory reported, can't execute game", memsize / (float)0x100000); Memory_Init (Q_malloc(memsize), memsize); } void Host_Init (int argc, char **argv, int default_memsize) { extern int hunk_size; cvar_t *v; srand((unsigned)time(NULL)); COM_InitArgv (argc, argv); Host_InitMemory (default_memsize); Con_Printf ("============= Starting %s =============\n", VersionStringFull()); Cbuf_Init (); Cmd_Init (); Cvar_Init (); COM_Init (); FS_Init (); NET_Init (); Netchan_Init (); Sys_Init (); CM_Init (); SV_Init (); Hunk_AllocName (0, "-HOST_HUNKLEVEL-"); host_hunklevel = Hunk_LowMark (); host_initialized = true; // walk through all vars and forse OnChange event if cvar was modified, // also apply that to variables which mirrored in userinfo because of cl_parsefunchars was't applyed as this moment, // same for serverinfo and may be this fix something also. for ( v = NULL; (v = Cvar_Next ( v )); ) { if ( Cvar_GetFlags( v ) & (CVAR_ROM) ) continue; Cvar_Set(v, v->string); } Con_Printf ("%4.1f megabyte heap\n", (float)hunk_size / (1024 * 1024)); Con_Printf ("QuakeWorld Initialized\n"); #ifndef WWW_INTEGRATION Con_Printf ("www authentication disabled (no curl support)\n"); #endif Cbuf_InsertText ("exec server.cfg\n"); // process command line arguments Cmd_StuffCmds_f (); Cbuf_Execute (); host_everything_loaded = true; SV_Map(true); // if a map wasn't specified on the command line, spawn mvdsv-kg map if (sv.state == ss_dead) { Cmd_ExecuteString ("map mvdsv-kg"); SV_Map(true); } // last resort - start map if (sv.state == ss_dead) { Cmd_ExecuteString ("map start"); SV_Map(true); } if (sv.state == ss_dead) SV_Error ("Couldn't spawn a server"); #if defined (_WIN32) && !defined(_CONSOLE) { void SetWindowText_(char*); SetWindowText_(va(SERVER_NAME ":%d - QuakeWorld server", NET_UDPSVPort())); } #endif } #endif // SERVERONLY /* ==================== SV_Init ==================== */ void SV_Init (void) { memset(&_localinfo_, 0, sizeof(_localinfo_)); _localinfo_.max = MAX_LOCALINFOS; PR_Init (); // send immediately svs.last_heartbeat = -99999; SV_InitLocal (); SV_MVDInit (); Login_Init (); #ifndef SERVERONLY server_cfg_done = true; #endif #if defined(SERVERONLY) && defined(WWW_INTEGRATION) Central_Init (); #endif } /* ============ SV_TimeOfDay ============ */ void SV_TimeOfDay(date_t *date, char *timeformat) { struct tm *newtime; time_t long_time; time(&long_time); newtime = localtime(&long_time); //bliP: date check -> if (!newtime) { date->day = 0; date->mon = 0; date->year = 0; date->hour = 0; date->min = 0; date->sec = 0; strlcpy(date->str, "#bad date#", sizeof(date->str)); return; } //<- date->day = newtime->tm_mday; date->mon = newtime->tm_mon; date->year = newtime->tm_year + 1900; date->hour = newtime->tm_hour; date->min = newtime->tm_min; date->sec = newtime->tm_sec; strftime(date->str, sizeof(date->str)-1, timeformat, newtime); } //bliP: player logging -> /* ============ SV_LogPlayer ============ */ void SV_LogPlayer(client_t *cl, char *msg, int level) { char info[MAX_EXT_INFO_STRING]; char name[CLIENT_NAME_LEN]; Info_ReverseConvert(&cl->_userinfo_ctx_, info, sizeof(info)); Q_normalizetext(info); strlcpy(name, cl->name, sizeof(name)); Q_normalizetext(name); SV_Write_Log(PLAYER_LOG, level, va("%s\\%s\\%i\\%s\\%s\\%i%s\n", msg, name, cl->userid, NET_BaseAdrToString(cl->netchan.remote_address), NET_BaseAdrToString(cl->realip), cl->netchan.remote_address.port, info ) ); } /* ============ SV_Write_Log ============ */ void SV_Write_Log(int sv_log, int level, char *msg) { static date_t date; char *log_msg, *error_msg; if (!(logs[sv_log].sv_logfile && *msg)) return; if (logs[sv_log].log_level < level) return; SV_TimeOfDay(&date, "%a %b %d, %H:%M:%S %Y"); switch (sv_log) { case FRAG_LOG: case MOD_FRAG_LOG: log_msg = msg; // these logs aren't in fs_gamedir error_msg = va("Can't write in %s log file: "/*%s/ */"%sN.log.\n", /*fs_gamedir,*/ logs[sv_log].message_on, logs[sv_log].file_name); break; default: log_msg = va("[%s].[%d] %s", date.str, level, msg); error_msg = va("Can't write in %s log file: "/*%s/ */"%s%i.log.\n", /*fs_gamedir,*/ logs[sv_log].message_on, logs[sv_log].file_name, NET_UDPSVPort()); } if (fprintf(logs[sv_log].sv_logfile, "%s", log_msg) < 0) { //bliP: Sys_Error to Con_DPrintf -> //VVD: Con_DPrintf to Sys_Printf -> Sys_Printf("%s", error_msg); //<- SV_Logfile(sv_log, false); } else { fflush(logs[sv_log].sv_logfile); if ((int)sv_maxlogsize.value && (FS_FileLength(logs[sv_log].sv_logfile) > (int)sv_maxlogsize.value)) { SV_Logfile(sv_log, true); } } } /* ============ Sys_compare_by functions for sort files in list ============ */ int Sys_compare_by_date (const void *a, const void *b) { return (int)(((file_t *)a)->time - ((file_t *)b)->time); } int Sys_compare_by_name (const void *a, const void *b) { return strncmp(((file_t *)a)->name, ((file_t *)b)->name, MAX_DEMO_NAME); } // real-world time passed double SV_ClientConnectedTime(client_t* client) { if (!client->connection_started_curtime) { return 0; } return curtime - client->connection_started_curtime; } // affected by pause double SV_ClientGameTime(client_t* client) { if (!client->connection_started_realtime) { return 0; } return realtime - client->connection_started_realtime; } void SV_SetClientConnectionTime(client_t* client) { client->connection_started_realtime = realtime; client->connection_started_curtime = curtime; } #endif // !CLIENTONLY mvdsv-0.35/src/sv_master.c000066400000000000000000000061611427146041000155410ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // sv_master.c - send heartbeats to master server #ifndef CLIENTONLY #include "qwsvdef.h" #define HEARTBEAT_SECONDS 300 static netadr_t master_adr[MAX_MASTERS]; // address of group servers /* ==================== SV_SetMaster_f Make a master server current ==================== */ void SV_SetMaster_f (void) { char data[2]; int i; memset (&master_adr, 0, sizeof(master_adr)); for (i=1 ; istr); Q_free(qwmsg1[i]); } } } void sv_mod_msg_file_OnChange(cvar_t *cvar, char *value, qbool *cancel) { FILE *fp = NULL; char *str_tok, buf[128]; size_t len; int i; free_qwmsg_t(qwmsg); if (value[0]) fp = fopen(value, "r"); if (fp == NULL) { if (value[0]) Con_Printf("WARNING: sv_mod_msg_file_OnChange: can't open file %s.\n", value); for (i = 0; i < MOD_MSG_MAX && qwmsg_def[i].str; i++) { qwmsg[i] = &qwmsg_def[i]; } qwm_static = true; Con_DPrintf("Initialized default mod messages.\nTotal: %d messages.\n", i); } else { for (i = 0; i < MOD_MSG_MAX && !feof(fp); i++) { if (fgets(buf, sizeof(buf), fp)) { qwmsg[i] = (qwmsg_t *) Q_malloc (sizeof(qwmsg_t)); // fill system_id str_tok = (char *)strtok(buf, "#"); qwmsg[i]->msg_type = Q_atoi(str_tok); // fill weapon_id str_tok = (char *)strtok(NULL, "#"); qwmsg[i]->id = Q_atoi(str_tok); // fill pl_count str_tok = (char *)strtok(NULL, "#"); qwmsg[i]->pl_count = Q_atoi(str_tok) == 1 ? 1 : 2; // fill reverse str_tok = (char *)strtok(NULL, "#"); qwmsg[i]->reverse = Q_atoi(str_tok) ? true : false; // fill str str_tok = (char *)strtok(NULL, "#"); len = strlen (str_tok) + 1; qwmsg[i]->str = (char *) Q_malloc (len); strlcpy(qwmsg[i]->str, str_tok, len); } else break; // Sys_Printf("msg_type = %d, id = %d, pl_count = %d, str = %s, reverse = %d\n", // qwmsg[i]->msg_type, qwmsg[i]->id, qwmsg[i]->pl_count, qwmsg[i]->str, qwmsg[i]->reverse); } qwm_static = false; Con_DPrintf("Initialized mod messages from file %s.\nTotal: %d messages.\n", value, i); fclose(fp); } qwmsg[i] = NULL; *cancel = false; } const char **qwmsg_pcre_check(const char *str, const char *qwm_str, int str_len) { pcre *reg; int *ovector[32]; const char *errbuf; int erroffset = 0; const char **buf = NULL; int stringcount; if (!(reg = pcre_compile(qwm_str, 0, &errbuf, &erroffset, 0))) { Sys_Printf("WARNING: qwmsg_pcre_check: pcre_compile(%s) error %s\n", qwm_str, errbuf); return NULL; } stringcount = pcre_exec(reg, NULL, str, str_len, 0, 0, (int *)&ovector[0], 32); pcre_free(reg); if (stringcount <= 0) { return NULL; } pcre_get_substring_list(str, (int *)&ovector[0], stringcount, &buf); return buf; } // main function char *parse_mod_string(char *str) { const char **buf; int i, str_len = strlen(str); char *ret = NULL; for (i = 0; qwmsg[i]; i++) { if ((buf = qwmsg_pcre_check(str, qwmsg[i]->str, str_len))) { int pl1, pl2; switch (qwmsg[i]->msg_type) { case WEAPON: pl1 = pl2 = 1; switch (qwmsg[i]->pl_count) { case 2: pl2 += qwmsg[i]->reverse; pl1 = 3 - pl2; case 1: str_len = strlen(buf[pl1]) + strlen(buf[pl2]) + strlen(qw_weapon[qwmsg[i]->id]) + 5 + 10; ret = (char *) Q_malloc (str_len); snprintf(ret, str_len, "%s\\%s\\%s\\%d\n", buf[pl1], buf[pl2], qw_weapon[qwmsg[i]->id], (int)time(NULL)); break; default: ret = NULL; } break; case SYSTEM: str_len = strlen(buf[1]) * 2 + strlen(qw_system[qwmsg[i]->id]) + 4 + 10; ret = (char *) Q_malloc (str_len); snprintf(ret, str_len, "%s\\%s\\%d\n", buf[1], qw_system[qwmsg[i]->id], (int)time(NULL)); break; default: ret = NULL; } pcre_free_substring_list(buf); break; } } return ret; } #endif // !CLIENTONLY mvdsv-0.35/src/sv_mod_frags.h000066400000000000000000000123411427146041000162110ustar00rootroot00000000000000/* * sv_mod_frags.h * QuakeWorld message definitions * For glad & vvd * (C) kreon 2005 * Messages from fuhquake's fragfile.dat */ /* 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. */ #ifndef __SV_MOD_FRAGS__ #define __SV_MOD_FRAGS__ extern cvar_t sv_mod_msg_file; #define MOD_MSG_MAX 512 // weapons definitions char *qw_weapon[] = { "die", "axe", "sg", "ssg", "ng", "sng", "gl", "rl", "lg", "rail", "drown", "trap", "tele", "dschrg", "squish", "fall", "team", "stomps" }; // system messages (not released yet) definitions char *qw_system[] = { "start", "end", "connect", "disconnect", "timeout" }; // main struct for mod's messages searching typedef struct qw_message { int msg_type; // type of message int id; // id of weapon id for WEAPON int pl_count; // count of players in each message char *str; // pointer to string qbool reverse; // reversing message? (a->b or b->a) } qwmsg_t; // messages types enum { MIN_TYPE = 0, WEAPON = 0, SYSTEM, MAX_TYPE}; // DEFAULT mod messages // From fuhquake's fragfile.dat qwmsg_t qwmsg_def[] = { {WEAPON, 10, 1, "(.*) sleeps with the fishes", false}, {WEAPON, 10, 1, "(.*) sucks it down", false}, {WEAPON, 10, 1, "(.*) gulped a load of slime", false}, {WEAPON, 10, 1, "(.*) can't exist on slime alone", false}, {WEAPON, 10, 1, "(.*) burst into flames", false}, {WEAPON, 10, 1, "(.*) turned into hot slag", false}, {WEAPON, 10, 1, "(.*) visits the Volcano God", false}, {WEAPON, 15, 1, "(.*) cratered", false}, {WEAPON, 15, 1, "(.*) fell to his death", false}, {WEAPON, 15, 1, "(.*) fell to her death", false}, {WEAPON, 11, 1, "(.*) blew up", false}, {WEAPON, 11, 1, "(.*) was spiked", false}, {WEAPON, 11, 1, "(.*) was zapped", false}, {WEAPON, 11, 1, "(.*) ate a lavaball", false}, {WEAPON, 12, 1, "(.*) was telefragged by his teammate", false}, {WEAPON, 12, 1, "(.*) was telefragged by her teammate", false}, {WEAPON, 0, 1, "(.*) died", false}, {WEAPON, 0, 1, "(.*) tried to leave", false}, {WEAPON, 14, 1, "(.*) was squished", false}, {WEAPON, 0, 1, "(.*) suicides", false}, {WEAPON, 6, 1, "(.*) tries to put the pin back in", false}, {WEAPON, 7, 1, "(.*) becomes bored with life", false}, {WEAPON, 7, 1, "(.*) discovers blast radius", false}, {WEAPON, 13, 1, "(.*) electrocutes himself.", false}, {WEAPON, 13, 1, "(.*) electrocutes herself.", false}, {WEAPON, 13, 1, "(.*) discharges into the slime", false}, {WEAPON, 13, 1, "(.*) discharges into the lava", false}, {WEAPON, 13, 1, "(.*) discharges into the water", false}, {WEAPON, 13, 1, "(.*) heats up the water", false}, {WEAPON, 16, 1, "(.*) squished a teammate", false}, {WEAPON, 16, 1, "(.*) mows down a teammate", false}, {WEAPON, 16, 1, "(.*) checks his glasses", false}, {WEAPON, 16, 1, "(.*) checks her glasses", false}, {WEAPON, 16, 1, "(.*) gets a frag for the other team", false}, {WEAPON, 16, 1, "(.*) loses another friend", false}, {WEAPON, 1, 2, "(.*) was ax-murdered by (.*)", false}, {WEAPON, 2, 2, "(.*) was lead poisoned by (.*)", false}, {WEAPON, 2, 2, "(.*) chewed on (.*)'s boomstick", false}, {WEAPON, 3, 2, "(.*) ate 8 loads of (.*)'s buckshot", false}, {WEAPON, 3, 2, "(.*) ate 2 loads of (.*)'s buckshot", false}, {WEAPON, 4, 2, "(.*) was body pierced by (.*)", false}, {WEAPON, 4, 2, "(.*) was nailed by (.*)", false}, {WEAPON, 5, 2, "(.*) was perforated by (.*)", false}, {WEAPON, 5, 2, "(.*) was punctured by (.*)", false}, {WEAPON, 5, 2, "(.*) was ventilated by (.*)", false}, {WEAPON, 5, 2, "(.*) was straw-cuttered by (.*)", false}, {WEAPON, 6, 2, "(.*) eats (.*)'s pineapple", false}, {WEAPON, 6, 2, "(.*) was gibbed by (.*)'s grenade", false}, {WEAPON, 7, 2, "(.*) was smeared by (.*)'s quad rocket", false}, {WEAPON, 7, 2, "(.*) was brutalized by (.*)'s quad rocket", false}, {WEAPON, 7, 2, "(.*) rips (.*) a new one", true}, {WEAPON, 7, 2, "(.*) was gibbed by (.*)'s rocket", false}, {WEAPON, 7, 2, "(.*) rides (.*)'s rocket", false}, {WEAPON, 8, 2, "(.*) accepts (.*)'s shaft", false}, {WEAPON, 9, 2, "(.*) was railed by (.*)", false}, {WEAPON, 12, 2, "(.*) was telefragged by (.*)", false}, {WEAPON, 14, 2, "(.*) squishes (.*)", true}, {WEAPON, 13, 2, "(.*) accepts (.*)'s discharge", false}, {WEAPON, 13, 2, "(.*) drains (.*)'s batteries", false}, {WEAPON, 8, 2, "(.*) gets a natural disaster from (.*)", false}, { 0, 0, 0, NULL, 0} }; // end #endif /* !__SV_MOD_FRAGS__ */ mvdsv-0.35/src/sv_move.c000066400000000000000000000224501427146041000152130ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // sv_move.c -- monster movement #ifndef CLIENTONLY #include "qwsvdef.h" #define STEPSIZE 18 /* ============= SV_CheckBottom Returns false if any part of the bottom of the entity is off an edge that is not a staircase. ============= */ qbool SV_CheckBottom (edict_t *ent) { vec3_t mins, maxs, start, stop; trace_t trace; int x, y; float mid, bottom; VectorAdd (ent->v->origin, ent->v->mins, mins); VectorAdd (ent->v->origin, ent->v->maxs, maxs); // if all of the points under the corners are solid world, don't bother // with the tougher checks // the corners must be within 16 of the midpoint start[2] = mins[2] - 1; for (x=0 ; x<=1 ; x++) for (y=0 ; y<=1 ; y++) { start[0] = x ? maxs[0] : mins[0]; start[1] = y ? maxs[1] : mins[1]; if (SV_PointContents (start) != CONTENTS_SOLID) goto realcheck; } return true; // we got out easy realcheck: // // check it for real... // start[2] = mins[2]; // the midpoint must be within 16 of the bottom start[0] = stop[0] = (mins[0] + maxs[0])*0.5; start[1] = stop[1] = (mins[1] + maxs[1])*0.5; stop[2] = start[2] - 2*STEPSIZE; trace = SV_Trace (start, vec3_origin, vec3_origin, stop, true, ent); if (trace.fraction == 1.0) return false; mid = bottom = trace.endpos[2]; // the corners must be within 16 of the midpoint for (x=0 ; x<=1 ; x++) for (y=0 ; y<=1 ; y++) { start[0] = stop[0] = x ? maxs[0] : mins[0]; start[1] = stop[1] = y ? maxs[1] : mins[1]; trace = SV_Trace (start, vec3_origin, vec3_origin, stop, true, ent); if (trace.fraction != 1.0 && trace.endpos[2] > bottom) bottom = trace.endpos[2]; if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) return false; } return true; } /* ============= SV_movestep Called by monster program code. The move will be adjusted for slopes and stairs, but if the move isn't possible, no move is done, false is returned, and pr_global_struct->trace_normal is set to the normal of the blocking wall ============= */ qbool SV_movestep (edict_t *ent, vec3_t move, qbool relink) { float dz; vec3_t oldorg, neworg, end; trace_t trace; int i; edict_t *enemy; // try the move VectorCopy (ent->v->origin, oldorg); VectorAdd (ent->v->origin, move, neworg); // flying monsters don't step up if ( (int)ent->v->flags & (FL_SWIM | FL_FLY) ) { // try one move with vertical motion, then one without for (i=0 ; i<2 ; i++) { VectorAdd (ent->v->origin, move, neworg); enemy = PROG_TO_EDICT(ent->v->enemy); if (i == 0 && enemy != sv.edicts) { dz = ent->v->origin[2] - PROG_TO_EDICT(ent->v->enemy)->v->origin[2]; if (dz > 40) neworg[2] -= 8; if (dz < 30) neworg[2] += 8; } trace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, neworg, false, ent); if (trace.fraction == 1) { if ( ((int)ent->v->flags & FL_SWIM) && SV_PointContents(trace.endpos) == CONTENTS_EMPTY ) return false; // swim monster left water VectorCopy (trace.endpos, ent->v->origin); if (relink) SV_LinkEdict (ent, true); return true; } if (enemy == sv.edicts) break; } return false; } // push down from a step height above the wished position neworg[2] += STEPSIZE; VectorCopy (neworg, end); end[2] -= STEPSIZE*2; trace = SV_Trace (neworg, ent->v->mins, ent->v->maxs, end, false, ent); if (trace.allsolid) return false; if (trace.startsolid) { neworg[2] -= STEPSIZE; trace = SV_Trace (neworg, ent->v->mins, ent->v->maxs, end, false, ent); if (trace.allsolid || trace.startsolid) return false; } if (trace.fraction == 1) { // if monster had the ground pulled out, go ahead and fall if ( (int)ent->v->flags & FL_PARTIALGROUND ) { VectorAdd (ent->v->origin, move, ent->v->origin); if (relink) SV_LinkEdict (ent, true); ent->v->flags = (int)ent->v->flags & ~FL_ONGROUND; // Con_Printf ("fall down\n"); return true; } return false; // walked off an edge } // check point traces down for dangling corners VectorCopy (trace.endpos, ent->v->origin); if (!SV_CheckBottom (ent)) { if ( (int)ent->v->flags & FL_PARTIALGROUND ) { // entity had floor mostly pulled out from underneath it // and is trying to correct if (relink) SV_LinkEdict (ent, true); return true; } VectorCopy (oldorg, ent->v->origin); return false; } if ( (int)ent->v->flags & FL_PARTIALGROUND ) { // Con_Printf ("back on ground\n"); ent->v->flags = (int)ent->v->flags & ~FL_PARTIALGROUND; } ent->v->groundentity = EDICT_TO_PROG(trace.e.ent); // the move is ok if (relink) SV_LinkEdict (ent, true); return true; } //============================================================================ /* ====================== SV_StepDirection Turns to the movement direction, and walks the current distance if facing it. ====================== */ void PF_changeyaw (void); qbool SV_StepDirection (edict_t *ent, float yaw, float dist) { vec3_t move, oldorigin; float delta; ent->v->ideal_yaw = yaw; PF_changeyaw(); // OUCH OUCH: its relay on what ent == self ? I'm not even mention about PR2... yaw = yaw*M_PI*2 / 360; move[0] = cos(yaw)*dist; move[1] = sin(yaw)*dist; move[2] = 0; VectorCopy (ent->v->origin, oldorigin); if (SV_movestep (ent, move, false)) { delta = ent->v->angles[YAW] - ent->v->ideal_yaw; if (delta > 45 && delta < 315) { // not turned far enough, so don't take the step VectorCopy (oldorigin, ent->v->origin); } SV_LinkEdict (ent, true); return true; } SV_LinkEdict (ent, true); return false; } /* ====================== SV_FixCheckBottom ====================== */ void SV_FixCheckBottom (edict_t *ent) { // Con_Printf ("SV_FixCheckBottom\n"); ent->v->flags = (int)ent->v->flags | FL_PARTIALGROUND; } /* ================ SV_NewChaseDir ================ */ #define DI_NODIR -1 void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist) { float deltax,deltay; float d[3]; float tdir, olddir, turnaround; olddir = anglemod( (int)(actor->v->ideal_yaw/45)*45 ); turnaround = anglemod(olddir - 180); deltax = enemy->v->origin[0] - actor->v->origin[0]; deltay = enemy->v->origin[1] - actor->v->origin[1]; if (deltax>10) d[1]= 0; else if (deltax<-10) d[1]= 180; else d[1]= DI_NODIR; if (deltay<-10) d[2]= 270; else if (deltay>10) d[2]= 90; else d[2]= DI_NODIR; // try direct route if (d[1] != DI_NODIR && d[2] != DI_NODIR) { if (d[1] == 0) tdir = d[2] == 90 ? 45 : 315; else tdir = d[2] == 90 ? 135 : 215; if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) return; } // try other directions if ( ((rand()&3) & 1) || fabs(deltay) > fabs(deltax)) { tdir=d[1]; d[1]=d[2]; d[2]=tdir; } if (d[1] != DI_NODIR && d[1] != turnaround && SV_StepDirection(actor, d[1], dist)) return; if (d[2] != DI_NODIR && d[2] != turnaround && SV_StepDirection(actor, d[2], dist)) return; /* there is no direct path to the player, so pick another direction */ if (olddir != DI_NODIR && SV_StepDirection(actor, olddir, dist)) return; if (rand()&1) /*randomly determine direction of search*/ { for (tdir=0 ; tdir<=315 ; tdir += 45) if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) return; } else { for (tdir=315 ; tdir >=0 ; tdir -= 45) if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) return; } if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) ) return; actor->v->ideal_yaw = olddir; // can't move // if a bridge was pulled out from underneath a monster, it may not have // a valid standing position at all if (!SV_CheckBottom (actor)) SV_FixCheckBottom (actor); } /* ====================== SV_CloseEnough ====================== */ qbool SV_CloseEnough (edict_t *ent, edict_t *goal, float dist) { int i; for (i=0 ; i<3 ; i++) { if (goal->v->absmin[i] > ent->v->absmax[i] + dist) return false; if (goal->v->absmax[i] < ent->v->absmin[i] - dist) return false; } return true; } /* ====================== SV_MoveToGoal ====================== */ // NOTE: If you change this, then change PF2_MoveToGoal too!!! void SV_MoveToGoal (void) { edict_t *ent, *goal; float dist; ent = PROG_TO_EDICT(pr_global_struct->self); goal = PROG_TO_EDICT(ent->v->goalentity); dist = G_FLOAT(OFS_PARM0); if ( !( (int)ent->v->flags & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) { G_FLOAT(OFS_RETURN) = 0; return; } // if the next step hits the enemy, return immediately if ( PROG_TO_EDICT(ent->v->enemy) != sv.edicts && SV_CloseEnough (ent, goal, dist) ) return; // bump around... if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->v->ideal_yaw, dist)) { SV_NewChaseDir (ent, goal, dist); } } #endif // !CLIENTONLY mvdsv-0.35/src/sv_nchan.c000066400000000000000000000107331427146041000153350ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // sv_nchan.c, user reliable data stream writes #ifndef CLIENTONLY #include "qwsvdef.h" // check to see if client block will fit, if not, rotate buffers void ClientReliableCheckBlock(client_t *cl, int maxsize) { if (cl->num_backbuf || cl->netchan.message.cursize > cl->netchan.message.maxsize - maxsize - 1) { // we would probably overflow the buffer, save it for next if (!cl->num_backbuf || cl->backbuf.cursize > cl->backbuf.maxsize - maxsize - 1) { if (cl->num_backbuf == MAX_BACK_BUFFERS) { Sys_Printf ("WARNING: MAX_BACK_BUFFERS for %s\n", cl->name); cl->backbuf.cursize = 0; // don't overflow without allowoverflow set cl->netchan.message.overflowed = true; // this will drop the client return; } memset(&cl->backbuf, 0, sizeof(cl->backbuf)); cl->backbuf.allowoverflow = true; cl->backbuf.data = cl->backbuf_data[cl->num_backbuf]; cl->backbuf.maxsize = cl->netchan.message.maxsize; cl->backbuf_size[cl->num_backbuf] = 0; cl->num_backbuf++; } } } // begin a client block, estimated maximum size void ClientReliableWrite_Begin(client_t *cl, int c, int maxsize) { ClientReliableCheckBlock(cl, maxsize); ClientReliableWrite_Byte(cl, c); } void ClientReliable_FinishWrite(client_t *cl) { if (cl->num_backbuf) { cl->backbuf_size[cl->num_backbuf - 1] = cl->backbuf.cursize; if (cl->backbuf.overflowed) { Sys_Printf ("WARNING: backbuf [%d] reliable overflow for %s\n",cl->num_backbuf,cl->name); cl->netchan.message.overflowed = true; // this will drop the client } } } void ClientReliableWrite_Angle(client_t *cl, float f) { if (cl->num_backbuf) { MSG_WriteAngle(&cl->backbuf, f); ClientReliable_FinishWrite(cl); } else MSG_WriteAngle(&cl->netchan.message, f); } void ClientReliableWrite_Angle16(client_t *cl, float f) { if (cl->num_backbuf) { MSG_WriteAngle16(&cl->backbuf, f); ClientReliable_FinishWrite(cl); } else MSG_WriteAngle16(&cl->netchan.message, f); } void ClientReliableWrite_Byte(client_t *cl, int c) { if (cl->num_backbuf) { MSG_WriteByte(&cl->backbuf, c); ClientReliable_FinishWrite(cl); } else MSG_WriteByte(&cl->netchan.message, c); } void ClientReliableWrite_Char(client_t *cl, int c) { if (cl->num_backbuf) { MSG_WriteChar(&cl->backbuf, c); ClientReliable_FinishWrite(cl); } else MSG_WriteChar(&cl->netchan.message, c); } void ClientReliableWrite_Float(client_t *cl, float f) { if (cl->num_backbuf) { MSG_WriteFloat(&cl->backbuf, f); ClientReliable_FinishWrite(cl); } else MSG_WriteFloat(&cl->netchan.message, f); } void ClientReliableWrite_Coord(client_t *cl, float f) { if (cl->num_backbuf) { MSG_WriteCoord(&cl->backbuf, f); ClientReliable_FinishWrite(cl); } else MSG_WriteCoord(&cl->netchan.message, f); } void ClientReliableWrite_Long(client_t *cl, int c) { if (cl->num_backbuf) { MSG_WriteLong(&cl->backbuf, c); ClientReliable_FinishWrite(cl); } else MSG_WriteLong(&cl->netchan.message, c); } void ClientReliableWrite_Short(client_t *cl, int c) { if (cl->num_backbuf) { MSG_WriteShort(&cl->backbuf, c); ClientReliable_FinishWrite(cl); } else MSG_WriteShort(&cl->netchan.message, c); } void ClientReliableWrite_String(client_t *cl, char *s) { if (cl->num_backbuf) { MSG_WriteString(&cl->backbuf, s); ClientReliable_FinishWrite(cl); } else MSG_WriteString(&cl->netchan.message, s); } void ClientReliableWrite_SZ(client_t *cl, void *data, int len) { if (cl->num_backbuf) { SZ_Write(&cl->backbuf, data, len); ClientReliable_FinishWrite(cl); } else SZ_Write(&cl->netchan.message, data, len); } void SV_ClearBackbuf (client_t *cl) { cl->num_backbuf = 0; } // clears both cl->netchan.message and backbuf void SV_ClearReliable (client_t *cl) { SZ_Clear (&cl->netchan.message); SV_ClearBackbuf (cl); } #endif // !CLIENTONLY mvdsv-0.35/src/sv_phys.c000066400000000000000000000634401427146041000152340ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // sv_phys.c #ifndef CLIENTONLY #include "qwsvdef.h" /* pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS corpses are SOLID_NOT and MOVETYPE_TOSS crates are SOLID_BBOX and MOVETYPE_TOSS walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY solid_edge items only clip against bsp models. */ cvar_t sv_maxvelocity = { "sv_maxvelocity","2000"}; cvar_t sv_gravity = { "sv_gravity", "800"}; cvar_t sv_stopspeed = { "sv_stopspeed", "100"}; cvar_t sv_maxspeed = { "sv_maxspeed", "320"}; cvar_t sv_spectatormaxspeed = { "sv_spectatormaxspeed", "500"}; cvar_t sv_accelerate = { "sv_accelerate", "10"}; cvar_t sv_airaccelerate = { "sv_airaccelerate", "10"}; cvar_t sv_antilag = { "sv_antilag", "", CVAR_SERVERINFO}; cvar_t sv_antilag_no_pred = { "sv_antilag_no_pred", "", CVAR_SERVERINFO}; // "negative" cvar so it doesn't show on serverinfo for no reason cvar_t sv_antilag_projectiles = { "sv_antilag_projectiles", "", CVAR_SERVERINFO}; cvar_t sv_wateraccelerate = { "sv_wateraccelerate", "10"}; cvar_t sv_friction = { "sv_friction", "4"}; cvar_t sv_waterfriction = { "sv_waterfriction", "4"}; cvar_t pm_ktjump = { "pm_ktjump", "1", CVAR_SERVERINFO}; cvar_t pm_bunnyspeedcap = { "pm_bunnyspeedcap", "", CVAR_SERVERINFO}; cvar_t pm_slidefix = { "pm_slidefix", "", CVAR_SERVERINFO}; void OnChange_pm_airstep (cvar_t *var, char *value, qbool *cancel); cvar_t pm_airstep = { "pm_airstep", "", CVAR_SERVERINFO, OnChange_pm_airstep}; cvar_t pm_pground = { "pm_pground", "", CVAR_SERVERINFO|CVAR_ROM}; cvar_t pm_rampjump = { "pm_rampjump", "", CVAR_SERVERINFO }; double sv_frametime; // when pm_airstep is 1, set pm_pground to 1, and vice versa // airstep works best with pground on void OnChange_pm_airstep (cvar_t *var, char *value, qbool *cancel) { float val = Q_atoi(value); Cvar_SetROM (&pm_pground, val ? "1" : ""); } void SV_Physics_Toss (edict_t *ent); /* ================ SV_CheckVelocity ================ */ void SV_CheckVelocity (edict_t *ent) { int i; float wishspeed; // // bound velocity // for (i=0 ; i<3 ; i++) { if (IS_NAN(ent->v->velocity[i])) { Con_DPrintf ("Got a NaN velocity on %s\n", PR_GetEntityString(ent->v->classname)); ent->v->velocity[i] = 0; } if (IS_NAN(ent->v->origin[i])) { Con_DPrintf ("Got a NaN origin on %s\n", PR_GetEntityString(ent->v->classname)); ent->v->origin[i] = 0; } /* if (ent->v->velocity[i] > sv_maxvelocity.value) ent->v->velocity[i] = sv_maxvelocity.value; else if (ent->v->velocity[i] < -sv_maxvelocity.value) ent->v->velocity[i] = -sv_maxvelocity.value; */ } // SV_MAXVELOCITY fix by Maddes wishspeed = VectorLength(ent->v->velocity); if (wishspeed > sv_maxvelocity.value) { VectorScale (ent->v->velocity, sv_maxvelocity.value/wishspeed, ent->v->velocity); wishspeed = sv_maxvelocity.value; } } /* ============= SV_RunThink Runs thinking code if time. There is some play in the exact time the think function will be called, because it is called before any movement is done in a frame. Not used for pushmove objects, because they must be exact. Returns false if the entity removed itself. ============= */ qbool SV_RunThink (edict_t *ent) { float thinktime; do { thinktime = ent->v->nextthink; if (thinktime <= 0) return true; if (thinktime > sv.time + sv_frametime) return true; if (thinktime < sv.time) thinktime = sv.time; // don't let things stay in the past. // it is possible to start that way // by a trigger with a local time. ent->v->nextthink = 0; pr_global_struct->time = thinktime; pr_global_struct->self = EDICT_TO_PROG(ent); pr_global_struct->other = EDICT_TO_PROG(sv.edicts); PR_EdictThink(ent->v->think); if (ent->e.free) return false; } while (1); return true; } /* ================== SV_Impact Two entities have touched, so run their touch functions ================== */ void SV_Impact (edict_t *e1, edict_t *e2) { int old_self, old_other; old_self = pr_global_struct->self; old_other = pr_global_struct->other; pr_global_struct->time = sv.time; if (e1->v->touch && e1->v->solid != SOLID_NOT) { pr_global_struct->self = EDICT_TO_PROG(e1); pr_global_struct->other = EDICT_TO_PROG(e2); PR_EdictTouch(e1->v->touch); } if (e2->v->touch && e2->v->solid != SOLID_NOT) { pr_global_struct->self = EDICT_TO_PROG(e2); pr_global_struct->other = EDICT_TO_PROG(e1); PR_EdictTouch(e2->v->touch); } pr_global_struct->self = old_self; pr_global_struct->other = old_other; } /* ================== ClipVelocity Slide off of the impacting object ================== */ #define STOP_EPSILON 0.1 void ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) { float backoff; float change; int i; backoff = DotProduct (in, normal) * overbounce; for (i=0 ; i<3 ; i++) { change = normal[i]*backoff; out[i] = in[i] - change; if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) out[i] = 0; } } /* ============ SV_FlyMove The basic solid body movement clip that slides along multiple planes Returns the clipflags if the velocity was modified (hit something solid) 1 = floor 2 = wall / step 4 = dead stop If steptrace is not NULL, the trace of any vertical wall hit will be stored ============ */ #define MAX_CLIP_PLANES 5 int SV_FlyMove (edict_t *ent, float time1, trace_t *steptrace, int type) { int bumpcount, numbumps; vec3_t dir; float d; int numplanes; vec3_t planes[MAX_CLIP_PLANES]; vec3_t primal_velocity, original_velocity, new_velocity; int i, j; trace_t trace; vec3_t end; float time_left; int blocked; numbumps = 4; blocked = 0; VectorCopy (ent->v->velocity, original_velocity); VectorCopy (ent->v->velocity, primal_velocity); numplanes = 0; time_left = time1; for (bumpcount=0 ; bumpcountv->origin[i] + time_left * ent->v->velocity[i]; trace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, end, type, ent); if (trace.allsolid) { // entity is trapped in another solid VectorClear (ent->v->velocity); return 3; } if (trace.fraction > 0) { // actually covered some distance VectorCopy (trace.endpos, ent->v->origin); VectorCopy (ent->v->velocity, original_velocity); numplanes = 0; } if (trace.fraction == 1) break; // moved the entire distance if (!trace.e.ent) SV_Error ("SV_FlyMove: !trace.e.ent"); if (trace.plane.normal[2] > 0.7) { blocked |= 1; // floor if (trace.e.ent->v->solid == SOLID_BSP) { ent->v->flags = (int)ent->v->flags | FL_ONGROUND; ent->v->groundentity = EDICT_TO_PROG(trace.e.ent); } } if (!trace.plane.normal[2]) { blocked |= 2; // step if (steptrace) *steptrace = trace; // save for player extrafriction } // // run the impact function // SV_Impact (ent, trace.e.ent); if (ent->e.free) break; // removed by the impact function time_left -= time_left * trace.fraction; // cliped to another plane if (numplanes >= MAX_CLIP_PLANES) { // this shouldn't really happen VectorClear (ent->v->velocity); return 3; } VectorCopy (trace.plane.normal, planes[numplanes]); numplanes++; // // modify original_velocity so it parallels all of the clip planes // for (i=0 ; iv->velocity); } else { // go along the crease if (numplanes != 2) { // Con_Printf ("clip velocity, numplanes == %i\n",numplanes); VectorClear (ent->v->velocity); return 7; } CrossProduct (planes[0], planes[1], dir); d = DotProduct (dir, ent->v->velocity); VectorScale (dir, d, ent->v->velocity); } // // if original velocity is against the original velocity, stop dead // to avoid tiny occilations in sloping corners // if (DotProduct (ent->v->velocity, primal_velocity) <= 0) { VectorClear (ent->v->velocity); return blocked; } } return blocked; } /* ============ SV_AddGravity ============ */ void SV_AddGravity (edict_t *ent, float scale) { ent->v->velocity[2] -= scale * movevars.gravity * sv_frametime; } /* =============================================================================== PUSHMOVE =============================================================================== */ /* ============ SV_PushEntity Does not change the entities velocity at all ============ */ trace_t SV_PushEntity (edict_t *ent, vec3_t push, unsigned int traceflags) { trace_t trace; vec3_t end; VectorAdd (ent->v->origin, push, end); if ((int)ent->v->flags&FL_LAGGEDMOVE) traceflags |= MOVE_LAGGED; if (ent->v->movetype == MOVETYPE_FLYMISSILE) trace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, end, MOVE_MISSILE|traceflags, ent); else if (ent->v->solid == SOLID_TRIGGER || ent->v->solid == SOLID_NOT) // only clip against bmodels trace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, end, MOVE_NOMONSTERS|traceflags, ent); else trace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, end, MOVE_NORMAL|traceflags, ent); VectorCopy (trace.endpos, ent->v->origin); SV_LinkEdict (ent, true); if (trace.e.ent) SV_Impact (ent, trace.e.ent); return trace; } /* ============ SV_Push ============ */ qbool SV_Push (edict_t *pusher, vec3_t move) { int i, e; edict_t *check, *block; vec3_t mins, maxs; vec3_t pushorig; int num_moved; edict_t *moved_edict[MAX_EDICTS]; vec3_t moved_from[MAX_EDICTS]; float solid_save; for (i=0 ; i<3 ; i++) { mins[i] = pusher->v->absmin[i] + move[i]; maxs[i] = pusher->v->absmax[i] + move[i]; } VectorCopy (pusher->v->origin, pushorig); // move the pusher to its final position VectorAdd (pusher->v->origin, move, pusher->v->origin); SV_LinkEdict (pusher, false); // see if any solid entities are inside the final position num_moved = 0; check = NEXT_EDICT(sv.edicts); for (e=1 ; ee.free) continue; if (check->v->movetype == MOVETYPE_PUSH || check->v->movetype == MOVETYPE_NONE || check->v->movetype == MOVETYPE_NOCLIP) continue; solid_save = pusher->v->solid; pusher->v->solid = SOLID_NOT; block = SV_TestEntityPosition (check); pusher->v->solid = solid_save; if (block) continue; // if the entity is standing on the pusher, it will definately be moved if ( ! ( ((int)check->v->flags & FL_ONGROUND) && PROG_TO_EDICT(check->v->groundentity) == pusher) ) { if ( check->v->absmin[0] >= maxs[0] || check->v->absmin[1] >= maxs[1] || check->v->absmin[2] >= maxs[2] || check->v->absmax[0] <= mins[0] || check->v->absmax[1] <= mins[1] || check->v->absmax[2] <= mins[2] ) continue; // see if the ent's bbox is inside the pusher's final position if (!SV_TestEntityPosition (check)) continue; } // remove the onground flag for non-players if (check->v->movetype != MOVETYPE_WALK) check->v->flags = (int)check->v->flags & ~FL_ONGROUND; VectorCopy (check->v->origin, moved_from[num_moved]); moved_edict[num_moved] = check; num_moved++; // try moving the contacted entity VectorAdd (check->v->origin, move, check->v->origin); block = SV_TestEntityPosition (check); if (!block) { // pushed ok SV_LinkEdict (check, false); continue; } // if it is ok to leave in the old position, do it VectorSubtract (check->v->origin, move, check->v->origin); block = SV_TestEntityPosition (check); if (!block) { //if leaving it where it was, allow it to drop to the floor again (useful for plats that move downward) //check->v->flags = (int)check->v->flags & ~FL_ONGROUND; // disconnect: is it needed? num_moved--; continue; } // if it is still inside the pusher, block if (check->v->mins[0] == check->v->maxs[0]) { SV_LinkEdict (check, false); continue; } if (check->v->solid == SOLID_NOT || check->v->solid == SOLID_TRIGGER) { // corpse check->v->mins[0] = check->v->mins[1] = 0; VectorCopy (check->v->mins, check->v->maxs); SV_LinkEdict (check, false); continue; } VectorCopy (pushorig, pusher->v->origin); SV_LinkEdict (pusher, false); // if the pusher has a "blocked" function, call it // otherwise, just stay in place until the obstacle is gone if (pusher->v->blocked) { pr_global_struct->self = EDICT_TO_PROG(pusher); pr_global_struct->other = EDICT_TO_PROG(check); PR_EdictBlocked (pusher->v->blocked); } // move back any entities we already moved for (i=0 ; iv->origin); SV_LinkEdict (moved_edict[i], false); } return false; } return true; } /* ============ SV_PushMove ============ */ void SV_PushMove (edict_t *pusher, float movetime) { int i; vec3_t move; if (!pusher->v->velocity[0] && !pusher->v->velocity[1] && !pusher->v->velocity[2]) { pusher->v->ltime += movetime; return; } for (i=0 ; i<3 ; i++) move[i] = pusher->v->velocity[i] * movetime; if (SV_Push (pusher, move)) pusher->v->ltime += movetime; } /* ================ SV_Physics_Pusher ================ */ void SV_Physics_Pusher (edict_t *ent) { float thinktime; float oldltime; float movetime; float l; vec3_t oldorg, move; oldltime = ent->v->ltime; thinktime = ent->v->nextthink; if (thinktime < ent->v->ltime + sv_frametime) { movetime = thinktime - ent->v->ltime; if (movetime < 0) movetime = 0; } else movetime = sv_frametime; if (movetime) { SV_PushMove (ent, movetime); // advances ent->v->ltime if not blocked } if (thinktime > oldltime && thinktime <= ent->v->ltime) { VectorCopy (ent->v->origin, oldorg); ent->v->nextthink = 0; pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(ent); pr_global_struct->other = EDICT_TO_PROG(sv.edicts); PR_EdictThink(ent->v->think); if (ent->e.free) return; VectorSubtract (ent->v->origin, oldorg, move); l = VectorLength(move); if (l > 1.0/64) { // Con_Printf ("**** snap: %f\n", VectorLength (l)); VectorCopy (oldorg, ent->v->origin); SV_Push (ent, move); } } } /* ============= SV_Physics_None Non moving objects can only think ============= */ void SV_Physics_None (edict_t *ent) { // regular thinking SV_RunThink (ent); } /* ============= SV_Physics_Noclip A moving object that doesn't obey physics ============= */ void SV_Physics_Noclip (edict_t *ent) { // regular thinking if (!SV_RunThink (ent)) return; VectorMA (ent->v->angles, sv_frametime, ent->v->avelocity, ent->v->angles); VectorMA (ent->v->origin, sv_frametime, ent->v->velocity, ent->v->origin); SV_LinkEdict (ent, false); } /* ============================================================================== TOSS / BOUNCE ============================================================================== */ /* ============= SV_CheckWaterTransition ============= */ void SV_CheckWaterTransition (edict_t *ent) { int cont; cont = SV_PointContents (ent->v->origin); if (!ent->v->watertype) { // just spawned here ent->v->watertype = cont; ent->v->waterlevel = 1; return; } if (cont <= CONTENTS_WATER) { if (ent->v->watertype == CONTENTS_EMPTY) { // just crossed into water SV_StartSound (ent, 0, "misc/h2ohit1.wav", 255, 1); } ent->v->watertype = cont; ent->v->waterlevel = 1; } else { if (ent->v->watertype != CONTENTS_EMPTY) { // just crossed into water SV_StartSound (ent, 0, "misc/h2ohit1.wav", 255, 1); } ent->v->watertype = CONTENTS_EMPTY; ent->v->waterlevel = cont; } } /* ============= SV_Physics_Toss Toss, bounce, and fly movement. When onground, do nothing. ============= */ void SV_Physics_Toss (edict_t *ent) { trace_t trace; vec3_t move; float backoff; // regular thinking if (!SV_RunThink (ent)) return; if (ent->v->velocity[2] > 0) ent->v->flags = (int)ent->v->flags & ~FL_ONGROUND; // if onground, return without moving if ( ((int)ent->v->flags & FL_ONGROUND) ) return; SV_CheckVelocity (ent); // add gravity if (ent->v->movetype != MOVETYPE_FLY && ent->v->movetype != MOVETYPE_FLYMISSILE) SV_AddGravity (ent, 1.0); // move angles VectorMA (ent->v->angles, sv_frametime, ent->v->avelocity, ent->v->angles); // move origin VectorScale (ent->v->velocity, sv_frametime, move); trace = SV_PushEntity (ent, move, (sv_antilag.value == 2 && sv_antilag_projectiles.value) ? MOVE_LAGGED:0); if (trace.fraction == 1) return; if (ent->e.free) return; if (ent->v->movetype == MOVETYPE_BOUNCE) backoff = 1.5; else backoff = 1; ClipVelocity (ent->v->velocity, trace.plane.normal, ent->v->velocity, backoff); // stop if on ground if (trace.plane.normal[2] > 0.7) { if (ent->v->velocity[2] < 60 || ent->v->movetype != MOVETYPE_BOUNCE ) { ent->v->flags = (int)ent->v->flags | FL_ONGROUND; ent->v->groundentity = EDICT_TO_PROG(trace.e.ent); VectorClear (ent->v->velocity); VectorClear (ent->v->avelocity); } } // check for in water SV_CheckWaterTransition (ent); } /* =============================================================================== STEPPING MOVEMENT =============================================================================== */ /* ============= SV_Physics_Step Monsters freefall when they don't have a ground entity, otherwise all movement is done with discrete steps. This is also used for objects that have become still on the ground, but will fall if the floor is pulled out from under them. FIXME: is this true? ============= */ void SV_Physics_Step (edict_t *ent) { qbool hitsound; // frefall if not onground if ( ! ((int)ent->v->flags & (FL_ONGROUND | FL_FLY | FL_SWIM) ) ) { if (ent->v->velocity[2] < movevars.gravity*-0.1) hitsound = true; else hitsound = false; SV_AddGravity (ent, 1.0); SV_CheckVelocity (ent); // Tonik: the check for SOLID_NOT is to fix the way dead bodies and // gibs behave (should not be blocked by players & monsters); // The SOLID_TRIGGER check is disabled lest we break frikbots if (ent->v->solid == SOLID_NOT /* || ent->v->solid == SOLID_TRIGGER*/) SV_FlyMove (ent, sv_frametime, NULL, MOVE_NOMONSTERS); else SV_FlyMove (ent, sv_frametime, NULL, MOVE_NORMAL); SV_LinkEdict (ent, true); if ( (int)ent->v->flags & FL_ONGROUND ) // just hit ground { if (hitsound) SV_StartSound (ent, 0, "demon/dland2.wav", 255, 1); } } // regular thinking SV_RunThink (ent); SV_CheckWaterTransition (ent); } //============================================================================ void SV_ProgStartFrame (qbool isBotFrame) { // let the progs know that a new frame has started pr_global_struct->self = EDICT_TO_PROG(sv.edicts); pr_global_struct->other = EDICT_TO_PROG(sv.edicts); pr_global_struct->time = sv.time; PR_GameStartFrame(isBotFrame); } /* ================ SV_RunEntity ================ */ void SV_RunEntity (edict_t *ent) { if (ent->e.lastruntime == sv.time) return; ent->e.lastruntime = sv.time; switch ((int)ent->v->movetype) { case MOVETYPE_PUSH: SV_Physics_Pusher (ent); break; case MOVETYPE_NONE: case MOVETYPE_LOCK: SV_Physics_None (ent); break; case MOVETYPE_NOCLIP: SV_Physics_Noclip (ent); break; case MOVETYPE_STEP: SV_Physics_Step (ent); break; case MOVETYPE_TOSS: case MOVETYPE_BOUNCE: case MOVETYPE_FLY: case MOVETYPE_FLYMISSILE: SV_Physics_Toss (ent); break; default: SV_Error ("SV_Physics: bad movetype %i", (int)ent->v->movetype); } } /* ** SV_RunNQNewmis ** ** sv_player will be valid */ void SV_RunNQNewmis (void) { edict_t *ent; double save_frametime; int i, pl; pl = EDICT_TO_PROG(sv_player); ent = NEXT_EDICT(sv.edicts); for (i=1 ; ie.free) continue; if (ent->e.lastruntime || ent->v->owner != pl) continue; if (ent->v->movetype != MOVETYPE_FLY && ent->v->movetype != MOVETYPE_FLYMISSILE && ent->v->movetype != MOVETYPE_BOUNCE) continue; if (ent->v->solid != SOLID_BBOX && ent->v->solid != SOLID_TRIGGER) continue; save_frametime = sv_frametime; sv_frametime = 0.05; SV_RunEntity (ent); sv_frametime = save_frametime; return; } } /* ================ SV_RunNewmis ================ */ void SV_RunNewmis (void) { edict_t *ent; double save_frametime; if (pr_nqprogs) return; if (!pr_global_struct->newmis) return; ent = PROG_TO_EDICT(pr_global_struct->newmis); pr_global_struct->newmis = 0; save_frametime = sv_frametime; sv_frametime = 0.05; SV_RunEntity (ent); sv_frametime = save_frametime; } /* ================ SV_Physics ================ */ void SV_Physics (void) { int i; client_t *cl,*savehc; edict_t *savesvpl; edict_t *ent; if (sv.state != ss_active) return; if (sv.old_time) { // don't bother running a frame if sv_mintic seconds haven't passed sv_frametime = sv.time - sv.old_time; if (sv_frametime < (double) sv_mintic.value) return; if (sv_frametime > (double) sv_maxtic.value) sv_frametime = (double) sv_maxtic.value; sv.old_time = sv.time; } else sv_frametime = 0.1; // initialization frame sv.physicstime = sv.time; if (pr_nqprogs) NQP_Reset (); PR_GLOBAL(frametime) = sv_frametime; SV_ProgStartFrame(false); // // treat each object in turn // even the world gets a chance to think // ent = sv.edicts; for (i=0 ; ie.free) continue; if (PR_GLOBAL(force_retouch)) SV_LinkEdict (ent, true); // force retouch even for stationary if (i > 0 && i <= MAX_CLIENTS) continue; // clients are run directly from packets SV_RunEntity (ent); SV_RunNewmis (); } if (PR_GLOBAL(force_retouch)) PR_GLOBAL(force_retouch)--; savesvpl = sv_player; savehc = sv_client; // so spec will have right goalentity - if speccing someone for ( i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++ ) { if ( cl->state == cs_free ) continue; sv_client = cl; sv_player = cl->edict; if (sv_client->spectator && sv_client->spec_track > 0) sv_player->v->goalentity = EDICT_TO_PROG(svs.clients[sv_client->spec_track-1].edict); } sv_player = savesvpl; sv_client = savehc; } #ifdef USE_PR2 void SV_RunBots(void) { int i; client_t *cl,*savehc; edict_t *savesvpl; double max_physfps = sv_maxfps.value; #ifdef SERVERONLY static double extramsec = 0; #endif if (max_physfps < 20 || max_physfps > 1000) { max_physfps = 77.0; } if (sv.state != ss_active || !sv.physicstime) return; #ifdef SERVERONLY if (sv.old_bot_time) { // don't bother running a frame if 1/fps seconds haven't passed double required = (double) 1.0f / max_physfps; extramsec += (sv.time - sv.old_bot_time); sv.old_bot_time = sv.time; if (extramsec < required) { return; } sv_frametime = required; extramsec -= required; } else { sv_frametime = 1.0f / max_physfps; // initialization frame extramsec = 0; sv.old_bot_time = sv.time; } #else // On internal server, try and match the user's framerate // ... don't run if no time passed, that is a user packet only if (sv.old_bot_time && sv.old_bot_time == sv.time) { return; } sv.old_bot_time = sv.time; #endif savesvpl = sv_player; savehc = sv_client; PR_GLOBAL(frametime) = sv_frametime; SV_ProgStartFrame (true); // // Run bots physics. // for ( i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++ ) { extern void SV_PreRunCmd(void); extern void SV_RunCmd (usercmd_t *ucmd, qbool inside, qbool simulate); extern void SV_PostRunCmd(void); if ( cl->state == cs_free ) continue; if ( !cl->isBot ) continue; sv_client = cl; sv_player = cl->edict; SV_PreRunCmd(); SV_RunCmd (&cl->botcmd, false, false); SV_PostRunCmd(); cl->lastcmd = cl->botcmd; cl->lastcmd.buttons = 0; memset(&cl->botcmd,0,sizeof(cl->botcmd)); cl->localtime = sv.time; cl->delta_sequence = -1; // no delta unless requested if (sv_antilag.value) { if (cl->antilag_position_next == 0 || cl->antilag_positions[(cl->antilag_position_next - 1) % MAX_ANTILAG_POSITIONS].localtime < cl->localtime) { cl->antilag_positions[cl->antilag_position_next % MAX_ANTILAG_POSITIONS].localtime = cl->localtime; VectorCopy(cl->edict->v->origin, cl->antilag_positions[cl->antilag_position_next % MAX_ANTILAG_POSITIONS].origin); cl->antilag_position_next++; } } else { cl->antilag_position_next = 0; } } sv_player = savesvpl; sv_client = savehc; } #endif void SV_SetMoveVars(void) { movevars.gravity = sv_gravity.value; movevars.stopspeed = sv_stopspeed.value; movevars.maxspeed = sv_maxspeed.value; movevars.spectatormaxspeed = sv_spectatormaxspeed.value; movevars.accelerate = sv_accelerate.value; movevars.airaccelerate = sv_airaccelerate.value; movevars.wateraccelerate = sv_wateraccelerate.value; movevars.friction = sv_friction.value; movevars.waterfriction = sv_waterfriction.value; movevars.entgravity = 1.0; } #endif // !CLIENTONLY mvdsv-0.35/src/sv_save.c000066400000000000000000000165011427146041000152030ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // sv_save.c #ifndef CLIENTONLY #ifdef SERVERONLY #include "qwsvdef.h" #else #include "quakedef.h" #include "vfs.h" #include "server.h" #include "sv_world.h" #endif extern cvar_t maxclients; #define SAVEGAME_COMMENT_LENGTH 39 #define SAVEGAME_VERSION 6 static void SV_SaveGameFileName(char* buffer, int buffer_size, char* name) { #ifdef SERVERONLY snprintf (buffer, buffer_size, "%s/save/%s", fs_gamedir, name); #else snprintf (buffer, buffer_size, "%s/save/%s", com_gamedir, name); #endif } //Writes a SAVEGAME_COMMENT_LENGTH character comment void SV_SavegameComment (char *buffer) { int i; char kills[20]; #ifdef SERVERONLY char *mapname = sv.mapname; int killed_monsters = (int)PR_GLOBAL(killed_monsters); int total_monsters = (int)PR_GLOBAL(total_monsters); #else char *mapname = cl.levelname; int killed_monsters = cl.stats[STAT_MONSTERS]; int total_monsters = cl.stats[STAT_TOTALMONSTERS]; #endif if (!mapname || !*mapname) mapname = "Unnamed_Level"; for (i = 0; i < SAVEGAME_COMMENT_LENGTH; i++) buffer[i] = ' '; memcpy (buffer, mapname, min(strlen(mapname), 21)); snprintf (kills, sizeof (kills), "kills:%3i/%-3i", killed_monsters, total_monsters); memcpy (buffer + 22, kills, strlen(kills)); // convert space to _ to make stdio happy for (i = 0; i < SAVEGAME_COMMENT_LENGTH; i++) if (buffer[i] == ' ') buffer[i] = '_'; buffer[SAVEGAME_COMMENT_LENGTH] = 0; } void SV_SaveGame_f(void) { char fname[MAX_OSPATH], comment[SAVEGAME_COMMENT_LENGTH+1]; FILE *f; int i; if (Cmd_Argc() != 2) { Con_Printf ("Usage: %s : save a game\n", Cmd_Argv(0)); return; } else if (strstr(Cmd_Argv(1), "..")) { Con_Printf ("Relative pathfnames are not allowed.\n"); return; } else if (sv.state != ss_active) { Con_Printf ("Not playing a local game.\n"); return; #ifndef SERVERONLY } else if (cl.intermission) { Con_Printf ("Can't save in intermission.\n"); return; #endif } else if (deathmatch.value != 0 || coop.value != 0 || maxclients.value != 1) { Con_Printf ("Can't save multiplayer games.\n"); return; } for (i = 1; i < MAX_CLIENTS; i++) { if (svs.clients[i].state == cs_spawned) { Con_Printf ("Can't save multiplayer games.\n"); return; } } if (svs.clients[0].state != cs_spawned) { Con_Printf ("Can't save, client #0 not spawned.\n"); return; } else if (svs.clients[0].edict->v->health <= 0) { Con_Printf ("Can't save game with a dead player\n"); // in fact, we can, but does it make sense? return; } SV_SaveGameFileName (fname, sizeof(fname), Cmd_Argv(1)); COM_DefaultExtension (fname, ".sav"); Con_Printf ("Saving game to %s...\n", fname); if (!(f = fopen (fname, "w"))) { FS_CreatePath (fname); if (!(f = fopen (fname, "w"))) { Con_Printf ("ERROR: couldn't open.\n"); return; } } fprintf (f, "%i\n", SAVEGAME_VERSION); SV_SavegameComment (comment); fprintf (f, "%s\n", comment); for (i = 0 ; i < NUM_SPAWN_PARMS; i++) fprintf (f, "%f\n", svs.clients->spawn_parms[i]); fprintf (f, "%d\n", current_skill); fprintf (f, "%s\n", sv.mapname); fprintf (f, "%f\n", sv.time); // write the light styles for (i = 0; i < MAX_LIGHTSTYLES; i++) { if (sv.lightstyles[i]) fprintf (f, "%s\n", sv.lightstyles[i]); else fprintf (f,"m\n"); } ED_WriteGlobals (f); for (i = 0; i < sv.num_edicts; i++) { ED_Write (f, EDICT_NUM(i)); fflush (f); } fclose (f); Con_Printf ("done.\n"); // force cache rebuild. FS_FlushFSHash(); } void SV_LoadGame_f(void) { char name[MAX_OSPATH], mapname[MAX_QPATH], str[32 * 1024]; const char* start; FILE *f; float time, tfloat, spawn_parms[NUM_SPAWN_PARMS]; edict_t *ent; int entnum, version, r; unsigned int i; if (Cmd_Argc() != 2) { Con_Printf ("Usage: %s : load a game\n", Cmd_Argv(0)); return; } SV_SaveGameFileName (name, sizeof(name), Cmd_Argv(1)); COM_DefaultExtension (name, ".sav"); Con_Printf ("Loading game from %s...\n", name); if (!(f = fopen (name, "rb"))) { Con_Printf ("ERROR: couldn't open.\n"); return; } if (fscanf (f, "%i\n", &version) != 1) { fclose (f); Con_Printf ("Error reading savegame data\n"); return; } if (version != SAVEGAME_VERSION) { fclose (f); Con_Printf ("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION); return; } if (fscanf (f, "%s\n", str) != 1) { fclose (f); Con_Printf ("Error reading savegame data\n"); return; } for (i = 0; i < NUM_SPAWN_PARMS; i++) { if (fscanf (f, "%f\n", &spawn_parms[i]) != 1) { fclose (f); Con_Printf ("Error reading savegame data\n"); return; } } // this silliness is so we can load 1.06 save files, which have float skill values if (fscanf (f, "%f\n", &tfloat) != 1) { fclose (f); Con_Printf ("Error reading savegame data\n"); return; } current_skill = (int)(tfloat + 0.1); if (fscanf (f, "%s\n", mapname) != 1) { fclose (f); Con_Printf ("Error reading savegame data\n"); return; } if (fscanf (f, "%f\n", &time) != 1) { fclose (f); Con_Printf ("Error reading savegame data\n"); return; } #ifndef SERVERONLY Host_EndGame(); CL_BeginLocalConnection (); #endif SV_SpawnServer(mapname, false, NULL, true); if (sv.state != ss_active) { Con_Printf ("Couldn't load map\n"); fclose (f); return; } // load the light styles for (i = 0; i < MAX_LIGHTSTYLES; i++) { int length; if (fscanf (f, "%s\n", str) != 1) { Con_Printf("Couldn't read lightstyles\n"); fclose (f); return; } str[sizeof(str) - 1] = '\0'; length = strlen(str) + 1; sv.lightstyles[i] = (char *) Hunk_Alloc (length); strlcpy (sv.lightstyles[i], str, length); } // pause until all clients connect if (!(sv.paused & 1)) SV_TogglePause (NULL, 1); sv.loadgame = true; // load the edicts out of the savegame file entnum = -1; // -1 is the globals while (!feof(f)) { for (i = 0; i < sizeof(str) - 1; i++) { r = fgetc (f); if (r == EOF || !r) break; str[i] = r; if (r == '}') { i++; break; } } if (i == sizeof(str)-1) Host_Error ("Loadgame buffer overflow"); str[i] = 0; start = str; start = COM_Parse(str); if (!com_token[0]) break; // end of file if (strcmp(com_token,"{")) Host_Error ("First token isn't a brace"); if (entnum == -1) { // parse the global vars ED_ParseGlobals (start); } else { // parse an edict ent = EDICT_NUM(entnum); ED_ClearEdict (ent); // FIXME: we also clear world edict here, is it OK? ED_ParseEdict (start, ent); // link it into the bsp tree if (!ent->e.free) SV_LinkEdict (ent, false); } entnum++; } sv.num_edicts = entnum; sv.time = time; fclose (f); for (i = 0; i < NUM_SPAWN_PARMS; i++) svs.clients->spawn_parms[i] = spawn_parms[i]; } #endif // !CLIENTONLY mvdsv-0.35/src/sv_send.c000066400000000000000000001046531427146041000152040ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef CLIENTONLY #include "qwsvdef.h" static void SV_BotWriteDamage(client_t* c, int i); #define CHAN_AUTO 0 #define CHAN_WEAPON 1 #define CHAN_VOICE 2 #define CHAN_ITEM 3 #define CHAN_BODY 4 /* ============================================================================= Con_Printf redirection ============================================================================= */ char outputbuf[OUTPUTBUF_SIZE]; redirect_t sv_redirected; static int sv_redirectbufcount; qbool SV_SkipCommsBotMessage(client_t* client); extern cvar_t sv_phs, sv_reliable_sound; /* ================== SV_FlushRedirect ================== */ void SV_FlushRedirect (void) { char send1[OUTPUTBUF_SIZE + 6]; if (sv_redirected == RD_PACKET) { send1[0] = 0xff; send1[1] = 0xff; send1[2] = 0xff; send1[3] = 0xff; send1[4] = A2C_PRINT; memcpy (send1 + 5, outputbuf, strlen(outputbuf) + 1); NET_SendPacket (NS_SERVER, strlen(send1) + 1, send1, net_from); } else if (sv_redirected == RD_CLIENT && sv_redirectbufcount < MAX_REDIRECTMESSAGES) { ClientReliableWrite_Begin (sv_client, svc_print, strlen(outputbuf)+3); ClientReliableWrite_Byte (sv_client, PRINT_HIGH); ClientReliableWrite_String (sv_client, outputbuf); sv_redirectbufcount++; } else if (sv_redirected == RD_MOD) { //return; } else if (sv_redirected > RD_MOD && sv_redirectbufcount < MAX_REDIRECTMESSAGES) { client_t *cl; cl = svs.clients + sv_redirected - RD_MOD - 1; if (cl->state == cs_spawned) { ClientReliableWrite_Begin (cl, svc_print, strlen(outputbuf)+3); ClientReliableWrite_Byte (cl, PRINT_HIGH); ClientReliableWrite_String (cl, outputbuf); sv_redirectbufcount++; } } // clear it outputbuf[0] = 0; } /* ================== SV_BeginRedirect Send Con_Printf data to the remote client instead of the console ================== */ void SV_BeginRedirect (redirect_t rd) { sv_redirected = rd; outputbuf[0] = 0; sv_redirectbufcount = 0; } void SV_EndRedirect (void) { SV_FlushRedirect (); sv_redirected = RD_NONE; } qbool SV_AddToRedirect(char *msg) { if (!sv_redirected) return false; // FIXME: probably we should check client's MTU instead of fixed MIN_MTU. if (strlen(msg) + strlen(outputbuf) > /* MAX_MSGLEN */ MIN_MTU - 10) SV_FlushRedirect (); strlcat(outputbuf, msg, sizeof(outputbuf)); return true; } #ifdef SERVERONLY /* ================ Con_Printf Handles cursor positioning, line wrapping, etc ================ */ #define MAXPRINTMSG 4096 void Con_Printf (char *fmt, ...) { va_list argptr; char msg[MAXPRINTMSG]; va_start (argptr,fmt); vsnprintf (msg, MAXPRINTMSG, fmt, argptr); va_end (argptr); // add to redirected message if (SV_AddToRedirect(msg)) return; // added. Sys_Printf ("%s", msg); // also echo to debugging console SV_Write_Log(CONSOLE_LOG, 0, msg); // dumb error message to log file if if (sv_error) SV_Write_Log(ERROR_LOG, 1, msg); } /* ================ Con_DPrintf A Con_Printf that only shows up if the "developer" cvar is set ================ */ void Con_DPrintf (char *fmt, ...) { va_list argptr; char msg[MAXPRINTMSG]; if (!(int)developer.value) return; va_start (argptr,fmt); vsnprintf (msg, MAXPRINTMSG, fmt, argptr); va_end (argptr); Con_Printf ("%s", msg); } #endif // SERVERONLY /* ============================================================================= EVENT MESSAGES ============================================================================= */ static void SV_PrintToClient(client_t *cl, int level, char *string) { if (cl->state < cs_preconnected) { Sys_Printf("SV_PrintToClient: client not ready."); return; } ClientReliableWrite_Begin (cl, svc_print, strlen(string)+3); ClientReliableWrite_Byte (cl, level); ClientReliableWrite_String (cl, string); } /* ================= SV_ClientPrintf Sends text across to be displayed if the level passes ================= */ void SV_ClientPrintf (client_t *cl, int level, char *fmt, ...) { va_list argptr; char string[1024]; if (level < cl->messagelevel) return; va_start (argptr,fmt); vsnprintf (string, sizeof(string), fmt, argptr); va_end (argptr); if (sv.mvdrecording) { if (MVDWrite_Begin (dem_single, cl - svs.clients, strlen(string)+3)) { MVD_MSG_WriteByte (svc_print); MVD_MSG_WriteByte (level); MVD_MSG_WriteString (string); } } SV_PrintToClient(cl, level, string); } void SV_ClientPrintf2 (client_t *cl, int level, char *fmt, ...) { va_list argptr; char string[1024]; if (level < cl->messagelevel) return; va_start (argptr,fmt); vsnprintf (string, sizeof(string), fmt, argptr); va_end (argptr); SV_PrintToClient(cl, level, string); } /* ================= SV_BroadcastPrintf Sends text to all active clients ================= */ char *parse_mod_string(char *str); void SV_DoBroadcastPrintf (int level, int flags, char *string) { char *fraglog; static char string2[1024] = {0}; client_t *cl; int i; if (!(flags & BPRINT_IGNORECONSOLE)) Sys_Printf ("%s", string); // print to the console if (!(flags & BPRINT_IGNORECLIENTS)) { for (i=0, cl = svs.clients ; imessagelevel) continue; if (cl->state < cs_connected) continue; SV_PrintToClient(cl, level, string); // this does't go to mvd demo } } if (!(flags & BPRINT_IGNOREINDEMO)) { if (flags & BPRINT_QTVONLY) { sizebuf_t msg; byte msg_buf[1024]; SZ_InitEx(&msg, msg_buf, sizeof(msg_buf), true); MSG_WriteByte (&msg, svc_print); MSG_WriteByte (&msg, level); MSG_WriteString (&msg, string); DemoWriteQTV(&msg); } else { if (sv.mvdrecording) { if (MVDWrite_Begin (dem_all, 0, strlen(string)+3)) { MVD_MSG_WriteByte (svc_print); MVD_MSG_WriteByte (level); MVD_MSG_WriteString (string); } } } } // SV_Write_Log(MOD_FRAG_LOG, 1, "=== SV_BroadcastPrintf ===\n"); // SV_Write_Log(MOD_FRAG_LOG, 1, va("%d\n===>", time(NULL))); // SV_Write_Log(MOD_FRAG_LOG, 1, string); // SV_Write_Log(MOD_FRAG_LOG, 1, "<===\n"); if (string[0] && logs[MOD_FRAG_LOG].sv_logfile) { if (string[strlen(string) - 1] == '\n') { strlcat(string2, string, sizeof(string2)); // SV_Write_Log(MOD_FRAG_LOG, 1, "=== SV_BroadcastPrintf ==={\n"); // SV_Write_Log(MOD_FRAG_LOG, 1, string2); // SV_Write_Log(MOD_FRAG_LOG, 1, "}==========================\n"); if ((fraglog = parse_mod_string(string2))) { SV_Write_Log(MOD_FRAG_LOG, 1, fraglog); Q_free(fraglog); } string2[0] = 0; } else strlcat(string2, string, sizeof(string2)); } // SV_Write_Log(MOD_FRAG_LOG, 1, "==========================\n\n"); } void SV_BroadcastPrintf (int level, char *fmt, ...) { va_list argptr; char string[1024]; va_start (argptr,fmt); vsnprintf (string, sizeof(string), fmt, argptr); va_end (argptr); SV_DoBroadcastPrintf (level, 0, string); } void SV_BroadcastPrintfEx (int level, int flags, char *fmt, ...) { va_list argptr; char string[1024]; va_start (argptr,fmt); vsnprintf (string, sizeof(string), fmt, argptr); va_end (argptr); SV_DoBroadcastPrintf (level, flags, string); } /* ================= SV_BroadcastCommand Sends text to all active clients ================= */ void SV_BroadcastCommand (char *fmt, ...) { va_list argptr; char string[1024]; if (!sv.state) return; va_start (argptr,fmt); vsnprintf (string, sizeof(string), fmt, argptr); va_end (argptr); MSG_WriteByte (&sv.reliable_datagram, svc_stufftext); MSG_WriteString (&sv.reliable_datagram, string); } /* ================= SV_Multicast Sends the contents of sv.multicast to a subset of the clients, then clears sv.multicast. MULTICAST_ALL same as broadcast MULTICAST_PVS send to clients potentially visible from org MULTICAST_PHS send to clients potentially hearable from org ================= */ void SV_MulticastEx (vec3_t origin, int to, const char *cl_reliable_key) { client_t* client; byte* mask; int leafnum; int j; qbool reliable; vec3_t vieworg; qbool mvd_only = false; reliable = false; switch (to) { case MULTICAST_ALL_R: reliable = true; // intentional fallthrough case MULTICAST_ALL: mask = NULL; // everything break; case MULTICAST_PHS_R: reliable = true; // intentional fallthrough case MULTICAST_PHS: mask = CM_LeafPHS (CM_PointInLeaf(origin)); break; case MULTICAST_PVS_R: reliable = true; // intentional fallthrough case MULTICAST_PVS: mask = CM_LeafPVS (CM_PointInLeaf (origin)); break; case MULTICAST_MVD_HIDDEN: mask = NULL; mvd_only = true; break; default: mask = NULL; SV_Error ("SV_Multicast: bad to:%i", to); } // send the data to all relevent clients for (j = 0, client = svs.clients; j < MAX_CLIENTS && !mvd_only; j++, client++) { int trackent = 0; if (client->state != cs_spawned) continue; if (SV_SkipCommsBotMessage(client)) continue; if (!mask) goto inrange; // multicast to all // in case of trackent we have to reflect his origin so PHS work right. if (fofs_trackent) { trackent = ((eval_t *)((byte *)(client->edict)->v + fofs_trackent))->_int; if (trackent < 1 || trackent > MAX_CLIENTS || svs.clients[trackent - 1].state != cs_spawned) trackent = 0; } if (trackent) { VectorAdd (svs.clients[trackent - 1].edict->v->origin, svs.clients[trackent - 1].edict->v->view_ofs, vieworg); } else { VectorAdd (client->edict->v->origin, client->edict->v->view_ofs, vieworg); } if (to == MULTICAST_PHS_R || to == MULTICAST_PHS) { vec3_t delta; VectorSubtract(origin, vieworg, delta); if (VectorLength(delta) <= 1024) goto inrange; } leafnum = CM_Leafnum(CM_PointInLeaf(vieworg)); if (leafnum) { // -1 is because pvs rows are 1 based, not 0 based like leafs leafnum = leafnum - 1; if ( !(mask[leafnum>>3] & (1<<(leafnum&7)) ) ) { // Con_Printf ("supressed multicast\n"); continue; } } inrange: if (reliable || (cl_reliable_key && *cl_reliable_key && strcmp("0", Info_Get(&client->_userinfo_ctx_, cl_reliable_key)))) { ClientReliableCheckBlock(client, sv.multicast.cursize); ClientReliableWrite_SZ(client, sv.multicast.data, sv.multicast.cursize); } else SZ_Write (&client->datagram, sv.multicast.data, sv.multicast.cursize); } if (sv.mvdrecording) { if (mvd_only) { mvdhidden_block_header_t header; header.length = sv.multicast.cursize - 2; // header.type_id = ...; < up to the mod to fill this part in // write to dem_multiple(0), which will be skipped by all major clients (ezQuake, FTE, fod) if (MVDWrite_HiddenBlockBegin(sv.multicast.cursize + sizeof(header.length))) { MVD_SZ_Write(&header.length, sizeof(header.length)); MVD_SZ_Write(sv.multicast.data, sv.multicast.cursize); } } else if (reliable) { if (MVDWrite_Begin(dem_all, 0, sv.multicast.cursize)) { MVD_SZ_Write(sv.multicast.data, sv.multicast.cursize); } } else { SZ_Write(&demo.datagram, sv.multicast.data, sv.multicast.cursize); } } SZ_Clear (&sv.multicast); } void SV_Multicast (vec3_t origin, int to) { SV_MulticastEx(origin, to, NULL); } void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count, int replacement_te, int replacement_count) { int i, v; qbool send_count; //if (AllClientsWantSVCParticle()) if (0) { MSG_WriteByte (&sv.multicast, nq_svc_particle); MSG_WriteCoord (&sv.multicast, org[0]); MSG_WriteCoord (&sv.multicast, org[1]); MSG_WriteCoord (&sv.multicast, org[2]); for (i=0 ; i<3 ; i++) { v = dir[i]*16; if (v > 127) v = 127; else if (v < -128) v = -128; MSG_WriteChar (&sv.multicast, v); } MSG_WriteByte (&sv.multicast, count); MSG_WriteByte (&sv.multicast, color); } else { if (replacement_te == TE_EXPLOSION || replacement_te == TE_LIGHTNINGBLOOD) send_count = false; else if (replacement_te == TE_BLOOD || replacement_te == TE_GUNSHOT) send_count = true; else return; // don't send anything MSG_WriteByte (&sv.multicast, svc_temp_entity); MSG_WriteByte (&sv.multicast, replacement_te); if (send_count) MSG_WriteByte (&sv.multicast, replacement_count); MSG_WriteCoord (&sv.multicast, org[0]); MSG_WriteCoord (&sv.multicast, org[1]); MSG_WriteCoord (&sv.multicast, org[2]); } SV_Multicast (org, MULTICAST_PVS); } /* ================== SV_StartSound Each entity can have eight independant sound sources, like voice, weapon, feet, etc. Channel 0 is an auto-allocate channel, the others override anything already running on that entity/channel pair. An attenuation of 0 will play full volume everywhere in the level. Larger attenuations will drop off. (max 4 attenuation) ================== */ void SV_StartSound (edict_t *entity, int channel, char *sample, int volume, float attenuation) { int sound_num; int i; int ent; vec3_t origin; qbool use_phs; qbool reliable = false; if (volume < 0 || volume > 255) SV_Error ("SV_StartSound: volume = %i", volume); if (attenuation < 0 || attenuation > 4) SV_Error ("SV_StartSound: attenuation = %f", attenuation); if (channel < 0 || channel > 15) SV_Error ("SV_StartSound: channel = %i", channel); // find precache number for sound for (sound_num=1 ; sound_numv->solid == SOLID_BSP) { for (i=0 ; i<3 ; i++) origin[i] = entity->v->origin[i]+0.5*(entity->v->mins[i]+entity->v->maxs[i]); } else { VectorCopy (entity->v->origin, origin); } MSG_WriteByte (&sv.multicast, svc_sound); MSG_WriteShort (&sv.multicast, channel); if (channel & SND_VOLUME) MSG_WriteByte (&sv.multicast, volume); if (channel & SND_ATTENUATION) MSG_WriteByte (&sv.multicast, attenuation*64); MSG_WriteByte (&sv.multicast, sound_num); for (i=0 ; i<3 ; i++) MSG_WriteCoord (&sv.multicast, origin[i]); if (use_phs) SV_MulticastEx (origin, reliable ? MULTICAST_PHS_R : MULTICAST_PHS, sv_reliable_sound.value ? "rsnd" : NULL); else SV_MulticastEx (origin, reliable ? MULTICAST_ALL_R : MULTICAST_ALL, sv_reliable_sound.value ? "rsnd" : NULL); } /* =============================================================================== FRAME UPDATES =============================================================================== */ int sv_nailmodel, sv_supernailmodel, sv_playermodel; void SV_FindModelNumbers (void) { int i; sv_nailmodel = -1; sv_supernailmodel = -1; sv_playermodel = -1; for (i=0 ; iedict; clnum = NUM_FOR_EDICT(ent) - 1; // send the chokecount for r_netgraph if (client->chokecount) { MSG_WriteByte (msg, svc_chokecount); MSG_WriteByte (msg, client->chokecount); client->chokecount = 0; } // send a damage message if the player got hit this frame if (ent->v->dmg_take || ent->v->dmg_save) { other = PROG_TO_EDICT(ent->v->dmg_inflictor); MSG_WriteByte (msg, svc_damage); MSG_WriteByte (msg, ent->v->dmg_save); MSG_WriteByte (msg, ent->v->dmg_take); for (i=0 ; i<3 ; i++) MSG_WriteCoord (msg, other->v->origin[i] + 0.5*(other->v->mins[i] + other->v->maxs[i])); ent->v->dmg_take = 0; ent->v->dmg_save = 0; } // add this to server demo if (sv.mvdrecording && msg->cursize) { if (MVDWrite_Begin(dem_single, clnum, msg->cursize)) { MVD_SZ_Write(msg->data, msg->cursize); } } // a fixangle might get lost in a dropped packet. Oh well. if (ent->v->fixangle) { ent->v->fixangle = 0; demo.fixangle[clnum] = true; MSG_WriteByte(msg, svc_setangle); #ifdef MVD_PEXT1_HIGHLAGTELEPORT if (client->mvdprotocolextensions1 & MVD_PEXT1_HIGHLAGTELEPORT) { if (fofs_teleported) { client->lastteleport_teleport = ((eval_t *)((byte *)(client->edict)->v + fofs_teleported))->_int; if (client->lastteleport_teleport) { MSG_WriteByte(msg, 1); // signal a teleport } else { MSG_WriteByte(msg, 2); // respawn } client->lastteleport_outgoingseq = client->netchan.outgoing_sequence; client->lastteleport_incomingseq = client->netchan.incoming_sequence; client->lastteleport_teleportyaw = (client->edict)->v->angles[YAW] - client->lastcmd.angles[YAW]; ((eval_t *)((byte *)(client->edict)->v + fofs_teleported))->_int = 0; SV_RotateCmd(client, &client->lastcmd); } else { MSG_WriteByte(msg, 0); // we don't know, so no changes made... } } #endif for (i=0 ; i < 3 ; i++) MSG_WriteAngle (msg, ent->v->angles[i] ); if (sv.mvdrecording) { MSG_WriteByte (&demo.datagram, svc_setangle); MSG_WriteByte (&demo.datagram, clnum); for (i=0 ; i < 3 ; i++) MSG_WriteAngle (&demo.datagram, ent->v->angles[i] ); } } // Z_EXT_TIME protocol extension // every now and then, send an update so that extrapolation // on client side doesn't stray too far off #ifdef FTE_PEXT_ACCURATETIMINGS if (client->fteprotocolextensions & FTE_PEXT_ACCURATETIMINGS) { //the fte pext causes the server to send out accurate timings, allowing for perfect interpolation. if (sv.physicstime - client->lastservertimeupdate > 0) { MSG_WriteByte(msg, svc_updatestatlong); MSG_WriteByte(msg, STAT_TIME); MSG_WriteLong(msg, (int)(sv.physicstime * 1000)); client->lastservertimeupdate = sv.physicstime; } } else #endif if ((SERVER_EXTENSIONS & Z_EXT_SERVERTIME) && (client->extensions & Z_EXT_SERVERTIME)) { //the zquake ext causes the server to send out peridoic timings, allowing for moderatly accurate game time. if (realtime - client->lastservertimeupdate > 5) { MSG_WriteByte(msg, svc_updatestatlong); MSG_WriteByte(msg, STAT_TIME); MSG_WriteLong(msg, (int) (sv.time * 1000)); client->lastservertimeupdate = realtime; } } } /* ======================= SV_UpdateClientStats Performs a delta update of the stats array. This should only be performed when a reliable message can be delivered this frame. ======================= */ void SV_UpdateClientStats (client_t *client) { edict_t *ent; int stats[MAX_CL_STATS], i; memset (stats, 0, sizeof(stats)); ent = client->edict; // if we are a spectator and we are tracking a player, we get his stats // so our status bar reflects his if (client->spectator && client->spec_track > 0) ent = svs.clients[client->spec_track - 1].edict; // in case of trackent we have to reflect his stats like for spectator. if (fofs_trackent) { int trackent = ((eval_t *)((byte *)(client->edict)->v + fofs_trackent))->_int; if (trackent < 1 || trackent > MAX_CLIENTS || svs.clients[trackent - 1].state != cs_spawned) trackent = 0; if (trackent) ent = svs.clients[trackent - 1].edict; } stats[STAT_HEALTH] = ent->v->health; stats[STAT_WEAPON] = SV_ModelIndex(PR_GetEntityString(ent->v->weaponmodel)); stats[STAT_AMMO] = ent->v->currentammo; stats[STAT_ARMOR] = ent->v->armorvalue; stats[STAT_SHELLS] = ent->v->ammo_shells; stats[STAT_NAILS] = ent->v->ammo_nails; stats[STAT_ROCKETS] = ent->v->ammo_rockets; stats[STAT_CELLS] = ent->v->ammo_cells; if (!client->spectator || client->spec_track > 0) stats[STAT_ACTIVEWEAPON] = ent->v->weapon; // stuff the sigil bits into the high bits of items for sbar stats[STAT_ITEMS] = (int) ent->v->items | ((int) PR_GLOBAL(serverflags) << 28); if (fofs_items2) // ZQ_ITEMS2 extension stats[STAT_ITEMS] |= (int)EdictFieldFloat(ent, fofs_items2) << 23; if (ent->v->health > 0 || client->spectator) // viewheight for PF_DEAD & PF_GIB is hardwired stats[STAT_VIEWHEIGHT] = ent->v->view_ofs[2]; for (i=0 ; istats[i]) { client->stats[i] = stats[i]; if (stats[i] >=0 && stats[i] <= 255) { ClientReliableWrite_Begin(client, svc_updatestat, 3); ClientReliableWrite_Byte(client, i); ClientReliableWrite_Byte(client, stats[i]); } else { ClientReliableWrite_Begin(client, svc_updatestatlong, 6); ClientReliableWrite_Byte(client, i); ClientReliableWrite_Long(client, stats[i]); } } } /* ======================= SV_SendClientDatagram ======================= */ void SV_SendClientDatagram (client_t *client, int client_num) { byte buf[MAX_DATAGRAM]; sizebuf_t msg; // packet_t *pack; SZ_InitEx(&msg, buf, sizeof(buf), true); // for faster downloading skip half the frames /*if (client->download && client->netchan.outgoing_sequence & 1) { // we're sending fake invalid delta update, so that client won't update screen MSG_WriteByte (&msg, svc_deltapacketentities); MSG_WriteByte (&msg, 0); MSG_WriteShort (&msg, 0); Netchan_Transmit (&client->netchan, msg.cursize, buf); } */ if (!SV_SkipCommsBotMessage(client)) { // add the client specific data to the datagram SV_WriteClientdataToMessage(client, &msg); // send over all the objects that are in the PVS // this will include clients, a packetentities, and // possibly a nails update SV_WriteEntitiesToClient(client, &msg, false); #ifdef FTE_PEXT2_VOICECHAT SV_VoiceSendPacket(client, &msg); #endif } // copy the accumulated multicast datagram // for this client out to the message if (client->datagram.overflowed) Con_Printf ("WARNING: datagram overflowed for %s\n", client->name); else SZ_Write (&msg, client->datagram.data, client->datagram.cursize); SZ_Clear (&client->datagram); // send deltas over reliable stream if (Netchan_CanReliable (&client->netchan)) SV_UpdateClientStats (client); if (msg.overflowed) { Con_Printf ("WARNING: msg overflowed for %s\n", client->name); SZ_Clear (&msg); } // send the datagram Netchan_Transmit (&client->netchan, msg.cursize, buf); } /* ======================= SV_UpdateToReliableMessages ======================= */ static void SV_UpdateToReliableMessages (void) { int i, j; client_t *client; edict_t *ent; // check for changes to be sent over the reliable streams to all clients for (i=0, sv_client = svs.clients ; istate != cs_spawned) continue; if (sv_client->sendinfo) { sv_client->sendinfo = false; SV_FullClientUpdate (sv_client, &sv.reliable_datagram); } ent = sv_client->edict; if (sv_client->old_frags != (int)ent->v->frags) { for (j=0, client = svs.clients ; jstate < cs_preconnected) continue; ClientReliableWrite_Begin(client, svc_updatefrags, 4); ClientReliableWrite_Byte(client, i); ClientReliableWrite_Short(client, (int) ent->v->frags); } if (sv.mvdrecording) { if (MVDWrite_Begin(dem_all, 0, 4)) { MVD_MSG_WriteByte(svc_updatefrags); MVD_MSG_WriteByte(i); MVD_MSG_WriteShort((int)ent->v->frags); } } sv_client->old_frags = (int) ent->v->frags; } // maxspeed/entgravity changes if (fofs_gravity && sv_client->entgravity != EdictFieldFloat(ent, fofs_gravity)) { sv_client->entgravity = EdictFieldFloat(ent, fofs_gravity); ClientReliableWrite_Begin(sv_client, svc_entgravity, 5); ClientReliableWrite_Float(sv_client, sv_client->entgravity); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, i, 5)) { MVD_MSG_WriteByte(svc_entgravity); MVD_MSG_WriteFloat(sv_client->entgravity); } } } if (fofs_maxspeed && sv_client->maxspeed != EdictFieldFloat(ent, fofs_maxspeed)) { sv_client->maxspeed = EdictFieldFloat(ent, fofs_maxspeed); ClientReliableWrite_Begin(sv_client, svc_maxspeed, 5); ClientReliableWrite_Float(sv_client, sv_client->maxspeed); if (sv.mvdrecording) { if (MVDWrite_Begin(dem_single, i, 5)) { MVD_MSG_WriteByte(svc_maxspeed); MVD_MSG_WriteFloat(sv_client->maxspeed); } } } } if (sv.datagram.overflowed) SZ_Clear (&sv.datagram); // append the broadcast messages to each client messages for (j=0, client = svs.clients ; jstate < cs_preconnected) continue; // reliables go to all connected or spawned ClientReliableCheckBlock(client, sv.reliable_datagram.cursize); ClientReliableWrite_SZ(client, sv.reliable_datagram.data, sv.reliable_datagram.cursize); if (client->state != cs_spawned) continue; // datagrams only go to spawned SZ_Write (&client->datagram, sv.datagram.data, sv.datagram.cursize); } if (sv.mvdrecording && sv.reliable_datagram.cursize) { if (MVDWrite_Begin(dem_all, 0, sv.reliable_datagram.cursize)) { MVD_SZ_Write(sv.reliable_datagram.data, sv.reliable_datagram.cursize); } } if (sv.mvdrecording) SZ_Write(&demo.datagram, sv.datagram.data, sv.datagram.cursize); // FIXME: ??? SZ_Clear (&sv.reliable_datagram); SZ_Clear (&sv.datagram); } //#ifdef _WIN32 //#pragma optimize( "", off ) //#endif /* ======================= SV_SendClientMessages ======================= */ void SV_SendClientMessages (void) { int i, j; client_t *c; if (sv.state != ss_active) return; // update frags, names, etc SV_UpdateToReliableMessages (); if (fofs_visibility) { for (i = 0; i < MAX_CLIENTS; ++i) { ((eval_t *)((byte *)(svs.clients[i].edict)->v + fofs_visibility))->_int = 0; } } // build individual updates for (i=0, c = svs.clients ; istate) continue; if (c->drop) { SV_DropClient(c); c->drop = false; continue; } // check to see if we have a backbuf to stick in the reliable if (c->num_backbuf) { // will it fit? if (c->netchan.message.cursize + c->backbuf_size[0] < c->netchan.message.maxsize) { Con_DPrintf("%s: backbuf %d bytes\n", c->name, c->backbuf_size[0]); // it'll fit SZ_Write(&c->netchan.message, c->backbuf_data[0], c->backbuf_size[0]); //move along, move along for (j = 1; j < c->num_backbuf; j++) { memcpy(c->backbuf_data[j - 1], c->backbuf_data[j], c->backbuf_size[j]); c->backbuf_size[j - 1] = c->backbuf_size[j]; } c->num_backbuf--; if (c->num_backbuf) { memset(&c->backbuf, 0, sizeof(c->backbuf)); c->backbuf.data = c->backbuf_data[c->num_backbuf - 1]; c->backbuf.cursize = c->backbuf_size[c->num_backbuf - 1]; c->backbuf.maxsize = c->netchan.message.maxsize; } } } #ifdef USE_PR2 if (c->isBot) { // Write damage to bot clients too (for mvd playback) SV_BotWriteDamage(c, i); SZ_Clear(&c->netchan.message); SZ_Clear(&c->datagram); c->num_backbuf = 0; // Need to tell mod what the bot would have seen SV_SetVisibleEntitiesForBot(c); continue; } #endif // if the reliable message overflowed, // drop the client if (c->netchan.message.overflowed) { SZ_Clear (&c->netchan.message); SZ_Clear (&c->datagram); SV_BroadcastPrintf (PRINT_HIGH, "%s overflowed\n", c->name); Con_Printf ("WARNING: reliable overflow for %s\n",c->name); SV_DropClient (c); c->send_message = true; c->netchan.cleartime = 0; // don't choke this message } // only send messages if the client has sent one // and the bandwidth is not choked if (!c->send_message) continue; c->send_message = false; // try putting this after choke? if (!sv.paused && !Netchan_CanPacket (&c->netchan)) { c->chokecount++; continue; // bandwidth choke } if (c->state == cs_spawned) SV_SendClientDatagram (c, i); else { Netchan_Transmit (&c->netchan, c->datagram.cursize, c->datagram.data); // just update reliable c->datagram.cursize = 0; } } } static void SV_BotWriteDamage(client_t* c, int i) { edict_t* ent = c->edict; if (c->edict->v->dmg_take || c->edict->v->dmg_save) { if (ent->v->dmg_take || ent->v->dmg_save) { int length = 3 + 3 * msg_coordsize; if (MVDWrite_Begin(dem_single, i, length)) { edict_t* other = PROG_TO_EDICT(ent->v->dmg_inflictor); MVD_MSG_WriteByte(svc_damage); MVD_MSG_WriteByte(ent->v->dmg_save); MVD_MSG_WriteByte(ent->v->dmg_take); for (i = 0; i < 3; i++) MVD_MSG_WriteCoord(other->v->origin[i] + 0.5 * (other->v->mins[i] + other->v->maxs[i])); } ent->v->dmg_take = 0; ent->v->dmg_save = 0; } } } void SV_MVDPings (void) { client_t *client; int j; for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) { if (client->state != cs_spawned) continue; if (MVDWrite_Begin (dem_all, 0, 7)) { MVD_MSG_WriteByte (svc_updateping); MVD_MSG_WriteByte (j); MVD_MSG_WriteShort(SV_CalcPing(client)); MVD_MSG_WriteByte (svc_updatepl); MVD_MSG_WriteByte (j); MVD_MSG_WriteByte (client->lossage); } } } void MVD_WriteStats(void) { client_t *c; int i, j; edict_t *ent; int stats[MAX_CL_STATS]; for (i = 0, c = svs.clients ; i < MAX_CLIENTS ; i++, c++) { if (c->state != cs_spawned) continue; // datagrams only go to spawned if (c->spectator) continue; ent = c->edict; memset (stats, 0, sizeof(stats)); stats[STAT_HEALTH] = ent->v->health; stats[STAT_WEAPON] = SV_ModelIndex(PR_GetEntityString(ent->v->weaponmodel)); stats[STAT_AMMO] = ent->v->currentammo; stats[STAT_ARMOR] = ent->v->armorvalue; stats[STAT_SHELLS] = ent->v->ammo_shells; stats[STAT_NAILS] = ent->v->ammo_nails; stats[STAT_ROCKETS] = ent->v->ammo_rockets; stats[STAT_CELLS] = ent->v->ammo_cells; stats[STAT_ACTIVEWEAPON] = ent->v->weapon; if (ent->v->health > 0) // viewheight for PF_DEAD & PF_GIB is hardwired stats[STAT_VIEWHEIGHT] = ent->v->view_ofs[2]; // stuff the sigil bits into the high bits of items for sbar stats[STAT_ITEMS] = (int) ent->v->items | ((int) PR_GLOBAL(serverflags) << 28); for (j = 0 ; j < MAX_CL_STATS; j++) { if (stats[j] != demo.stats[i][j]) { demo.stats[i][j] = stats[j]; if (stats[j] >= 0 && stats[j] <= 255) { if (MVDWrite_Begin(dem_stats, i, 3)) { MVD_MSG_WriteByte(svc_updatestat); MVD_MSG_WriteByte(j); MVD_MSG_WriteByte(stats[j]); } } else { if (MVDWrite_Begin(dem_stats, i, 6)) { MVD_MSG_WriteByte(svc_updatestatlong); MVD_MSG_WriteByte(j); MVD_MSG_WriteLong(stats[j]); } } } } } } void DestFlush(qbool compleate); void SV_MVD_RunPendingConnections(void); void QTV_ReadDests( void ); void SV_SendDemoMessage(void) { int i, cls = 0; client_t *c; sizebuf_t msg; byte msg_buf[MAX_MVD_SIZE]; // data without mvd header float min_fps; extern cvar_t sv_demofps, sv_demoIdlefps; extern cvar_t sv_demoPings; if (sv.state != ss_active) return; SV_MVD_RunPendingConnections(); if (!sv.mvdrecording) { DestFlush(false); // well, this may help close some fucked up dests return; } for (i = 0, c = svs.clients; i < MAX_CLIENTS; i++, c++) { if (c->state != cs_spawned) continue; // datagrams only go to spawned cls |= 1 << i; } // if no players or paused, use idle fps if (cls && !sv.paused) min_fps = max(4.0, (int)sv_demofps.value ? (int)sv_demofps.value : 20.0); else min_fps = bound(4.0, (int)sv_demoIdlefps.value, 30); if (curtime - demo.curtime < 1.0 / min_fps) { return; } SZ_InitEx(&msg, msg_buf, sizeof(msg_buf), true); if ((int)sv_demoPings.value) { if (curtime - demo.pingtime > sv_demoPings.value) { SV_MVDPings(); demo.pingtime = curtime; } } MVD_WriteStats(); // send over all the objects that are in the PVS // this will include clients, a packetentities, and // possibly a nails update if (!demo.recorder.delta_sequence) demo.recorder.delta_sequence = -1; SV_WriteEntitiesToClient (&demo.recorder, &msg, true); if (msg.overflowed) { Sys_Printf("WARNING: msg overflowed in SV_SendDemoMessage\n"); } else { if (msg.cursize) { if (MVDWrite_Begin(dem_all, 0, msg.cursize)) { MVD_SZ_Write(msg.data, msg.cursize); } } } SZ_Clear(&msg); // copy the accumulated multicast datagram // for this client out to the message if (demo.datagram.overflowed) { Sys_Printf("WARNING: demo.datagram overflowed in SV_SendDemoMessage\n"); } else { if (demo.datagram.cursize) { if (MVDWrite_Begin(dem_all, 0, demo.datagram.cursize)) { MVD_SZ_Write (demo.datagram.data, demo.datagram.cursize); } } } SZ_Clear (&demo.datagram); demo.recorder.delta_sequence = demo.recorder.netchan.incoming_sequence&255; demo.recorder.netchan.incoming_sequence++; demo.frames[demo.parsecount & UPDATE_MASK].time = sv.time; demo.frames[demo.parsecount & UPDATE_MASK].paused = sv.paused; demo.frames[demo.parsecount & UPDATE_MASK].pause_duration = (int)bound(0, (curtime - demo.curtime) * 1000.0f, 255); demo.curtime = curtime; demo.time = sv.time; // let's not wait so much time (was 60) if (demo.parsecount - demo.lastwritten > 5) { SV_MVDWritePackets(1); } // flush once per demo frame DestFlush(false); // read QTV input once per demo frame QTV_ReadDests(); if (!sv.mvdrecording) return; demo.parsecount++; } //#ifdef _WIN32 //#pragma optimize( "", on ) //#endif /* ======================= SV_SendMessagesToAll FIXME: does this sequence right? ======================= */ void SV_SendMessagesToAll (void) { int i; client_t *c; for (i=0, c = svs.clients ; istate >= cs_connected) // FIXME: should this only send to active? c->send_message = true; SV_SendClientMessages (); } #endif // !CLIENTONLY mvdsv-0.35/src/sv_sys_unix.c000066400000000000000000000363201427146041000161270ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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 "qwsvdef.h" extern cvar_t sys_restart_on_error; extern cvar_t sys_select_timeout, sys_simulation; cvar_t sys_nostdout = {"sys_nostdout", "0"}; cvar_t sys_extrasleep = {"sys_extrasleep", "0"}; static qbool stdin_ready = false; //static qbool isdaemon = false; static qbool do_stdin = true; /* =============================================================================== REQUIRED SYS FUNCTIONS =============================================================================== */ /* ============ Sys_FileTime returns -1 if not present ============ */ int Sys_FileTime (const char *path) { struct stat buf; return stat(path, &buf) == -1 ? -1 : buf.st_mtime; } int Sys_FileSizeTime (char *path, int *time1) { struct stat buf; if (stat(path, &buf) == -1) { *time1 = -1; return 0; } else { *time1 = buf.st_mtime; return buf.st_size; } } /* ============ Sys_mkdir ============ */ void Sys_mkdir (const char *path) { if (mkdir (path, 0777) != -1) return; if (qerrno != EEXIST) Sys_Error ("mkdir %s: (%i): %s", path, qerrno, strerror(qerrno)); } /* ================ Sys_remove ================ */ int Sys_remove (const char *path) { return unlink(path); } //bliP: rmdir -> /* ================ Sys_rmdir ================ */ int Sys_rmdir (const char *path) { return rmdir(path); } //<- /* ================ Sys_listdir ================ */ dir_t Sys_listdir (const char *path, const char *ext, int sort_type) { static file_t list[MAX_DIRFILES]; dir_t dir; char pathname[MAX_OSPATH]; DIR *d; DIR *testdir; //bliP: list dir struct dirent *oneentry; qbool all; int r; pcre *preg = NULL; const char *errbuf; memset(list, 0, sizeof(list)); memset(&dir, 0, sizeof(dir)); dir.files = list; all = !strncmp(ext, ".*", 3); if (!all) if (!(preg = pcre_compile(ext, PCRE_CASELESS, &errbuf, &r, NULL))) { Con_Printf("Sys_listdir: pcre_compile(%s) error: %s at offset %d\n", ext, errbuf, r); Q_free(preg); return dir; } if (!(d = opendir(path))) { if (!all) Q_free(preg); return dir; } while ((oneentry = readdir(d))) { if (!strncmp(oneentry->d_name, ".", 2) || !strncmp(oneentry->d_name, "..", 3)) continue; if (!all) { switch (r = pcre_exec(preg, NULL, oneentry->d_name, strlen(oneentry->d_name), 0, 0, NULL, 0)) { case 0: break; case PCRE_ERROR_NOMATCH: continue; default: Con_Printf("Sys_listdir: pcre_exec(%s, %s) error code: %d\n", ext, oneentry->d_name, r); Q_free(preg); return dir; } } snprintf(pathname, sizeof(pathname), "%s/%s", path, oneentry->d_name); if ((testdir = opendir(pathname))) { dir.numdirs++; list[dir.numfiles].isdir = true; list[dir.numfiles].size = list[dir.numfiles].time = 0; closedir(testdir); } else { list[dir.numfiles].isdir = false; //list[dir.numfiles].time = Sys_FileTime(pathname); dir.size += (list[dir.numfiles].size = Sys_FileSizeTime(pathname, &list[dir.numfiles].time)); } strlcpy (list[dir.numfiles].name, oneentry->d_name, MAX_DEMO_NAME); if (++dir.numfiles == MAX_DIRFILES - 1) break; } closedir(d); if (!all) Q_free(preg); switch (sort_type) { case SORT_NO: break; case SORT_BY_DATE: qsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_date); break; case SORT_BY_NAME: qsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_name); break; } return dir; } int Sys_EnumerateFiles (char *gpath, char *match, int (*func)(char *, int, void *), void *parm) { DIR *dir, *dir2; char apath[MAX_OSPATH]; char file[MAX_OSPATH]; char truepath[MAX_OSPATH]; char *s; struct dirent *ent; //printf("path = %s\n", gpath); //printf("match = %s\n", match); if (!gpath) gpath = ""; *apath = '\0'; strncpy(apath, match, sizeof(apath)); for (s = apath+strlen(apath)-1; s >= apath; s--) { if (*s == '/') { s[1] = '\0'; match += s - apath+1; break; } } if (s < apath) //didn't find a '/' *apath = '\0'; snprintf(truepath, sizeof(truepath), "%s/%s", gpath, apath); //printf("truepath = %s\n", truepath); //printf("gamepath = %s\n", gpath); //printf("apppath = %s\n", apath); //printf("match = %s\n", match); dir = opendir(truepath); if (!dir) { Con_DPrintf("Failed to open dir %s\n", truepath); return true; } do { ent = readdir(dir); if (!ent) break; if (*ent->d_name != '.') if (wildcmp(match, ent->d_name)) { snprintf(file, sizeof(file), "%s/%s", truepath, ent->d_name); //would use stat, but it breaks on fat32. if ((dir2 = opendir(file))) { closedir(dir2); snprintf(file, sizeof(file), "%s%s/", apath, ent->d_name); //printf("is directory = %s\n", file); } else { snprintf(file, sizeof(file), "%s%s", apath, ent->d_name); //printf("file = %s\n", file); } if (!func(file, -2, parm)) { closedir(dir); return false; } } } while(1); closedir(dir); return true; } /* ================ Sys_Exit ================ */ void Sys_Exit (int code) { exit(code); // appkit isn't running } /* ================ Sys_Quit ================ */ void Sys_Quit (qbool restart) { if (restart) { int maxfd = getdtablesize() - 1; // close all file descriptors besides stdin stdout and stderr, I am not sure, perhaps I can safely close even those descriptors too. for (; maxfd > 2; maxfd--) close(maxfd); if (execv(com_argv[0], com_argv) == -1) { Sys_Printf("Restart failed: %s\n", strerror(qerrno)); Sys_Exit(1); } } Sys_Exit(0); // appkit isn't running } /* ================ Sys_Error ================ */ void Sys_Error (const char *error, ...) { static qbool inerror = false; va_list argptr; char text[1024]; sv_error = true; if (inerror) Sys_Exit (1); inerror = true; va_start (argptr,error); vsnprintf (text, sizeof(text), error, argptr); va_end (argptr); if (!(int)sys_nostdout.value) Sys_Printf ("ERROR: %s\n", text); if (logs[ERROR_LOG].sv_logfile) { SV_Write_Log (ERROR_LOG, 1, va ("ERROR: %s\n", text)); // fclose (logs[ERROR_LOG].sv_logfile); } // FIXME: hack - checking SV_Shutdown with net_socket set in -1 NET_Shutdown if (svs.socketip != -1) SV_Shutdown (va("ERROR: %s\n", text)); if ((int)sys_restart_on_error.value) Sys_Quit (true); Sys_Exit (1); } /* ================ Sys_DoubleTime ================ */ #if (_POSIX_TIMERS > 0) && defined(_POSIX_MONOTONIC_CLOCK) #include double Sys_DoubleTime(void) { static unsigned int secbase; struct timespec ts; #if defined __linux__ && defined CLOCK_MONOTONIC_RAW clock_gettime(CLOCK_MONOTONIC_RAW, &ts); #else clock_gettime(CLOCK_MONOTONIC, &ts); #endif if (!secbase) { secbase = ts.tv_sec; return ts.tv_nsec / 1000000000.0; } return (ts.tv_sec - secbase) + ts.tv_nsec / 1000000000.0; } #else double Sys_DoubleTime(void) { struct timeval tp; struct timezone tzp; static int secbase; gettimeofday(&tp, &tzp); if (!secbase) { secbase = tp.tv_sec; return tp.tv_usec/1000000.0; } return (tp.tv_sec - secbase) + tp.tv_usec / 1000000.0; } #endif /* ================ Sys_ConsoleInput Checks for a complete line of text typed in at the console, then forwards it to the host command processor ================ */ char *Sys_ConsoleInput (void) { static char text[256]; ssize_t len = 0; if (!do_stdin || !stdin_ready) return NULL; // the select didn't say it was ready stdin_ready = false; len = read (STDIN_FILENO, text, sizeof(text)); if (len < 0) return NULL; // error. if (len == 0) { // end of file do_stdin = false; return NULL; } text[len - 1] = 0; // rip off the /n and terminate return text; } /* ================ Sys_Printf ================ */ void Sys_Printf (char *fmt, ...) { va_list argptr; char text[4096], line[4096]; char* startpos; char* endpos; int len; date_t date; va_start (argptr,fmt); vsnprintf(text, sizeof(text), fmt, argptr); va_end (argptr); if (sys_nostdout.value) return; if (strlen(text) < 2) return; // normalize text before add to console. Q_normalizetext(text); SV_TimeOfDay(&date, "%Y-%m-%d %H:%M:%S"); startpos = text; while (startpos && startpos[0]) { endpos = strchr(startpos, '\n'); if (endpos) { *endpos = '\0'; } fprintf(stdout, "[%s] %s\n", date.str, startpos); fflush(stdout); if (endpos) { startpos = endpos + 1; if (startpos[0] == (char)10) { ++startpos; } } else { break; } } } /* ============= Sys_Init Quake calls this so the system can register variables before host_hunklevel is marked ============= */ void Sys_Init (void) { Cvar_Register (&sys_nostdout); Cvar_Register (&sys_extrasleep); } void Sys_Sleep(unsigned long ms) { usleep(ms*1000); } int Sys_Script (const char *path, const char *args) { char exec_path[1024]; char *exec_args[1024]; char *tmp_args, *p; int i; if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) return 0; switch(fork()) { case -1: /* oops, we cannot fork */ return 0; case 0: break; default: return 1; } strlcpy(exec_path, "./", sizeof(exec_path)); strlcat(exec_path, path, sizeof(exec_path)); strlcat(exec_path, ".qws", sizeof(exec_path)); tmp_args = strdup(args); if (tmp_args == NULL) goto err_exit; memset(exec_args, 0, sizeof(exec_args)); /* translate args into array of arguments for execv(2) call */ i = 0; exec_args[i++] = exec_path; for (p = tmp_args; *p;) { while(*p == ' ') { *(p++) = '\0'; } if (*p) exec_args[i++] = p; /* go to another space or end of string */ while(*p && *p != ' ') p++; } if (chdir(fs_gamedir) == -1) goto err_exit; if (execv(exec_path, exec_args) == -1) goto err_exit; err_exit: /* indicate error */ exit(1); } DL_t Sys_DLOpen(const char *path) { DL_t ret = dlopen(path, #ifdef __OpenBSD__ DL_LAZY #else RTLD_NOW #endif ); if (!ret) Con_DPrintf("Sys_DLOpen: %s\n", dlerror()); return ret; } qbool Sys_DLClose(DL_t dl) { return !dlclose(dl); } void *Sys_DLProc(DL_t dl, const char *name) { return dlsym(dl, name); } int Sys_CreateThread(DWORD (WINAPI *func)(void *), void *param) { pthread_t thread; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_attr_setschedpolicy(&attr, SCHED_OTHER); // ale gowno pthread_create(&thread, &attr, (void *)func, param); return 1; } // Function only_digits was copied from bind (DNS server) sources. static int only_digits(const char *s) { if (*s == '\0') return (0); while (*s != '\0') { if (!isdigit(*s)) return (0); s++; } return (1); } // Init unix related stuff. // Daemon, chroot, setgid and setuid code (-d, -t, -g, -u) // was copied from bind (DNS server) sources. static void SV_System_Init(void) { int j; qbool ind; uid_t user_id = 0; gid_t group_id = 0; struct passwd *pw = NULL; struct group *gr; char *user_name = NULL, *group_name = NULL, *chroot_dir; // daemon if (COM_CheckParm ("-d")) { switch (fork()) { case -1: exit(-1); // Error, do normal exit(). case 0: break; // Child, continue process. default: _exit(0); // Parent, for some reason we prefer here "limited" clean up with _exit(). } if (setsid() == -1) Sys_Printf("setsid: %s\n", strerror(qerrno)); if ((j = open(_PATH_DEVNULL, O_RDWR)) != -1) { (void)dup2(j, STDIN_FILENO); (void)dup2(j, STDOUT_FILENO); (void)dup2(j, STDERR_FILENO); if (j > 2) (void)close(j); //isdaemon = true; do_stdin = false; } } // setgid j = COM_CheckParm ("-g"); if (j && j + 1 < com_argc) { ind = true; group_name = com_argv[j + 1]; if (only_digits(group_name)) group_id = Q_atoi(group_name); else { if (!(gr = getgrnam(group_name))) { Sys_Printf("WARNING: group \"%s\" unknown\n", group_name); ind = false; } else group_id = gr->gr_gid; } if (ind) if (setgid(group_id) < 0) Sys_Printf("WARNING: Can't setgid to group \"%s\": %s\n", group_name, strerror(qerrno)); } // setuid - only resolve name ind = false; j = COM_CheckParm ("-u"); if (j && j + 1 < com_argc) { ind = true; user_name = com_argv[j + 1]; j = only_digits(user_name); if (j) { user_id = Q_atoi(user_name); pw = getpwuid(user_id); } if (!j || !pw) { if (!(pw = getpwnam(user_name))) { if (j) Sys_Printf("WARNING: user with uid %u unknown, but we will try to setuid\n", (unsigned)user_id); else { Sys_Printf("WARNING: user \"%s\" unknown\n", user_name); ind = false; } } else user_id = pw->pw_uid; } if (ind) { if (pw) { if (!group_name) { group_id = pw->pw_gid; if (setgid(group_id) < 0) Sys_Printf("WARNING: Can't setgid to group \"%s\": %s\n", group_name, strerror(qerrno)); } if (!getuid() && initgroups(pw->pw_name, group_id) < 0) Sys_Printf("WARNING: Can't initgroups(%s, %d): %s", user_name, (unsigned)group_id, strerror(qerrno)); } } } // chroot j = COM_CheckParm ("-t"); if (j && j + 1 < com_argc) { chroot_dir = com_argv[j + 1]; if (chroot(chroot_dir) < 0) Sys_Printf("chroot %s failed: %s\n", chroot_dir, strerror(qerrno)); else if (chdir("/") < 0) Sys_Printf("chdir(\"/\") to %s failed: %s\n", chroot_dir, strerror(qerrno)); } // setuid - we can't setuid before chroot and // can't resolve uid/gid from user/group names after chroot if (ind) { if (setuid(user_id) < 0) Sys_Printf("WARNING: Can't setuid to user \"%s\": %s\n", user_name, strerror(qerrno)); } } /* ============= main ============= */ int main (int argc, char *argv[]) { double time1, oldtime, newtime; // Without signal(SIGPIPE, SIG_IGN); MVDSV crashes on *nix when qtvproxy will be disconnect. signal(SIGPIPE, SIG_IGN); COM_InitArgv (argc, argv); SV_System_Init(); // daemonize and so... Host_Init(argc, argv, DEFAULT_MEM_SIZE); // run one frame immediately for first heartbeat SV_Frame (0.1); // main loop oldtime = Sys_DoubleTime () - 0.1; while (1) { // select on the net socket and stdin // the only reason we have a timeout at all is so that if the last // connected client times out, the message would not otherwise // be printed until the next event. if (!sys_simulation.value) { stdin_ready = NET_Sleep((int)sys_select_timeout.value / 1000, do_stdin); } // find time passed since last cycle newtime = Sys_DoubleTime (); time1 = newtime - oldtime; oldtime = newtime; curtime = newtime; SV_Frame (time1); // extrasleep is just a way to generate a fucked up connection on purpose if ((int)sys_extrasleep.value) usleep ((unsigned long)sys_extrasleep.value); } return 0; } mvdsv-0.35/src/sv_sys_win.c000066400000000000000000000415451427146041000157460ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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 "qwsvdef.h" #include #include extern cvar_t sys_restart_on_error; extern cvar_t sys_select_timeout, sys_simulation; cvar_t sys_nostdout = {"sys_nostdout", "0"}; cvar_t sys_sleep = {"sys_sleep", "8"}; static char title[16]; static qbool isdaemon = false; //============================================================================== // WINDOWS CMD LINE CRAP //============================================================================== static int argc; static char *argv[MAX_NUM_ARGVS]; char *Sys_GetModuleName(void) { static char exename[1024] = {0}; // static int i; if (exename[0]) return exename; if(!(i = GetModuleFileName(NULL, exename, sizeof(exename)-1))) // here we get loong string, with full path { exename[0] = 0; // oh, something bad } else { exename[i] = 0; // ensure null terminator } return exename; } #ifdef _CONSOLE void ParseCommandLine (int ac, char **av) { argc = 1; argv[0] = Sys_GetModuleName(); for( ; argc < MAX_NUM_ARGVS && ac > 0; argc++, ac--) { argv[argc] = av[argc-1]; } } #else void ParseCommandLine (char *lpCmdLine) { argc = 1; argv[0] = Sys_GetModuleName(); while (*lpCmdLine && (argc < MAX_NUM_ARGVS)) { while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126))) lpCmdLine++; if (*lpCmdLine) { if (*lpCmdLine == '\"') { lpCmdLine++; argv[argc] = lpCmdLine; argc++; while (*lpCmdLine && *lpCmdLine != '\"') // this include chars less that 32 and greate than 126... is that evil? lpCmdLine++; } else { argv[argc] = lpCmdLine; argc++; while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126))) lpCmdLine++; } if (*lpCmdLine) { *lpCmdLine = 0; lpCmdLine++; } } } } #endif /* ================ Sys_FileTime ================ */ int Sys_FileTime (const char *path) { struct _stat buf; return _stat (path, &buf) == -1 ? -1 : buf.st_mtime; } /* ================ Sys_mkdir ================ */ void Sys_mkdir (const char *path) { _mkdir(path); } /* ================ Sys_remove ================ */ int Sys_remove (const char *path) { return remove(path); } //bliP: rmdir -> /* ================ Sys_rmdir ================ */ int Sys_rmdir (const char *path) { return _rmdir(path); } //<- /* ================ Sys_listdir ================ */ dir_t Sys_listdir (const char *path, const char *ext, int sort_type) { static file_t list[MAX_DIRFILES]; dir_t dir; HANDLE h; WIN32_FIND_DATA fd; char pathname[MAX_DEMO_NAME]; qbool all; int r; pcre *preg; const char *errbuf; memset(list, 0, sizeof(list)); memset(&dir, 0, sizeof(dir)); dir.files = list; all = !strncmp(ext, ".*", 3); if (!all) if (!(preg = pcre_compile(ext, PCRE_CASELESS, &errbuf, &r, NULL))) { Con_Printf("Sys_listdir: pcre_compile(%s) error: %s at offset %d\n", ext, errbuf, r); Q_free(preg); return dir; } snprintf(pathname, sizeof(pathname), "%s/*.*", path); if ((h = FindFirstFile (pathname , &fd)) == INVALID_HANDLE_VALUE) { if (!all) Q_free(preg); return dir; } do { if (!strncmp(fd.cFileName, ".", 2) || !strncmp(fd.cFileName, "..", 3)) continue; if (!all) { switch (r = pcre_exec(preg, NULL, fd.cFileName, strlen(fd.cFileName), 0, 0, NULL, 0)) { case 0: break; case PCRE_ERROR_NOMATCH: continue; default: Con_Printf("Sys_listdir: pcre_exec(%s, %s) error code: %d\n", ext, fd.cFileName, r); if (!all) Q_free(preg); return dir; } } if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //bliP: list dir { dir.numdirs++; list[dir.numfiles].isdir = true; list[dir.numfiles].size = list[dir.numfiles].time = 0; } else { list[dir.numfiles].isdir = false; snprintf(pathname, sizeof(pathname), "%s/%s", path, fd.cFileName); list[dir.numfiles].time = Sys_FileTime(pathname); dir.size += (list[dir.numfiles].size = fd.nFileSizeLow); } strlcpy (list[dir.numfiles].name, fd.cFileName, sizeof(list[0].name)); if (++dir.numfiles == MAX_DIRFILES - 1) break; } while (FindNextFile(h, &fd)); FindClose (h); if (!all) Q_free(preg); switch (sort_type) { case SORT_NO: break; case SORT_BY_DATE: qsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_date); break; case SORT_BY_NAME: qsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_name); break; } return dir; } int Sys_EnumerateFiles (char *gpath, char *match, int (*func)(char *, int, void *), void *parm) { HANDLE r; WIN32_FIND_DATA fd; char apath[MAX_OSPATH]; char apath2[MAX_OSPATH]; char file[MAX_OSPATH]; char *s; int go; if (!gpath) return 0; snprintf(apath, sizeof(apath), "%s/%s", gpath, match); for (s = apath+strlen(apath)-1; s> apath; s--) { if (*s == '/') break; } *s = '\0'; // This is what we ask windows for. snprintf(file, sizeof(file), "%s/*.*", apath); // We need to make apath contain the path in match but not gpath strlcpy(apath2, match, sizeof(apath)); match = s+1; for (s = apath2+strlen(apath2)-1; s> apath2; s--) { if (*s == '/') break; } *s = '\0'; if (s != apath2) strlcat (apath2, "/", sizeof (apath2)); r = FindFirstFile(file, &fd); if (r==(HANDLE)-1) return 1; go = true; do { if (*fd.cFileName == '.'); // Don't ever find files with a name starting with '.' else if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //is a directory { if (wildcmp(match, fd.cFileName)) { snprintf(file, sizeof(file), "%s%s/", apath2, fd.cFileName); go = func(file, fd.nFileSizeLow, parm); } } else { if (wildcmp(match, fd.cFileName)) { snprintf(file, sizeof(file), "%s%s", apath2, fd.cFileName); go = func(file, fd.nFileSizeLow, parm); } } } while(FindNextFile(r, &fd) && go); FindClose(r); return go; } /* ================ Sys_Exit ================ */ void Sys_Exit(int code) { #ifndef _CONSOLE RemoveNotifyIcon(); #endif exit(code); } /* ================ Sys_Quit ================ */ void myInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) { // nothing } void Sys_Quit (qbool restart) { if (restart) { #ifndef __MINGW32__ int maxfd = 131072; // well, should be enough for everyone... _set_invalid_parameter_handler(myInvalidParameterHandler); // so close() does not crash our program on invalid handle... // close all file descriptors even stdin stdout and stderr, seems that not hurt... for (; maxfd > -1; maxfd--) { close(maxfd); closesocket(maxfd); // yeah, windows separate sockets and files, so you can't close socket with close() like on *nix. } if (execv(argv[0], com_argv) == -1) #endif { #ifdef _CONSOLE if (!((int)sys_nostdout.value || isdaemon)) printf("Restart failed: (%i): %s\n", qerrno, strerror(qerrno)); #else if (!(COM_CheckParm("-noerrormsgbox") || isdaemon)) MessageBox(NULL, strerror(qerrno), "Restart failed", 0 /* MB_OK */ ); #endif Sys_Exit(1); } } Sys_Exit(0); } /* ================ Sys_Error ================ */ void Sys_Error (const char *error, ...) { static qbool inerror = false; va_list argptr; char text[1024]; sv_error = true; if (inerror) Sys_Exit (1); inerror = true; va_start (argptr, error); vsnprintf (text, sizeof (text), error, argptr); va_end (argptr); #ifdef _CONSOLE if (!((int)sys_nostdout.value || isdaemon)) printf ("ERROR: %s\n", text); #else if (!(COM_CheckParm ("-noerrormsgbox") || isdaemon)) MessageBox (NULL, text, "Error", 0 /* MB_OK */ ); else Sys_Printf ("ERROR: %s\n", text); #endif if (logs[ERROR_LOG].sv_logfile) { SV_Write_Log (ERROR_LOG, 1, va ("ERROR: %s\n", text)); // fclose (logs[ERROR_LOG].sv_logfile); } // FIXME: hack - checking SV_Shutdown with svs.socketip set in -1 NET_Shutdown if (svs.socketip != -1) SV_Shutdown (va("ERROR: %s\n", text)); if ((int)sys_restart_on_error.value) Sys_Quit (true); Sys_Exit (1); } static double pfreq; static qbool hwtimer = false; static __int64 startcount; void Sys_InitDoubleTime (void) { __int64 freq; if (!COM_CheckParm("-nohwtimer") && QueryPerformanceFrequency ((LARGE_INTEGER *)&freq) && freq > 0) { // hardware timer available pfreq = (double)freq; hwtimer = true; QueryPerformanceCounter ((LARGE_INTEGER *)&startcount); } else { // make sure the timer is high precision, otherwise // NT gets 18ms resolution timeBeginPeriod (1); } } double Sys_DoubleTime (void) { __int64 pcount; static DWORD starttime; static qbool first = true; DWORD now; if (hwtimer) { QueryPerformanceCounter ((LARGE_INTEGER *)&pcount); if (first) { first = false; startcount = pcount; return 0.0; } // TODO: check for wrapping; is it necessary? return (pcount - startcount) / pfreq; } now = timeGetTime(); if (first) { first = false; starttime = now; return 0.0; } if (now < starttime) // wrapped? return (now / 1000.0) + (LONG_MAX - starttime / 1000.0); if (now - starttime == 0) return 0.0; return (now - starttime) / 1000.0; } /* ================ Sys_ConsoleInput ================ */ char *Sys_ConsoleInput (void) { #ifdef _CONSOLE static char text[256], *t; static int len = 0; int c; // read a line out if (!isdaemon) while (_kbhit()) { c = _getch(); if (c == 224) { if (_kbhit()) { // assume escape sequence (arrows etc), skip _getch(); continue; } // assume character } if (c < 32 && c != '\r' && c != 8) continue; putch (c); if (c == '\r') { text[len] = 0; putch ('\n'); len = 0; return text; } if (c == 8) { if (len) { putch (' '); putch (c); len--; text[len] = 0; } continue; } text[len] = c; len++; text[len] = 0; if (len == sizeof(text)) len = 0; } #endif // If you searching where input added under non console application, then you should check DialogFunc WM_COMMAND. return NULL; } /* ================ Sys_Printf ================ */ void Sys_Printf(char* fmt, ...) { va_list argptr; char text[MAXCMDBUF]; char* startpos; char* endpos; date_t date; #ifdef _CONSOLE if ((int)sys_nostdout.value) { return; } #endif if (isdaemon) { return; } va_start(argptr, fmt); vsnprintf(text, MAXCMDBUF, fmt, argptr); va_end(argptr); // normalize text before add to console. Q_normalizetext((char*)text); #ifndef _CONSOLE ConsoleAddText(text); #else SV_TimeOfDay(&date, "%Y-%m-%d %H:%M:%S"); startpos = text; while (startpos && startpos[0]) { endpos = strchr(startpos, '\n'); if (endpos) { *endpos = '\0'; } fprintf(stdout, "[%s] %s\n", date.str, startpos); fflush(stdout); if (endpos) { startpos = endpos + 1; if (startpos[0] == (char)10) { ++startpos; } } else { break; } } #endif //_CONSOLE } /* ============= Sys_Init Quake calls this so the system can register variables before host_hunklevel is marked ============= */ void Sys_Init (void) { qbool WinNT; OSVERSIONINFO vinfo; // make sure the timer is high precision, otherwise // NT gets 18ms resolution timeBeginPeriod( 1 ); vinfo.dwOSVersionInfoSize = sizeof(vinfo); if (!GetVersionEx (&vinfo)) Sys_Error ("Couldn't get OS info"); if (vinfo.dwMajorVersion < 4 || vinfo.dwPlatformId == VER_PLATFORM_WIN32s) Sys_Error (SERVER_NAME " requires at least Win95 or NT 4.0"); WinNT = (vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT ? true : false); Cvar_Register (&sys_nostdout); Cvar_Register (&sys_sleep); if (COM_CheckParm ("-nopriority")) { Cvar_Set (&sys_sleep, "0"); } else { if ( ! SetPriorityClass (GetCurrentProcess(), HIGH_PRIORITY_CLASS)) Con_Printf ("SetPriorityClass() failed\n"); else Con_Printf ("Process priority class set to HIGH\n"); // sys_sleep > 0 seems to cause packet loss on WinNT (why?) if (WinNT) Cvar_Set (&sys_sleep, "0"); } Sys_InitDoubleTime (); } void Sys_Sleep (unsigned long ms) { Sleep (ms); } int Sys_Script (const char *path, const char *args) { STARTUPINFO si; PROCESS_INFORMATION pi; char cmdline[1024], curdir[MAX_OSPATH]; memset (&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOWMINNOACTIVE; GetCurrentDirectory(sizeof(curdir), curdir); snprintf(cmdline, sizeof(cmdline), "%s\\sh.exe %s.qws %s", curdir, path, args); strlcat(curdir, va("\\%s", fs_gamedir+2), MAX_OSPATH); return CreateProcess (NULL, cmdline, NULL, NULL, FALSE, 0/*DETACHED_PROCESS /*CREATE_NEW_CONSOLE*/ , NULL, curdir, &si, &pi); } DL_t Sys_DLOpen (const char *path) { return LoadLibrary (path); } qbool Sys_DLClose (DL_t dl) { return FreeLibrary (dl); } void *Sys_DLProc (DL_t dl, const char *name) { return (void *) GetProcAddress (dl, name); } int Sys_CreateThread(DWORD (WINAPI *func)(void *), void *param) { DWORD threadid; HANDLE thread; thread = CreateThread ( NULL, // pointer to security attributes 0, // initial thread stack size func, // pointer to thread function param, // argument for new thread CREATE_SUSPENDED, // creation flags &threadid); // pointer to receive thread ID ResumeThread(thread); return 1; } #ifdef _CONSOLE /* ================== main ================== */ int main(int ac, char *av[]) { double newtime, time, oldtime; int sleep_msec; ParseCommandLine (ac, av); COM_InitArgv (argc, argv); GetConsoleTitle(title, sizeof(title)); Host_Init(argc, argv, DEFAULT_MEM_SIZE); // run one frame immediately for first heartbeat SV_Frame (0.1); // // main loop // oldtime = Sys_DoubleTime () - 0.1; while (1) { sleep_msec = (int)sys_sleep.value; if (sleep_msec > 0) { if (sleep_msec > 13) sleep_msec = 13; Sleep (sleep_msec); } // select on the net socket and stdin // the only reason we have a timeout at all is so that if the last // connected client times out, the message would not otherwise // be printed until the next event. if (!sys_simulation.value) { NET_Sleep((int)sys_select_timeout.value / 1000, false); } // find time passed since last cycle newtime = Sys_DoubleTime (); time = newtime - oldtime; oldtime = newtime; curtime = newtime; SV_Frame (time); } return true; } #else // _CONSOLE int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { static MSG msg; static double newtime, time, oldtime; register int sleep_msec; static qbool disable_gpf = false; ParseCommandLine(lpCmdLine); COM_InitArgv (argc, argv); // create main window if (!CreateMainWindow(hInstance, nCmdShow)) return 1; if (COM_CheckParm("-noerrormsgbox")) disable_gpf = true; if (COM_CheckParm ("-d")) { isdaemon = disable_gpf = true; //close(0); close(1); close(2); } if (disable_gpf) { DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX); SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX); } Host_Init(argc, argv, DEFAULT_MEM_SIZE); // if stared miminize update notify icon message (with correct port) if (minimized) UpdateNotifyIconMessage(va(SERVER_NAME ":%d", NET_UDPSVPort())); // run one frame immediately for first heartbeat SV_Frame (0.1); // // main loop // oldtime = Sys_DoubleTime () - 0.1; while(1) { // get messeges sent to windows if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) ) { if( !GetMessage( &msg, NULL, 0, 0 ) ) break; if(!IsDialogMessage(DlgHwnd, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } CheckIdle(); // server frame sleep_msec = (int)sys_sleep.value; if (sleep_msec > 0) { if (sleep_msec > 13) sleep_msec = 13; Sleep (sleep_msec); } // select on the net socket and stdin // the only reason we have a timeout at all is so that if the last // connected client times out, the message would not otherwise // be printed until the next event. if (!sys_simulation.value) { NET_Sleep((int)sys_select_timeout.value / 1000, false); } // find time passed since last cycle newtime = Sys_DoubleTime (); time = newtime - oldtime; oldtime = newtime; curtime = newtime; SV_Frame (time); } Sys_Exit(msg.wParam); return msg.wParam; } #endif // _CONSOLE mvdsv-0.35/src/sv_user.c000066400000000000000000004033271427146041000152310ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // sv_user.c -- server code for moving users #ifndef CLIENTONLY #include "qwsvdef.h" static void SV_ClientDownloadComplete(client_t* cl); edict_t *sv_player; usercmd_t cmd; cvar_t sv_spectalk = {"sv_spectalk", "1"}; cvar_t sv_sayteam_to_spec = {"sv_sayteam_to_spec", "1"}; cvar_t sv_mapcheck = {"sv_mapcheck", "1"}; cvar_t sv_minping = {"sv_minping", "0"}; cvar_t sv_maxping = {"sv_maxping", "0"}; cvar_t sv_enable_cmd_minping = {"sv_enable_cmd_minping", "1"}; cvar_t sv_kickuserinfospamtime = {"sv_kickuserinfospamtime", "3"}; cvar_t sv_kickuserinfospamcount = {"sv_kickuserinfospamcount", "300"}; cvar_t sv_maxuploadsize = {"sv_maxuploadsize", "1048576"}; #ifdef FTE_PEXT_CHUNKEDDOWNLOADS cvar_t sv_downloadchunksperframe = {"sv_downloadchunksperframe", "2"}; #endif #ifdef FTE_PEXT2_VOICECHAT // Enable reception of voice packets. cvar_t sv_voip = {"sv_voip", "1"}; // Record voicechat into mvds. Requires player support. 0=noone, 1=everyone, 2=spectators only. cvar_t sv_voip_record = {"sv_voip_record", "0"}; // Echo voice packets back to their sender, a debug/test setting. cvar_t sv_voip_echo = {"sv_voip_echo", "0"}; #endif #ifdef MVD_PEXT1_SERVERSIDEWEAPON static qbool SV_ClientExtensionWeaponSwitch(client_t* cl); #endif #ifdef MVD_PEXT1_DEBUG typedef struct { int msec; byte model; vec3_t pos; qbool present; } antilag_client_info_t; static struct { antilag_client_info_t antilag_clients[MAX_CLIENTS]; } debug_info; static void SV_DebugServerSideWeaponScript(client_t* cl, int best_impulse); static void SV_DebugServerSideWeaponInstruction(client_t* cl); cvar_t sv_debug_weapons = { "sv_debug_weapons", "0" }; #endif // These don't need any protocol extensions cvar_t sv_debug_usercmd = { "sv_debug_usercmd", "0" }; cvar_t sv_debug_antilag = { "sv_debug_antilag", "0" }; static void SV_UserSetWeaponRank(client_t* cl, const char* new_wrank); static void SV_DebugClientCommand(byte playernum, const usercmd_t* cmd, int dropnum_); extern vec3_t player_mins; extern int fp_messages, fp_persecond, fp_secondsdead; extern char fp_msg[]; extern cvar_t pausable; extern cvar_t pm_bunnyspeedcap; extern cvar_t pm_ktjump; extern cvar_t pm_slidefix; extern cvar_t pm_airstep; extern cvar_t pm_pground; extern cvar_t pm_rampjump; extern double sv_frametime; //bliP: init -> extern cvar_t sv_unfake; //bliP: 24/9 kickfake to unfake extern cvar_t sv_kicktop; extern cvar_t sv_speedcheck; //bliP: 24/9 //<- static void OnChange_sv_maxpitch (cvar_t *var, char *str, qbool *cancel); static void OnChange_sv_minpitch (cvar_t *var, char *str, qbool *cancel); cvar_t sv_maxpitch = {"sv_maxpitch", "80", 0, OnChange_sv_maxpitch}; cvar_t sv_minpitch = {"sv_minpitch", "-70", 0, OnChange_sv_minpitch}; static void SetUpClientEdict (client_t *cl, edict_t *ent); static qbool IsLocalIP(netadr_t a) { return a.ip[0] == 10 || (a.ip[0] == 172 && (a.ip[1] & 0xF0) == 16) || (a.ip[0] == 192 && a.ip[1] == 168) || a.ip[0] >= 224; } static qbool IsInetIP(netadr_t a) { return a.ip[0] != 127 && !IsLocalIP(a); } // // pitch clamping // // All this OnChange code is because we want the cvar names to have sv_ prefixes, // but don't want them in serverinfo (save a couple of bytes of space) // Value sanity checks are also done here // static void OnChange_sv_maxpitch (cvar_t *var, char *str, qbool *cancel) { float newval; char *newstr; *cancel = true; newval = bound (0, Q_atof(str), 89.9f); if (newval == var->value) return; Cvar_SetValue (var, newval); newstr = (newval == 80.0f) ? (char *)"" : var->string; // don't show default values in serverinfo SV_ServerinfoChanged("maxpitch", newstr); } static void OnChange_sv_minpitch (cvar_t *var, char *str, qbool *cancel) { float newval; char *newstr; *cancel = true; newval = bound (-89.9f, Q_atof(str), 0.0f); if (newval == var->value) return; Cvar_SetValue (var, newval); newstr = (newval == -70.0f) ? (char *)"" : var->string; // don't show default values in serverinfo SV_ServerinfoChanged("minpitch", newstr); } /* ============================================================ USER STRINGCMD EXECUTION sv_client and sv_player will be valid. ============================================================ */ /* ================== PlayerCheckPing Check that player's ping falls below sv_maxping value ================== */ qbool PlayerCheckPing(void) { if (sv_client->maxping_met) return true; int maxping = Q_atof(sv_maxping.string); int playerping = sv_client->frames[sv_client->netchan.incoming_acknowledged & UPDATE_MASK].ping_time * 1000; if (maxping && playerping > maxping) { SV_ClientPrintf(sv_client, PRINT_HIGH, "\nYour ping is too high for this server! Maximum ping is set to %i, your ping is %i.\nForcing spectator.\n\n", maxping, playerping); return false; } sv_client->maxping_met = true; return true; } /* ================ Cmd_New_f Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each server load. ================ */ int SV_VIPbyIP (netadr_t adr); static void Cmd_New_f (void) { char *gamedir; int playernum; extern cvar_t sv_serverip; extern cvar_t sv_getrealip; if (sv_client->state == cs_spawned) return; if (!SV_ClientConnectedTime(sv_client) || sv_client->state == cs_connected) { SV_SetClientConnectionTime(sv_client); } sv_client->spawncount = svs.spawncount; // request protocol extensions. if (sv_client->process_pext) { MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, "cmd pext\n"); return; } // do not proceed if realip is unknown if (sv_client->state == cs_preconnected && !sv_client->realip.ip[0] && (int)sv_getrealip.value) { char *server_ip = sv_serverip.string[0] ? sv_serverip.string : NET_AdrToString(net_local_sv_ipadr); if (!((IsLocalIP(net_local_sv_ipadr) && IsLocalIP(sv_client->netchan.remote_address)) || (IsInetIP (net_local_sv_ipadr) && IsInetIP (sv_client->netchan.remote_address))) && sv_client->netchan.remote_address.ip[0] != 127 && !sv_serverip.string[0]) { Sys_Printf ("WARNING: Incorrect server ip address: %s\n" "Set hostname in your operation system or set correctly sv_serverip cvar.\n", server_ip); *(int *)&sv_client->realip = *(int *)&sv_client->netchan.remote_address; sv_client->state = cs_connected; } else { if (sv_client->realip_count++ < 10) { sv_client->state = cs_preconnected; MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("packet %s \"ip %d %d\"\ncmd new\n", server_ip, sv_client - svs.clients, sv_client->realip_num)); } if (SV_ClientConnectedTime(sv_client) > 3 || sv_client->realip_count > 10) { if ((int)sv_getrealip.value == 2) { Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c\nFailed to validate client's IP.\n\n", A2C_PRINT); sv_client->rip_vip = 2; } sv_client->state = cs_connected; } else { return; } } } // rip_vip means that client can be connected if he has VIP for he's real ip // drop him if he hasn't if (sv_client->rip_vip == 1) { if ((sv_client->vip = SV_VIPbyIP(sv_client->realip)) == 0) { Sys_Printf ("%s:full connect\n", NET_AdrToString (net_from)); Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c\nserver is full\n\n", A2C_PRINT); } else sv_client->rip_vip = 0; } // we can be connected now, announce it, and possibly login if (!sv_client->rip_vip) { if (sv_client->state == cs_preconnected) { // get highest VIP level if (sv_client->vip < SV_VIPbyIP(sv_client->realip)) sv_client->vip = SV_VIPbyIP(sv_client->realip); if (sv_client->vip && sv_client->spectator) Sys_Printf ("VIP spectator %s connected\n", sv_client->name); else if (sv_client->spectator) Sys_Printf ("Spectator %s connected\n", sv_client->name); else Sys_Printf ("Client %s connected\n", sv_client->name); Info_SetStar (&sv_client->_userinfo_ctx_, "*VIP", sv_client->vip ? va("%d", sv_client->vip) : ""); // now we are connected sv_client->state = cs_connected; } if (!SV_Login(sv_client)) return; // If logins are mandatory, check if (SV_LoginRequired(sv_client)) { return; } //bliP: cuff, mute -> sv_client->lockedtill = SV_RestorePenaltyFilter(sv_client, ft_mute); sv_client->cuff_time = SV_RestorePenaltyFilter(sv_client, ft_cuff); //<- } // send the info about the new client to all connected clients // SV_FullClientUpdate (sv_client, &sv.reliable_datagram); // sv_client->sendinfo = true; gamedir = Info_ValueForKey (svs.info, "*gamedir"); if (!gamedir[0]) gamedir = "qw"; #ifdef FTE_PEXT_FLOATCOORDS if (msg_coordsize > 2 && !(sv_client->fteprotocolextensions & FTE_PEXT_FLOATCOORDS)) { SV_ClientPrintf(sv_client, 2, "\n\n\n\n" "Your client lacks the necessary extensions\n" " to connect to this server.\n" "Set /cl_pext_floatcoords 1, or upgrade.\n" "Please upgrade to one of the following:\n" "> ezQuake 2.2 (https://ezquake.github.io)\n" "> fodquake 0.4 (http://fodquake.net)\n" "> FTEQW (http://fte.triptohell.info/)\n"); if (!sv_client->spectator) { SV_DropClient (sv_client); return; } if (!SV_SkipCommsBotMessage(sv_client)) { return; } } #endif #ifdef FTE_PEXT_ENTITYDBL if (sv.max_edicts > 512 && !(sv_client->fteprotocolextensions & FTE_PEXT_ENTITYDBL)) { SV_ClientPrintf(sv_client, 2, "\n\nWARNING:\n" "Your client lacks support for extended\n" " entity limits, some enemies/projectiles\n" " may be invisible to you.\n" "Please upgrade to one of the following:\n" "> ezQuake 2.2 (https://ezquake.github.io)\n" "> fodquake 0.4 (http://fodquake.net)\n" "> FTEQW (http://fte.triptohell.info/)\n"); } #endif //NOTE: This doesn't go through ClientReliableWrite since it's before the user //spawns. These functions are written to not overflow if (sv_client->num_backbuf) { Con_Printf("WARNING %s: [SV_New] Back buffered (%d0), clearing\n", sv_client->name, sv_client->netchan.message.cursize); sv_client->num_backbuf = 0; SZ_Clear(&sv_client->netchan.message); } // send the serverdata MSG_WriteByte (&sv_client->netchan.message, svc_serverdata); #ifdef PROTOCOL_VERSION_FTE if (sv_client->fteprotocolextensions) // let the client know { unsigned int ext = sv_client->fteprotocolextensions; #ifdef FTE_PEXT_FLOATCOORDS if (msg_coordsize == 2) //we're not using float orgs on this level. ext &= ~FTE_PEXT_FLOATCOORDS; #endif MSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION_FTE); MSG_WriteLong (&sv_client->netchan.message, ext); } #endif #ifdef PROTOCOL_VERSION_FTE2 if (sv_client->fteprotocolextensions2) // let the client know { MSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION_FTE2); MSG_WriteLong (&sv_client->netchan.message, sv_client->fteprotocolextensions2); } #endif // PROTOCOL_VERSION_FTE2 #ifdef PROTOCOL_VERSION_MVD1 if (sv_client->mvdprotocolextensions1) { MSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION_MVD1); MSG_WriteLong (&sv_client->netchan.message, sv_client->mvdprotocolextensions1); } #endif MSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION); MSG_WriteLong (&sv_client->netchan.message, svs.spawncount); MSG_WriteString(&sv_client->netchan.message, gamedir); playernum = NUM_FOR_EDICT(sv_client->edict)-1; if (sv_client->spectator) playernum |= 128; MSG_WriteByte (&sv_client->netchan.message, playernum); // send full levelname if (sv_client->rip_vip) MSG_WriteString (&sv_client->netchan.message, ""); else MSG_WriteString (&sv_client->netchan.message, PR_GetEntityString(sv.edicts->v->message)); // send the movevars MSG_WriteFloat(&sv_client->netchan.message, movevars.gravity); MSG_WriteFloat(&sv_client->netchan.message, movevars.stopspeed); MSG_WriteFloat(&sv_client->netchan.message, /* sv_client->maxspeed */ movevars.maxspeed); // FIXME: this does't work, Tonik? MSG_WriteFloat(&sv_client->netchan.message, movevars.spectatormaxspeed); MSG_WriteFloat(&sv_client->netchan.message, movevars.accelerate); MSG_WriteFloat(&sv_client->netchan.message, movevars.airaccelerate); MSG_WriteFloat(&sv_client->netchan.message, movevars.wateraccelerate); MSG_WriteFloat(&sv_client->netchan.message, movevars.friction); MSG_WriteFloat(&sv_client->netchan.message, movevars.waterfriction); MSG_WriteFloat(&sv_client->netchan.message, /* sv_client->entgravity */ movevars.entgravity); // FIXME: this does't work, Tonik? if (!sv_client->spectator) { if (!PlayerCheckPing()) { sv_client->old_frags = 0; SV_SetClientConnectionTime(sv_client); sv_client->spectator = true; sv_client->spec_track = 0; Info_SetStar(&sv_client->_userinfo_ctx_, "*spectator", "1"); Info_SetStar(&sv_client->_userinfoshort_ctx_, "*spectator", "1"); } } if (sv_client->rip_vip) { SV_LogPlayer(sv_client, va("dropped %d", sv_client->rip_vip), 1); SV_DropClient (sv_client); return; } // send music MSG_WriteByte (&sv_client->netchan.message, svc_cdtrack); MSG_WriteByte (&sv_client->netchan.message, sv.edicts->v->sounds); // send server info string MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("fullserverinfo \"%s\"\n", svs.info) ); //bliP: player logging SV_LogPlayer(sv_client, "connect", 1); } /* ================== Cmd_Soundlist_f ================== */ static void Cmd_Soundlist_f (void) { char **s; unsigned n; if (sv_client->state != cs_connected) { Con_Printf ("soundlist not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if (Q_atoi(Cmd_Argv(1)) != svs.spawncount) { SV_ClearReliable (sv_client); Con_Printf ("SV_Soundlist_f from different level\n"); Cmd_New_f (); return; } n = Q_atoi(Cmd_Argv(2)); if (n >= MAX_SOUNDS) { SV_ClearReliable (sv_client); SV_ClientPrintf (sv_client, PRINT_HIGH, "SV_Soundlist_f: Invalid soundlist index\n"); SV_DropClient (sv_client); return; } //NOTE: This doesn't go through ClientReliableWrite since it's before the user //spawns. These functions are written to not overflow if (sv_client->num_backbuf) { Con_Printf("WARNING %s: [SV_Soundlist] Back buffered (%d0), clearing\n", sv_client->name, sv_client->netchan.message.cursize); sv_client->num_backbuf = 0; SZ_Clear(&sv_client->netchan.message); } MSG_WriteByte (&sv_client->netchan.message, svc_soundlist); MSG_WriteByte (&sv_client->netchan.message, n); for (s = sv.sound_precache+1 + n ; *s && sv_client->netchan.message.cursize < (MAX_MSGLEN/2); s++, n++) MSG_WriteString (&sv_client->netchan.message, *s); MSG_WriteByte (&sv_client->netchan.message, 0); // next msg if (*s) MSG_WriteByte (&sv_client->netchan.message, n); else MSG_WriteByte (&sv_client->netchan.message, 0); } static char *TrimModelName (const char *full) { static char shortn[MAX_QPATH]; int len; if (!strncmp(full, "progs/", 6) && !strchr(full + 6, '/')) strlcpy (shortn, full + 6, sizeof(shortn)); // strip progs/ else strlcpy (shortn, full, sizeof(shortn)); len = strlen(shortn); if (len > 4 && !strcmp(shortn + len - 4, ".mdl") && strchr(shortn, '.') == shortn + len - 4) { // strip .mdl shortn[len - 4] = '\0'; } return shortn; } /* ================== Cmd_Modellist_f ================== */ static void Cmd_Modellist_f (void) { char **s; unsigned n; int i; unsigned maxclientsupportedmodels; if (sv_client->state != cs_connected) { Con_Printf ("modellist not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if (Q_atoi(Cmd_Argv(1)) != svs.spawncount) { SV_ClearReliable (sv_client); Con_Printf ("SV_Modellist_f from different level\n"); Cmd_New_f (); return; } n = Q_atoi(Cmd_Argv(2)); if (n >= MAX_MODELS) { SV_ClearReliable (sv_client); SV_ClientPrintf (sv_client, PRINT_HIGH, "SV_Modellist_f: Invalid modellist index\n"); SV_DropClient (sv_client); return; } if (n == 0 && (sv_client->extensions & Z_EXT_VWEP) && sv.vw_model_name[0]) { int i; char ss[1024] = "//vwep "; // send VWep precaches for (i = 0, s = sv.vw_model_name; i < MAX_VWEP_MODELS; s++, i++) { if (!*s || !**s) { break; } if (i > 0) { strlcat(ss, " ", sizeof(ss)); } strlcat(ss, TrimModelName(*s), sizeof(ss)); } strlcat(ss, "\n", sizeof(ss)); if (ss[strlen(ss) - 1] == '\n') { // didn't overflow? ClientReliableWrite_Begin (sv_client, svc_stufftext, 2 + strlen(ss)); ClientReliableWrite_String (sv_client, ss); } } //NOTE: This doesn't go through ClientReliableWrite since it's before the user //spawns. These functions are written to not overflow if (sv_client->num_backbuf) { Con_Printf("WARNING %s: [SV_Modellist] Back buffered (%d0), clearing\n", sv_client->name, sv_client->netchan.message.cursize); sv_client->num_backbuf = 1; SZ_Clear(&sv_client->netchan.message); } #ifdef FTE_PEXT_MODELDBL if (n > 255) { MSG_WriteByte(&sv_client->netchan.message, svc_fte_modellistshort); MSG_WriteShort(&sv_client->netchan.message, n); } else #endif { MSG_WriteByte(&sv_client->netchan.message, svc_modellist); MSG_WriteByte(&sv_client->netchan.message, n); } maxclientsupportedmodels = 256; #ifdef FTE_PEXT_MODELDBL if (sv_client->fteprotocolextensions & FTE_PEXT_MODELDBL) { maxclientsupportedmodels *= 2; } #endif s = sv.model_precache + 1 + n; for (i = 1 + n; i < maxclientsupportedmodels && *s && (((i-1)&255) == 0 || sv_client->netchan.message.cursize < (MAX_MSGLEN/2)); i++, s++) { MSG_WriteString (&sv_client->netchan.message, *s); } n = i - 1; if (!s[0] || n == maxclientsupportedmodels - 1) { n = 0; } // next msg (nul terminator then next request indicator) MSG_WriteByte (&sv_client->netchan.message, 0); MSG_WriteByte (&sv_client->netchan.message, n); } /* ================== Cmd_PreSpawn_f ================== */ static void Cmd_PreSpawn_f (void) { unsigned int buf; unsigned int check; int i, j; if (sv_client->state != cs_connected) { Con_Printf ("prespawn not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if (Q_atoi(Cmd_Argv(1)) != svs.spawncount) { SV_ClearReliable (sv_client); Con_Printf ("SV_PreSpawn_f from different level\n"); Cmd_New_f (); return; } buf = Q_atoi(Cmd_Argv(2)); if (buf >= sv.num_signon_buffers + sv.static_entity_count + sv.num_baseline_edicts) buf = 0; if (!buf) { // should be three numbers following containing checksums check = Q_atoi(Cmd_Argv(3)); // Con_DPrintf("Client check = %d\n", check); if ((int)sv_mapcheck.value && check != sv.map_checksum && check != sv.map_checksum2) { SV_ClientPrintf (sv_client, PRINT_HIGH, "Map model file does not match (%s), %i != %i/%i.\n" "You may need a new version of the map, or the proper install files.\n", sv.modelname, check, sv.map_checksum, sv.map_checksum2); SV_DropClient (sv_client); return; } sv_client->checksum = check; } if (SV_SkipCommsBotMessage(sv_client)) { // skip pre-spawning MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("cmd spawn %i 0\n", svs.spawncount) ); return; } //NOTE: This doesn't go through ClientReliableWrite since it's before the user //spawns. These functions are written to not overflow if (sv_client->num_backbuf) { Con_Printf("WARNING %s: [SV_PreSpawn] Back buffered (%d0), clearing\n", sv_client->name, sv_client->netchan.message.cursize); sv_client->num_backbuf = 0; SZ_Clear(&sv_client->netchan.message); } if (buf < sv.static_entity_count) { entity_state_t from = { 0 }; while (buf < sv.static_entity_count) { entity_state_t* s = &sv.static_entities[buf]; if (sv_client->netchan.message.cursize >= (sv_client->netchan.message.maxsize / 2)) { break; } if (sv_client->fteprotocolextensions & FTE_PEXT_SPAWNSTATIC2) { MSG_WriteByte(&sv_client->netchan.message, svc_fte_spawnstatic2); SV_WriteDelta(sv_client, &from, s, &sv_client->netchan.message, true); } else if (s->modelindex < 256) { MSG_WriteByte(&sv_client->netchan.message, svc_spawnstatic); MSG_WriteByte(&sv_client->netchan.message, s->modelindex); MSG_WriteByte(&sv_client->netchan.message, s->frame); MSG_WriteByte(&sv_client->netchan.message, s->colormap); MSG_WriteByte(&sv_client->netchan.message, s->skinnum); for (i = 0; i < 3; ++i) { MSG_WriteCoord(&sv_client->netchan.message, s->origin[i]); MSG_WriteAngle(&sv_client->netchan.message, s->angles[i]); } } ++buf; } } else if (buf < sv.static_entity_count + sv.num_baseline_edicts) { static entity_state_t empty_baseline = { 0 }; for (i = buf - sv.static_entity_count; i < sv.num_baseline_edicts; ++i) { edict_t* svent = EDICT_NUM(i); entity_state_t* s = &svent->e.baseline; if (sv_client->netchan.message.cursize >= (sv_client->netchan.message.maxsize / 2)) { break; } if (!s->number || !s->modelindex || !memcmp(s, &empty_baseline, sizeof(empty_baseline))) { ++buf; continue; } if (sv_client->fteprotocolextensions & FTE_PEXT_SPAWNSTATIC2) { MSG_WriteByte(&sv_client->netchan.message, svc_fte_spawnbaseline2); SV_WriteDelta(sv_client, &empty_baseline, s, &sv_client->netchan.message, true); } else if (s->modelindex < 256) { MSG_WriteByte(&sv_client->netchan.message, svc_spawnbaseline); MSG_WriteShort(&sv_client->netchan.message, i); MSG_WriteByte(&sv_client->netchan.message, svent->e.baseline.modelindex); MSG_WriteByte(&sv_client->netchan.message, svent->e.baseline.frame); MSG_WriteByte(&sv_client->netchan.message, svent->e.baseline.colormap); MSG_WriteByte(&sv_client->netchan.message, svent->e.baseline.skinnum); for (j = 0; j < 3; j++) { MSG_WriteCoord(&sv_client->netchan.message, svent->e.baseline.origin[j]); MSG_WriteAngle(&sv_client->netchan.message, svent->e.baseline.angles[j]); } } ++buf; } } else { SZ_Write( &sv_client->netchan.message, sv.signon_buffers[buf - sv.static_entity_count - sv.num_baseline_edicts], sv.signon_buffer_size[buf - sv.static_entity_count - sv.num_baseline_edicts] ); ++buf; } if (buf == sv.num_signon_buffers + sv.static_entity_count + sv.num_baseline_edicts) { // all done prespawning MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("cmd spawn %i 0\n", svs.spawncount) ); } else { // need to prespawn more MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("cmd prespawn %i %i\n", svs.spawncount, buf) ); } } /* ================== Cmd_Spawn_f ================== */ static void Cmd_Spawn_f (void) { int i; client_t *client; unsigned n; if (sv_client->state != cs_connected) { Con_Printf ("Spawn not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if (Q_atoi(Cmd_Argv(1)) != svs.spawncount) { SV_ClearReliable (sv_client); Con_Printf ("SV_Spawn_f from different level\n"); Cmd_New_f (); return; } n = Q_atoi(Cmd_Argv(2)); if (n >= MAX_CLIENTS) { SV_ClientPrintf (sv_client, PRINT_HIGH, "SV_Spawn_f: Invalid client start\n"); SV_DropClient (sv_client); return; } // send all current names, colors, and frag counts // FIXME: is this a good thing? SZ_Clear (&sv_client->netchan.message); // send current status of all other players // normally this could overflow, but no need to check due to backbuf for (i=n, client = svs.clients + n ; inetchan.message.cursize < (MAX_MSGLEN/2); i++, client++) SV_FullClientUpdateToClient (client, sv_client); if (i < MAX_CLIENTS) { MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("cmd spawn %i %d\n", svs.spawncount, i) ); return; } // send all current light styles for (i=0 ; iedict; sv_client->entgravity = fofs_gravity ? EdictFieldFloat(ent, fofs_gravity) : 1.0; sv_client->maxspeed = fofs_maxspeed? EdictFieldFloat(ent, fofs_maxspeed) : (int)sv_maxspeed.value; if (sv.paused & 1) SV_TogglePause (NULL, 1); } else { SetUpClientEdict(sv_client, sv_client->edict); } // // force stats to be updated // memset (sv_client->stats, 0, sizeof(sv_client->stats)); ClientReliableWrite_Begin (sv_client, svc_updatestatlong, 6); ClientReliableWrite_Byte (sv_client, STAT_TOTALSECRETS); ClientReliableWrite_Long (sv_client, PR_GLOBAL(total_secrets)); ClientReliableWrite_Begin (sv_client, svc_updatestatlong, 6); ClientReliableWrite_Byte (sv_client, STAT_TOTALMONSTERS); ClientReliableWrite_Long (sv_client, PR_GLOBAL(total_monsters)); ClientReliableWrite_Begin (sv_client, svc_updatestatlong, 6); ClientReliableWrite_Byte (sv_client, STAT_SECRETS); ClientReliableWrite_Long (sv_client, PR_GLOBAL(found_secrets)); ClientReliableWrite_Begin (sv_client, svc_updatestatlong, 6); ClientReliableWrite_Byte (sv_client, STAT_MONSTERS); ClientReliableWrite_Long (sv_client, PR_GLOBAL(killed_monsters)); // get the client to check and download skins // when that is completed, a begin command will be issued ClientReliableWrite_Begin (sv_client, svc_stufftext, 8); ClientReliableWrite_String (sv_client, "skins\n" ); } /* ================== SV_SpawnSpectator ================== */ static void SV_SpawnSpectator (void) { int i; edict_t *e; VectorClear (sv_player->v->origin); VectorClear (sv_player->v->view_ofs); sv_player->v->view_ofs[2] = 22; sv_player->v->fixangle = true; sv_player->v->movetype = MOVETYPE_NOCLIP; // progs can change this to MOVETYPE_FLY, for example // search for an info_playerstart to spawn the spectator at for (i=MAX_CLIENTS-1 ; iv->classname), "info_player_start")) { VectorCopy (e->v->origin, sv_player->v->origin); VectorCopy (e->v->angles, sv_player->v->angles); return; } } } /* ================== Cmd_Begin_f ================== */ static void Cmd_Begin_f (void) { unsigned pmodel = 0, emodel = 0; int i; if (sv_client->state == cs_spawned) return; // don't begin again // handle the case of a level changing while a client was connecting if (Q_atoi(Cmd_Argv(1)) != svs.spawncount) { SV_ClearReliable (sv_client); Con_Printf ("SV_Begin_f from different level\n"); Cmd_New_f (); return; } sv_client->state = cs_spawned; if (!sv.loadgame) { if (sv_client->spectator) SV_SpawnSpectator (); // copy spawn parms out of the client_t for (i=0 ; i< NUM_SPAWN_PARMS ; i++) (&PR_GLOBAL(parm1))[i] = sv_client->spawn_parms[i]; // call the spawn function pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); G_FLOAT(OFS_PARM0) = (float) sv_client->vip; PR_GameClientConnect(sv_client->spectator); // actually spawn the player pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); PR_GamePutClientInServer(sv_client->spectator); } // clear the net statistics, because connecting gives a bogus picture sv_client->netchan.frame_latency = 0; sv_client->netchan.frame_rate = 0; sv_client->netchan.drop_count = 0; sv_client->netchan.good_count = 0; //check he's not cheating if (!sv_client->spectator) { if (!*Info_Get (&sv_client->_userinfo_ctx_, "pmodel") || !*Info_Get (&sv_client->_userinfo_ctx_, "emodel")) //bliP: typo? 2nd pmodel to emodel SV_BroadcastPrintf (PRINT_HIGH, "%s WARNING: missing player/eyes model checksum\n", sv_client->name); else { pmodel = Q_atoi(Info_Get (&sv_client->_userinfo_ctx_, "pmodel")); emodel = Q_atoi(Info_Get (&sv_client->_userinfo_ctx_, "emodel")); if (!(pmodel == sv.model_newplayer_checksum || pmodel == sv.model_player_checksum) || emodel != sv.eyes_player_checksum) SV_BroadcastPrintf (PRINT_HIGH, "%s WARNING: non standard player/eyes model detected\n", sv_client->name); } } // if we are paused, tell the client if (sv.paused) { ClientReliableWrite_Begin (sv_client, svc_setpause, 2); ClientReliableWrite_Byte (sv_client, sv.paused); SV_ClientPrintf(sv_client, PRINT_HIGH, "Server is paused.\n"); } if (sv.loadgame) { // send a fixangle over the reliable channel to make sure it gets there // Never send a roll angle, because savegames can catch the server // in a state where it is expecting the client to correct the angle // and it won't happen if the game was just loaded, so you wind up // with a permanent head tilt edict_t *ent; ent = EDICT_NUM( 1 + (sv_client - svs.clients) ); MSG_WriteByte (&sv_client->netchan.message, svc_setangle); for (i = 0; i < 2; i++) MSG_WriteAngle (&sv_client->netchan.message, ent->v->v_angle[i]); MSG_WriteAngle (&sv_client->netchan.message, 0); } sv_client->lastservertimeupdate = -99; // update immediately } //============================================================================= /* ================== SV_DownloadNextFile ================== */ static qbool SV_DownloadNextFile (void) { int num; char *name, n[MAX_OSPATH]; unsigned char all_demos_downloaded[] = "All demos downloaded.\n"; unsigned char incorrect_demo_number[] = "Incorrect demo number.\n"; switch (sv_client->demonum[0]) { case 1: if (sv_client->demolist) { Con_Printf((char *)Q_redtext(all_demos_downloaded)); sv_client->demolist = false; } sv_client->demonum[0] = 0; case 0: return false; default:; } num = sv_client->demonum[--(sv_client->demonum[0])]; if (num == 0) { Con_Printf((char *)Q_redtext(incorrect_demo_number)); return SV_DownloadNextFile(); } if (!(name = SV_MVDNum(num))) { Con_Printf((char *)Q_yelltext((unsigned char*)va("Demo number %d not found.\n", (num & 0xFF000000) ? -(num >> 24) : ((num & 0x00800000) ? (num | 0xFF000000) : num) ))); return SV_DownloadNextFile(); } //Con_Printf("downloading demos/%s\n",name); snprintf(n, sizeof(n), "download demos/%s\n", name); ClientReliableWrite_Begin (sv_client, svc_stufftext, strlen(n) + 2); ClientReliableWrite_String (sv_client, n); return true; } /* ================== SV_CompleteDownoload ================== This is a sub routine for SV_NextDownload(), called when download complete, we set up some fields for sv_client. */ void SV_CompleteDownoload(void) { unsigned char download_completed[] = "Download completed.\n"; if (!sv_client->download) return; SV_ClientDownloadComplete(sv_client); Con_Printf((char *)Q_redtext(download_completed)); if (SV_DownloadNextFile()) return; // if map changed tell the client to reconnect if (sv_client->spawncount != svs.spawncount) { char *str = "changing\n" "reconnect\n"; ClientReliableWrite_Begin (sv_client, svc_stufftext, strlen(str)+2); ClientReliableWrite_String (sv_client, str); } } /* ================== Cmd_NextDownload_f ================== */ #ifdef FTE_PEXT_CHUNKEDDOWNLOADS // qqshka: percent is optional, u can't relay on it void SV_NextChunkedDownload(int chunknum, int percent, int chunked_download_number) { #define CHUNKSIZE 1024 char buffer[CHUNKSIZE]; int i; sv_client->file_percent = bound(0, percent, 100); //bliP: file percent if (chunknum < 0) { // qqshka: FTE's chunked download does't have any way of signaling what client complete dl-ing, so doing it this way. SV_CompleteDownoload(); return; } if (sv_client->download_chunks_perframe) { int maxchunks = bound(1, (int)sv_downloadchunksperframe.value, 4); // too much requests or client sent something wrong if (sv_client->download_chunks_perframe >= maxchunks || chunked_download_number < 1) return; } if (!sv_client->download_chunks_perframe) // ignore "rate" if not first packet per frame if (sv_client->datagram.cursize + CHUNKSIZE+5+50 > sv_client->datagram.maxsize) return; //choked! if (VFS_SEEK(sv_client->download, chunknum*CHUNKSIZE, SEEK_SET)) return; // FIXME: ERROR of some kind i = VFS_READ(sv_client->download, buffer, CHUNKSIZE, NULL); if (i > 0) { byte data[1+ (sizeof("\\chunk")-1) + 4 + 1 + 4 + CHUNKSIZE]; // byte + (sizeof("\\chunk")-1) + long + byte + long + CHUNKSIZE sizebuf_t *msg, msg_oob; if (sv_client->download_chunks_perframe) { msg = &msg_oob; SZ_Init (&msg_oob, data, sizeof(data)); MSG_WriteByte(msg, A2C_PRINT); SZ_Write(msg, "\\chunk", sizeof("\\chunk")-1); MSG_WriteLong(msg, chunked_download_number); // return back, so they sure what it proper chunk } else msg = &sv_client->datagram; if (i != CHUNKSIZE) memset(buffer+i, 0, CHUNKSIZE-i); MSG_WriteByte(msg, svc_download); MSG_WriteLong(msg, chunknum); SZ_Write(msg, buffer, CHUNKSIZE); if (sv_client->download_chunks_perframe) Netchan_OutOfBand (NS_SERVER, sv_client->netchan.remote_address, msg->cursize, msg->data); } else { ; // FIXME: EOF/READ ERROR } sv_client->download_chunks_perframe++; } #endif static void Cmd_NextDownload_f (void) { byte buffer[FILE_TRANSFER_BUF_SIZE]; int r, tmp; int percent; int size; double frametime; if (!sv_client->download) return; #ifdef FTE_PEXT_CHUNKEDDOWNLOADS if (sv_client->fteprotocolextensions & FTE_PEXT_CHUNKEDDOWNLOADS) { SV_NextChunkedDownload(atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2)), atoi(Cmd_Argv(3))); return; } #endif tmp = sv_client->downloadsize - sv_client->downloadcount; frametime = max(0.05, min(0, sv_client->netchan.frame_rate)); //Sys_Printf("rate:%f\n", sv_client->netchan.frame_rate); r = (int)((curtime + frametime - sv_client->netchan.cleartime)/sv_client->netchan.rate); if (r <= 10) r = 10; if (r > FILE_TRANSFER_BUF_SIZE) r = FILE_TRANSFER_BUF_SIZE; // don't send too much if already buffering if (sv_client->num_backbuf) r = 10; if (r > tmp) r = tmp; Con_DPrintf("Downloading: %d", r); r = VFS_READ(sv_client->download, buffer, r, NULL); Con_DPrintf(" => %d, total: %d => %d", r, sv_client->downloadsize, sv_client->downloadcount); ClientReliableWrite_Begin (sv_client, svc_download, 6 + r); ClientReliableWrite_Short (sv_client, r); sv_client->downloadcount += r; if (!(size = sv_client->downloadsize)) size = 1; percent = (sv_client->downloadcount * (double)100.) / size; if (percent == 100 && sv_client->downloadcount != sv_client->downloadsize) percent = 99; else if (percent != 100 && sv_client->downloadcount == sv_client->downloadsize) percent = 100; Con_DPrintf("; %d\n", percent); ClientReliableWrite_Byte (sv_client, percent); ClientReliableWrite_SZ (sv_client, buffer, r); sv_client->file_percent = percent; //bliP: file percent if (sv_client->downloadcount == sv_client->downloadsize) SV_CompleteDownoload(); } static void OutofBandPrintf(netadr_t where, char *fmt, ...) { va_list argptr; char send1[1024]; send1[0] = 0xff; send1[1] = 0xff; send1[2] = 0xff; send1[3] = 0xff; send1[4] = A2C_PRINT; va_start (argptr, fmt); vsnprintf (send1 + 5, sizeof(send1) - 5, fmt, argptr); va_end (argptr); NET_SendPacket (NS_SERVER, strlen(send1) + 1, send1, where); } /* ================== SV_NextUpload ================== */ void SV_ReplaceChar(char *s, char from, char to); void SV_CancelUpload(void) { SV_ClientPrintf(sv_client, PRINT_HIGH, "Upload denied\n"); ClientReliableWrite_Begin (sv_client, svc_stufftext, 8); ClientReliableWrite_String (sv_client, "stopul"); if (sv_client->upload) { fclose (sv_client->upload); sv_client->upload = NULL; sv_client->file_percent = 0; //bliP: file percent } } static void SV_NextUpload (void) { int percent; int size; char *name = sv_client->uploadfn; // Sys_Printf("-- %s\n", name); SV_ReplaceChar(name, '\\', '/'); if (!*name || !strncmp(name, "../", 3) || strstr(name, "/../") || *name == '/' #ifdef _WIN32 || (isalpha(*name) && name[1] == ':') #endif //_WIN32 ) { //bliP: can't upload back a directory SV_CancelUpload(); // suck out rest of packet size = MSG_ReadShort (); MSG_ReadByte (); if (size > 0) msg_readcount += size; return; } size = MSG_ReadShort (); sv_client->file_percent = percent = MSG_ReadByte (); if (size <= 0 || size >= MAX_DATAGRAM || percent < 0 || percent > 100) { SV_CancelUpload(); return; } if (sv_client->upload) { int pos = ftell(sv_client->upload); if (pos == -1 || (sv_client->remote_snap && (pos + size) > (int)sv_maxuploadsize.value)) { msg_readcount += size; SV_CancelUpload(); return; } } else { sv_client->upload = fopen(name, "wb"); if (!sv_client->upload) { Sys_Printf("Can't create %s\n", name); ClientReliableWrite_Begin (sv_client, svc_stufftext, 8); ClientReliableWrite_String (sv_client, "stopul"); *name = 0; return; } Sys_Printf("Receiving %s from %d...\n", name, sv_client->userid); if (sv_client->remote_snap) OutofBandPrintf(sv_client->snap_from, "Server receiving %s from %d...\n", name, sv_client->userid); // force cache rebuild. FS_FlushFSHash(); } Sys_Printf("-"); fwrite (net_message.data + msg_readcount, 1, size, sv_client->upload); msg_readcount += size; Con_DPrintf ("UPLOAD: %d received\n", size); if (percent != 100) { ClientReliableWrite_Begin (sv_client, svc_stufftext, 8); ClientReliableWrite_String (sv_client, "nextul\n"); } else { fclose (sv_client->upload); sv_client->upload = NULL; sv_client->file_percent = 0; //bliP: file percent Sys_Printf("\n%s upload completed.\n", name); if (sv_client->remote_snap) { char *p; if ((p = strchr(name, '/')) != NULL) p++; else p = name; OutofBandPrintf(sv_client->snap_from, "%s upload completed.\nTo download, enter:\ndownload %s\n", name, p); } } } static void SV_UserCleanFilename(char* name) { char* p; // lowercase name (needed for casesen file systems) for (p = name; *p; p++) { *p = (char)tolower(*p); } } /* ================== Cmd_Download_f ================== */ //void SV_ReplaceChar(char *s, char from, char to); static void Cmd_Download_f(void) { char *name, n[MAX_OSPATH], *val; char alternative_path[MAX_OSPATH]; extern cvar_t allow_download; extern cvar_t allow_download_skins; extern cvar_t allow_download_models; extern cvar_t allow_download_sounds; extern cvar_t allow_download_maps; extern cvar_t allow_download_pakmaps; extern cvar_t allow_download_demos; extern cvar_t allow_download_other; extern cvar_t download_map_url; //bliP: download url extern cvar_t sv_demoDir; int i; qbool allow_dl = false; if (Cmd_Argc() != 2) { Con_Printf("download [filename]\n"); return; } name = Cmd_Argv(1); SV_ReplaceChar(name, '\\', '/'); // couple of checks to not allow dl-ing anything except in quake dir if ( //TODO: split name to pathname and filename and check for 'bad symbols' only in pathname *name == '/' // no absolute || !strncmp(name, "../", 3) // no leading ../ || strstr(name, "/../") // no /../ || ((i = strlen(name)) < 3 ? 0 : !strncmp(name + i - 3, "/..", 4)) // no /.. at end || *name == '.' //relative is pointless || ((i = strlen(name)) < 4 ? 0 : !strncasecmp(name + i - 4, ".log", 5)) // no logs #ifdef _WIN32 // no leading X: || ( name[0] && name[1] == ':' && ((*name >= 'a' && *name <= 'z') || (*name >= 'A' && *name <= 'Z'))) #endif //_WIN32 ) goto deny_download; if (sv_client->special) allow_dl = true; // NOTE: user used techlogin, allow dl anything in quake dir in such case! else if (!strstr(name, "/")) allow_dl = false; // should be in subdir else if (!(int)allow_download.value) allow_dl = false; // global allow check else if (!strncmp(name, "skins/", 6)) allow_dl = allow_download_skins.value; // skins else if (!strncmp(name, "progs/", 6)) allow_dl = allow_download_models.value; // models else if (!strncmp(name, "sound/", 6)) allow_dl = allow_download_sounds.value; // sounds else if (!strncmp(name, "maps/", 5)) // maps, note usage of allow_download_pakmaps a bit below allow_dl = allow_download_maps.value; // maps else if (!strncmp(name, "demos/", 6) || !strncmp(name, "demonum/", 8)) allow_dl = allow_download_demos.value; // demos else allow_dl = allow_download_other.value; // all other stuff if (!allow_dl) goto deny_download; SV_ClientDownloadComplete(sv_client); memset(alternative_path, 0, sizeof(alternative_path)); if ( !strncmp(name, "demos/", 6) && sv_demoDir.string[0]) { snprintf(n,sizeof(n), "%s/%s", sv_demoDir.string, name + 6); name = n; if (sv_demoDirAlt.string[0]) { strlcpy(alternative_path, sv_demoDirAlt.string, sizeof(alternative_path)); strlcat(alternative_path, "/", sizeof(alternative_path)); strlcat(alternative_path, name + 6, sizeof(alternative_path)); } } else if (!strncmp(name, "demonum/", 8)) { int num = Q_atoi(name + 8); if (num == 0 && name[8] != '0') { char *num_s = name + 8; int num_s_len = strlen(num_s); for (num = 0; num < num_s_len; num++) if (num_s[num] != '.') { Con_Printf("usage: download demonum/num\n" "if num is negative then download the Nth to last recorded demo, " "also can type any quantity of dots and " "where N dots is the Nth to last recorded demo\n"); goto deny_download; } num &= 0xF; num <<= 24; } else num &= 0x00FFFFFF; name = SV_MVDNum(num); if (!name) { Con_Printf((char *)Q_yelltext((unsigned char*)va("Demo number %d not found.\n", (num & 0xFF000000) ? -(num >> 24) : ((num & 0x00800000) ? (num | 0xFF000000) : num) ))); goto deny_download; } //Con_Printf("downloading demos/%s\n",name); snprintf(n, sizeof(n), "download demos/%s\n", name); ClientReliableWrite_Begin (sv_client, svc_stufftext,strlen(n) + 2); ClientReliableWrite_String (sv_client, n); return; } SV_UserCleanFilename(name); SV_UserCleanFilename(alternative_path); sv_client->downloadcount = 0; #ifdef SERVERONLY #define CLIENT_DOWNLOAD_RELATIVE_BASE FS_GAME // FIXME: Should we use FS_BASE ??? #else #define CLIENT_DOWNLOAD_RELATIVE_BASE FS_BASE #endif sv_client->download = FS_OpenVFS(name, "rb", CLIENT_DOWNLOAD_RELATIVE_BASE); if (!sv_client->download && alternative_path[0]) { sv_client->download = FS_OpenVFS(alternative_path, "rb", CLIENT_DOWNLOAD_RELATIVE_BASE); } if (sv_client->download) { sv_client->downloadsize = VFS_GETLEN(sv_client->download); } // if not techlogin, perform extra check to block .pak maps if (!sv_client->special) { // special check for maps that came from a pak file if (sv_client->download && !strncmp(name, "maps/", 5) && VFS_COPYPROTECTED(sv_client->download) && !(int)allow_download_pakmaps.value) { SV_ClientDownloadComplete(sv_client); goto deny_download; } } if (!sv_client->download) { Sys_Printf ("Couldn't download %s to %s\n", name, sv_client->name); goto deny_download; } // set donwload rate val = Info_Get (&sv_client->_userinfo_ctx_, "drate"); sv_client->netchan.rate = 1. / SV_BoundRate(true, Q_atoi(*val ? val : "99999")); // disable duplicate packet setting while downloading sv_client->netchan.dupe = 0; // all checks passed, start downloading #ifdef FTE_PEXT_CHUNKEDDOWNLOADS if (sv_client->fteprotocolextensions & FTE_PEXT_CHUNKEDDOWNLOADS) { ClientReliableWrite_Begin (sv_client, svc_download, 10+strlen(name)); ClientReliableWrite_Long (sv_client, -1); ClientReliableWrite_Long (sv_client, sv_client->downloadsize); ClientReliableWrite_String (sv_client, name); } #endif Cmd_NextDownload_f (); Sys_Printf ("Downloading %s to %s\n", name, sv_client->name); //bliP: download info/download url -> if (!strncmp(name, "maps/", 5)) { SV_ClientPrintf (sv_client, PRINT_HIGH, "Map %s is %.0fKB (%.2fMB)\n", name, (float)sv_client->downloadsize / 1024, (float)sv_client->downloadsize / 1024 / 1024); if (download_map_url.string[0]) { name += 5; SV_ClientPrintf (sv_client, PRINT_HIGH, "Download this map faster:\n"); SV_ClientPrintf (sv_client, PRINT_HIGH, "%s%s\n\n", download_map_url.string, name); } } else { SV_ClientPrintf (sv_client, PRINT_HIGH, "File %s is %.0fKB (%.2fMB)\n", name, (float)sv_client->downloadsize / 1024, (float)sv_client->downloadsize / 1024 / 1024); } //<- return; deny_download: #ifdef FTE_PEXT_CHUNKEDDOWNLOADS if (sv_client->fteprotocolextensions & FTE_PEXT_CHUNKEDDOWNLOADS) { ClientReliableWrite_Begin (sv_client, svc_download, 10+strlen(name)); ClientReliableWrite_Long (sv_client, -1); ClientReliableWrite_Long (sv_client, -1); // FIXME: -1 = Couldn't download, -2 = deny, we always use -1 atm ClientReliableWrite_String (sv_client, name); } else #endif { ClientReliableWrite_Begin (sv_client, svc_download, 4); ClientReliableWrite_Short (sv_client, -1); ClientReliableWrite_Byte (sv_client, 0); } SV_DownloadNextFile(); return; } /* ================== Cmd_DemoDownload_f ================== */ static void Cmd_StopDownload_f(void); static void Cmd_DemoDownload_f(void) { int i, num, cmd_argv_i_len; char *cmd_argv_i; unsigned char download_queue_cleared[] = "Download queue cleared.\n"; unsigned char download_queue_empty[] = "Download queue empty.\n"; unsigned char download_queue_already_exists[] = "Download queue already exists.\n"; unsigned char cmdhelp_dldesc[] = "Download a demo from the server your are connected to"; unsigned char cmdhelp_dl[] = "cmd dl"; unsigned char cmdhelp_pound[] = "#"; unsigned char cmdhelp_dot[] = "."; unsigned char cmdhelp_bs[] = "\\"; unsigned char cmdhelp_stop[] = "stop"; unsigned char cmdhelp_cancel[] = "cancel"; if (Cmd_Argc() < 2) { Con_Printf("\n%s\n" "Usage:\n" " %s %s [%s [%s]]\n" " \"#\" is one or several numbers from the demo list\n" " %s %s [%s%s [%s%s%s]]\n" " Each number of dots represents the Nth last recorded demo\n" " (Note that you can mix numbers and groups of dots)\n" " %s [%s|%s|%s]\n" " \"\\\", \"stop\" or \"cancel\" clear the download queue\n\n", Q_redtext(cmdhelp_dldesc), Q_redtext(cmdhelp_dl), Q_redtext(cmdhelp_pound), Q_redtext(cmdhelp_pound), Q_redtext(cmdhelp_pound), Q_redtext(cmdhelp_dl), Q_redtext(cmdhelp_dot), Q_redtext(cmdhelp_dot), Q_redtext(cmdhelp_dot), Q_redtext(cmdhelp_dot), Q_redtext(cmdhelp_dot), Q_redtext(cmdhelp_dot), Q_redtext(cmdhelp_dl), Q_redtext(cmdhelp_bs), Q_redtext(cmdhelp_stop), Q_redtext(cmdhelp_cancel) ); return; } if (!strcmp(Cmd_Argv(1), "cancel") || !strcmp(Cmd_Argv(1), "stop")) { Cmd_StopDownload_f(); // should not have any arguments, so it's OK return; } if (!strcmp(Cmd_Argv(1), "\\")) { if (sv_client->demonum[0]) { Con_Printf((char *)Q_redtext(download_queue_cleared)); sv_client->demonum[0] = 0; } else Con_Printf((char *)Q_redtext(download_queue_empty)); return; } if (sv_client->demonum[0]) { Con_Printf((char *)Q_redtext(download_queue_already_exists)); return; } sv_client->demolist = ((sv_client->demonum[0] = Cmd_Argc()) > 2); for (i = 1; i < sv_client->demonum[0]; i++) { cmd_argv_i = Cmd_Argv(i); cmd_argv_i_len = strlen(cmd_argv_i); num = Q_atoi(cmd_argv_i); if (num == 0 && cmd_argv_i[0] != '0') { for (num = 0; num < cmd_argv_i_len; num++) if (cmd_argv_i[num] != '.') { num = 0; break; } if (num) { num &= 0xF; num <<= 24; } } else num &= 0x00FFFFFF; sv_client->demonum[sv_client->demonum[0] - i] = num; } SV_DownloadNextFile(); } /* ================== Cmd_StopDownload_f ================== */ static void Cmd_StopDownload_f(void) { unsigned char download_stopped[] = "Download stopped and download queue cleared.\n"; if (!sv_client->download) return; sv_client->downloadcount = sv_client->downloadsize; SV_ClientDownloadComplete(sv_client); #ifdef FTE_PEXT_CHUNKEDDOWNLOADS if (sv_client->fteprotocolextensions & FTE_PEXT_CHUNKEDDOWNLOADS) { char *name = ""; // FIXME: FTE's chunked dl does't support "cmd stopdl", work around ClientReliableWrite_Begin (sv_client, svc_download, 10+strlen(name)); ClientReliableWrite_Long (sv_client, -1); ClientReliableWrite_Long (sv_client, -3); // -3 = dl was stopped ClientReliableWrite_String (sv_client, name); } else #endif { ClientReliableWrite_Begin (sv_client, svc_download, 6); ClientReliableWrite_Short (sv_client, 0); ClientReliableWrite_Byte (sv_client, 100); } sv_client->demonum[0] = 0; sv_client->demolist = false; Con_Printf ((char *)Q_redtext(download_stopped)); } //============================================================================= /* ================== SV_Say ================== */ static void SV_Say (qbool team) { qbool fake = false; client_t *client; int j, tmp, cls = 0; char *p; // used basically for QC based mods. char text[2048] = {0}; // used if mod does not have own support for say/say_team. qbool write_begin; if (Cmd_Argc () < 2) return; p = Cmd_Args(); // unfake if requested. if (!team && sv_unfake.value) { char *ch; for (ch = p; *ch; ch++) if (*ch == 13) *ch = '#'; } // remove surrounding " if any. if (p[0] == '"' && (j = (int)strlen(p)) > 2 && p[j-1] == '"') { // form text[]. snprintf(text, sizeof(text), "%s", p + 1); // skip opening " and copy rest text including closing ". text[max(0,(int)strlen(text)-1)] = '\n'; // replace closing " with new line. } else { // form text[]. snprintf(text, sizeof(text), "%s\n", p); } if (!sv_client->logged && !sv_client->logged_in_via_web) { SV_ParseLogin(sv_client); return; } // try handle say in the mod. SV_EndRedirect (); pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); j = PR_ClientSay(team, p); SV_BeginRedirect (RD_CLIENT); if (j) return; // say was handled by mod. if (sv_client->spectator && (!(int)sv_spectalk.value || team)) strlcpy(text, va("[SPEC] %s: %s", sv_client->name, text), sizeof(text)); else if (team) strlcpy(text, va("(%s): %s", sv_client->name, text), sizeof(text)); else { strlcpy(text, va("%s: %s", sv_client->name, text), sizeof(text)); } if (fp_messages) { if (curtime < sv_client->lockedtill) { SV_ClientPrintf(sv_client, PRINT_CHAT, "You can't talk for %d more seconds\n", (int) (sv_client->lockedtill - curtime)); return; } tmp = sv_client->whensaidhead - fp_messages + 1; if (tmp < 0) tmp = 10+tmp; if (sv_client->whensaid[tmp] && (curtime - sv_client->whensaid[tmp] < fp_persecond)) { sv_client->lockedtill = curtime + fp_secondsdead; if (fp_msg[0]) { SV_ClientPrintf(sv_client, PRINT_CHAT, "FloodProt: %s\n", fp_msg); } else { SV_ClientPrintf(sv_client, PRINT_CHAT, "FloodProt: You can't talk for %d seconds.\n", fp_secondsdead); } return; } sv_client->whensaidhead++; if (sv_client->whensaidhead > 9) sv_client->whensaidhead = 0; sv_client->whensaid[sv_client->whensaidhead] = curtime; } Sys_Printf ("%s", text); SV_Write_Log(CONSOLE_LOG, 1, text); fake = ( strchr(text, 13) ? true : false ); // check if string contain "$\" for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) { if (client->state < cs_preconnected) continue; if (team) { // the spectator team if (sv_client->spectator) { if (!client->spectator) continue; // on different teams } else { if (sv_client == client) ; // send msg to self anyway else if (client->spectator) { if( !sv_sayteam_to_spec.value // player can't say_team to spec in this case || !fake // self say_team does't contain $\ so this is treat as private message || (client->spec_track <= 0 && strcmp(sv_client->team, client->team)) // spec do not track player and on different team || (client->spec_track > 0 && strcmp(sv_client->team, svs.clients[client->spec_track - 1].team)) // spec track player on different team ) continue; // on different teams } else if (coop.value) ; // allow team messages to everyone in coop from players. else if (!teamplay.value) continue; // non team game else if (strcmp(sv_client->team, client->team)) continue; // on different teams } } else { if (sv_client->spectator) { // check for spectalk off. if (!client->spectator && !(int)sv_spectalk.value) continue; // off - specs can't talk to players. } } cls |= 1 << j; SV_ClientPrintf2(client, PRINT_CHAT, "%s", text); } if (!sv.mvdrecording || !cls) return; // non-team messages should be seen always, even if not tracking any player if (!team && ((sv_client->spectator && (int)sv_spectalk.value) || !sv_client->spectator)) { write_begin = MVDWrite_Begin (dem_all, 0, strlen(text)+3); } else { write_begin = MVDWrite_Begin (dem_multiple, cls, strlen(text)+3); } if (write_begin) { MVD_MSG_WriteByte (svc_print); MVD_MSG_WriteByte (PRINT_CHAT); MVD_MSG_WriteString (text); } } /* ================== Cmd_Say_f ================== */ static void Cmd_Say_f(void) { SV_Say (false); } /* ================== Cmd_Say_Team_f ================== */ static void Cmd_Say_Team_f(void) { SV_Say (true); } //============================================================================ /* ================= Cmd_Pings_f The client is showing the scoreboard, so send new ping times for all clients ================= */ static void Cmd_Pings_f (void) { client_t *client; int j; for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) { if (!(client->state == cs_spawned || (client->state == cs_connected/* && client->spawncount != svs.spawncount*/)) ) continue; ClientReliableWrite_Begin (sv_client, svc_updateping, 4); ClientReliableWrite_Byte (sv_client, j); ClientReliableWrite_Short (sv_client, SV_CalcPing(client)); ClientReliableWrite_Begin (sv_client, svc_updatepl, 4); ClientReliableWrite_Byte (sv_client, j); ClientReliableWrite_Byte (sv_client, client->lossage); } } /* ================== Cmd_Kill_f ================== */ static void Cmd_Kill_f (void) { if (sv_player->v->health <= 0) { SV_ClientPrintf (sv_client, PRINT_HIGH, "Can't suicide -- already dead!\n"); return; } pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); PR_ClientKill(); } static void SV_NotifyStreamsOfPause(void) { if (sv.mvdrecording) { sizebuf_t msg; byte msg_buf[20]; SZ_InitEx(&msg, msg_buf, sizeof(msg_buf), true); MSG_WriteByte(&msg, svc_setpause); MSG_WriteByte(&msg, sv.paused ? 1 : 0); DemoWriteQTV(&msg); } } /* ================== SV_TogglePause ================== */ void SV_TogglePause (const char *msg, int bit) { int i; client_t *cl; extern cvar_t sv_paused; sv.paused ^= bit; Cvar_SetROM (&sv_paused, va("%i", sv.paused)); if (sv.paused) sv.pausedsince = Sys_DoubleTime(); if (msg) SV_BroadcastPrintf (PRINT_HIGH, "%s", msg); // send notification to all clients for (i=0, cl = svs.clients ; istate) continue; ClientReliableWrite_Begin (cl, svc_setpause, 2); ClientReliableWrite_Byte (cl, sv.paused ? 1 : 0); cl->lastservertimeupdate = -99; // force an update to be sent } // send notification to all streams SV_NotifyStreamsOfPause(); } /* ================== Cmd_Pause_f ================== */ static void Cmd_Pause_f (void) { char st[CLIENT_NAME_LEN + 32]; qbool newstate; newstate = sv.paused ^ 1; if (!(int)pausable.value) { SV_ClientPrintf (sv_client, PRINT_HIGH, "Pause not allowed.\n"); return; } if (sv_client->spectator) { SV_ClientPrintf (sv_client, PRINT_HIGH, "Spectators can not pause.\n"); return; } if (GE_ShouldPause) { pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); G_FLOAT(OFS_PARM0) = newstate; PR_ExecuteProgram (GE_ShouldPause); if (!G_FLOAT(OFS_RETURN)) return; // progs said ignore the request } if (newstate & 1) snprintf (st, sizeof(st), "%s paused the game\n", sv_client->name); else snprintf (st, sizeof(st), "%s unpaused the game\n", sv_client->name); SV_TogglePause(st, 1); } /* ================= Cmd_Drop_f The client is going to disconnect, so remove the connection immediately ================= */ static void Cmd_Drop_f (void) { SV_EndRedirect (); if (sv_client->state == cs_zombie) // FIXME return; // FIXME if (!sv_client->spectator) SV_BroadcastPrintf (PRINT_HIGH, "%s dropped\n", sv_client->name); SV_DropClient (sv_client); } /* ================= Cmd_PTrack_f Change the bandwidth estimate for a client ================= */ static void Cmd_PTrack_f (void) { int i; edict_t *ent, *tent; if (!sv_client->spectator) return; if (Cmd_Argc() != 2) { // turn off tracking sv_client->spec_track = 0; ent = EDICT_NUM(sv_client - svs.clients + 1); tent = EDICT_NUM(0); ent->v->goalentity = EDICT_TO_PROG(tent); return; } i = Q_atoi(Cmd_Argv(1)); if (i < 0 || i >= MAX_CLIENTS || svs.clients[i].state != cs_spawned || svs.clients[i].spectator) { SV_ClientPrintf (sv_client, PRINT_HIGH, "Invalid client to track\n"); sv_client->spec_track = 0; ent = EDICT_NUM(sv_client - svs.clients + 1); tent = EDICT_NUM(0); ent->v->goalentity = EDICT_TO_PROG(tent); return; } sv_client->spec_track = i + 1; // now tracking ent = EDICT_NUM(sv_client - svs.clients + 1); tent = EDICT_NUM(i + 1); ent->v->goalentity = EDICT_TO_PROG(tent); } /* ================= Cmd_Rate_f Change the bandwidth estimate for a client ================= */ static void Cmd_Rate_f (void) { int rate; if (Cmd_Argc() != 2) { SV_ClientPrintf (sv_client, PRINT_HIGH, "Current rate is %i\n", (int)(1.0/sv_client->netchan.rate + 0.5)); return; } rate = SV_BoundRate (sv_client->download != NULL, Q_atoi(Cmd_Argv(1))); SV_ClientPrintf (sv_client, PRINT_HIGH, "Net rate set to %i\n", rate); sv_client->netchan.rate = 1.0/rate; } //bliP: upload files -> /* ================= Cmd_TechLogin_f Login to upload ================= */ int Master_Rcon_Validate (void); static void Cmd_TechLogin_f (void) { if (sv_client->logincount > 4) //denied return; if (Cmd_Argc() < 2) { if (sv_client->special) { sv_client->special = false; sv_client->logincount = 0; SV_ClientPrintf (sv_client, PRINT_HIGH, "Logged out.\n"); } return; } if (!Master_Rcon_Validate()) //don't even let them know they're wrong { sv_client->logincount++; return; } sv_client->special = true; SV_ClientPrintf (sv_client, PRINT_HIGH, "Logged in.\n"); } /* ================ Cmd_Upload_f ================ */ static void Cmd_Upload_f (void) { FILE *f; char str[MAX_OSPATH]; if (sv_client->state != cs_spawned) return; if (!sv_client->special) { Con_Printf ("Client not tagged to upload.\n"); return; } if (Cmd_Argc() != 3) { Con_Printf ("upload [local filename] [remote filename]\n"); return; } snprintf(sv_client->uploadfn, sizeof(sv_client->uploadfn), "%s", Cmd_Argv(2)); if (!sv_client->uploadfn[0]) { //just in case.. Con_Printf ("Bad file name.\n"); return; } if ((f = fopen(sv_client->uploadfn, "rb"))) { Con_Printf ("File already exists.\n"); fclose(f); return; } sv_client->remote_snap = false; FS_CreatePath (sv_client->uploadfn); //fixed, need to create path snprintf (str, sizeof (str), "cmd fileul \"%s\"\n", Cmd_Argv(1)); ClientReliableWrite_Begin (sv_client, svc_stufftext, strlen(str) + 2); ClientReliableWrite_String (sv_client, str); } //<- /* ================== Cmd_SetInfo_f Allow clients to change userinfo ================== */ char *shortinfotbl[] = { "name", "team", "skin", "topcolor", "bottomcolor", #ifdef CHAT_ICON_EXPERIMENTAL "chat", #endif "gender", "*auth", "*flag", //"*client", //"*spectator", //"*VIP", NULL }; static void Cmd_SetInfo_f (void) { extern cvar_t sv_forcenick; sv_client_state_t saved_state; char oldval[MAX_EXT_INFO_STRING]; char info[MAX_EXT_INFO_STRING]; if (sv_kickuserinfospamtime.value > 0 && (int)sv_kickuserinfospamcount.value > 0) { if (!sv_client->lastuserinfotime || curtime - sv_client->lastuserinfotime > sv_kickuserinfospamtime.value) { sv_client->lastuserinfocount = 0; sv_client->lastuserinfotime = curtime; } else if (++(sv_client->lastuserinfocount) > (int)sv_kickuserinfospamcount.value) { if (!sv_client->drop) { saved_state = sv_client->state; sv_client->state = cs_free; SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked for userinfo spam\n", sv_client->name); sv_client->state = saved_state; SV_ClientPrintf (sv_client, PRINT_HIGH, "You were kicked from the game for userinfo spamming\n"); SV_LogPlayer (sv_client, "userinfo spam", 1); sv_client->drop = true; } return; } } switch (Cmd_Argc()) { case 1: Con_Printf ("User info settings:\n"); Info_ReverseConvert(&sv_client->_userinfo_ctx_, info, sizeof(info)); Info_Print(info); Con_DPrintf ("[%d/%d]\n", strlen(info), sizeof(info)); if (developer.value) { Con_Printf ("User info settings short:\n"); Info_ReverseConvert(&sv_client->_userinfoshort_ctx_, info, sizeof(info)); Info_Print(info); Con_DPrintf ("[%d/%d]\n", strlen(info), sizeof(info)); } return; case 3: break; default: Con_Printf ("usage: setinfo [ ]\n"); return; } if (Cmd_Argv(1)[0] == '*') return; // don't set privileged values if (strchr(Cmd_Argv(1), '\\') || strchr(Cmd_Argv(2), '\\')) return; // illegal char if (strstr(Cmd_Argv(1), "&c") || strstr(Cmd_Argv(1), "&r") || strstr(Cmd_Argv(2), "&c") || strstr(Cmd_Argv(2), "&r")) return; strlcpy(oldval, Info_Get(&sv_client->_userinfo_ctx_, Cmd_Argv(1)), sizeof(oldval)); pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); if (PR_UserInfoChanged(0)) return; // does not allowed to be changed by mod. Info_Set (&sv_client->_userinfo_ctx_, Cmd_Argv(1), Cmd_Argv(2)); // name is extracted below in ExtractFromUserInfo // strlcpy (sv_client->name, Info_ValueForKey (sv_client->userinfo, "name") // , CLIENT_NAME_LEN); // SV_FullClientUpdate (sv_client, &sv.reliable_datagram); // sv_client->sendinfo = true; //Info_ValueForKey(sv_client->userinfo, Cmd_Argv(1)); if (!strcmp(Info_Get(&sv_client->_userinfo_ctx_, Cmd_Argv(1)), oldval)) return; // key hasn't changed if (!strcmp(Cmd_Argv(1), "name")) { //bliP: mute -> if (curtime < sv_client->lockedtill) { SV_ClientPrintf(sv_client, PRINT_CHAT, "You can't change your name while you're muted\n"); return; } //<- //VVD: forcenick -> //meag: removed sv_login check to allow optional logins... sv_forcenick should still take effect if ((int)sv_forcenick.value && /*(int)sv_login.value &&*/ sv_client->login[0]) { // allow differences in case, redtext if (Q_namecmp(sv_client->login, Cmd_Argv(2))) { SV_ClientPrintf(sv_client, PRINT_CHAT, "You can't change your name while logged in on this server.\n"); Info_Set(&sv_client->_userinfo_ctx_, "name", sv_client->login); strlcpy(sv_client->name, sv_client->login, CLIENT_NAME_LEN); MSG_WriteByte(&sv_client->netchan.message, svc_stufftext); MSG_WriteString(&sv_client->netchan.message, va("name %s\n", sv_client->login)); MSG_WriteByte(&sv_client->netchan.message, svc_stufftext); MSG_WriteString(&sv_client->netchan.message, va("setinfo name %s\n", sv_client->login)); return; } } //<- } //bliP: kick top -> if ((int)sv_kicktop.value && !strcmp(Cmd_Argv(1), "topcolor")) { if (!sv_client->lasttoptime || curtime - sv_client->lasttoptime > 8) { sv_client->lasttopcount = 0; sv_client->lasttoptime = curtime; } else if (sv_client->lasttopcount++ > 5) { if (!sv_client->drop) { saved_state = sv_client->state; sv_client->state = cs_free; SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked for topcolor spam\n", sv_client->name); sv_client->state = saved_state; SV_ClientPrintf (sv_client, PRINT_HIGH, "You were kicked from the game for topcolor spamming\n"); SV_LogPlayer (sv_client, "topcolor spam", 1); sv_client->drop = true; } return; } } //<- ProcessUserInfoChange (sv_client, Cmd_Argv (1), oldval); PR_UserInfoChanged(1); } void ProcessUserInfoChange (client_t* sv_client, const char* key, const char* old_value) { int i; // process any changed values SV_ExtractFromUserinfo (sv_client, !strcmp(key, "name")); if (mod_UserInfo_Changed) { pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_client->edict); PR_SetTmpString(&G_INT(OFS_PARM0), key); PR_SetTmpString(&G_INT(OFS_PARM1), old_value); PR_SetTmpString(&G_INT(OFS_PARM2), Info_Get(&sv_client->_userinfo_ctx_, key)); PR_ExecuteProgram (mod_UserInfo_Changed); } for (i = 0; shortinfotbl[i] != NULL; i++) { if (!strcmp(key, shortinfotbl[i])) { char *nuw = Info_Get(&sv_client->_userinfo_ctx_, key); Info_SetStar (&sv_client->_userinfoshort_ctx_, key, nuw); i = sv_client - svs.clients; MSG_WriteByte (&sv.reliable_datagram, svc_setinfo); MSG_WriteByte (&sv.reliable_datagram, i); MSG_WriteString (&sv.reliable_datagram, key); MSG_WriteString (&sv.reliable_datagram, nuw); break; } } } /* ================== SV_ShowServerinfo_f Dumps the serverinfo info string ================== */ static void Cmd_ShowServerinfo_f (void) { Info_Print (svs.info); } static void Cmd_NoSnap_f(void) { if (*sv_client->uploadfn) { *sv_client->uploadfn = 0; SV_BroadcastPrintf (PRINT_HIGH, "%s refused remote screenshot\n", sv_client->name); } } /* ============== Cmd_MinPing_f ============== */ static void Cmd_MinPing_f (void) { float minping; switch (Cmd_Argc()) { case 2: if (GameStarted()) Con_Printf("Can't change sv_minping: demo recording or match in progress.\n"); else if (!(int)sv_enable_cmd_minping.value) Con_Printf("Can't change sv_minping: sv_enable_cmd_minping == 0.\n"); else { minping = Q_atof(Cmd_Argv(1)); if (minping < 0 || minping > 300) Con_Printf("Value must be >= 0 and <= 300.\n"); else Cvar_SetValue (&sv_minping, (int)minping); } case 1: Con_Printf("sv_minping = %s\n", sv_minping.string); break; default: Con_Printf("usage: minping []\n = '' show current sv_minping value\n"); } } /* ============== Cmd_AirStep_f ============== */ static void Cmd_AirStep_f (void) { int val; unsigned char red_airstep[64] = "pm_airstep"; if (sv_client->spectator) { Con_Printf("Spectators can't change pm_airstep\n"); return; } switch (Cmd_Argc()) { case 2: if (GameStarted()) Con_Printf("Can't change pm_airstep: demo recording in progress or serverinfo key status is not 'Standby'.\n"); else { val = Q_atoi(Cmd_Argv(1)); if (val != 0 && val != 1) Con_Printf("Value must be 0 or 1.\n"); else { float old = pm_airstep.value; // remember Cvar_Set (&pm_airstep, val ? "1" : ""); // set new value if (pm_airstep.value != old) { // seems value was changed SV_BroadcastPrintf (2, "%s turns %s %s\n", sv_client->name, Q_redtext(red_airstep), pm_airstep.value ? "on" : "off"); break; } } } case 1: Con_Printf("pm_airstep = %s\n", pm_airstep.string); break; default: Con_Printf("usage: airstep [0 | 1]\n"); } } /* ============== Cmd_ShowMapsList_f ============== */ static void Cmd_ShowMapsList_f(void) { char *value, *key; int i, j, len, i_mod_2 = 1; unsigned char ztndm3[] = "ztndm3"; unsigned char list_of_custom_maps[] = "list of custom maps"; unsigned char end_of_list[] = "end of list"; Con_Printf("Vote for maps by typing the mapname, for example \"%s\"\n\n---%s\n", Q_redtext(ztndm3), Q_redtext(list_of_custom_maps)); for (i = LOCALINFO_MAPS_LIST_START; i <= LOCALINFO_MAPS_LIST_END; i++) { key = va("%d", i); value = Info_Get(&_localinfo_, key); if (*value) { if (!(i_mod_2 = i % 2)) { if ((len = 19 - strlen(value)) < 1) len = 1; for (j = 0; j < len; j++) strlcat(value, " ", MAX_KEY_STRING); } Con_Printf("%s%s", value, i_mod_2 ? "\n" : ""); } else break; } Con_Printf("%s---%s\n", i_mod_2 ? "" : "\n", Q_redtext(end_of_list)); } static void SetUpClientEdict (client_t *cl, edict_t *ent) { ED_ClearEdict(ent); // restore client name. PR_SetEntityString(ent, ent->v->netname, cl->name); // so spec will have right goalentity - if speccing someone if(cl->spectator && cl->spec_track > 0) ent->v->goalentity = EDICT_TO_PROG(svs.clients[cl->spec_track-1].edict); ent->v->colormap = NUM_FOR_EDICT(ent); ent->v->team = 0; // FIXME cl->entgravity = 1.0; if (fofs_gravity) EdictFieldFloat(ent, fofs_gravity) = 1.0; cl->maxspeed = sv_maxspeed.value; if (fofs_maxspeed) EdictFieldFloat(ent, fofs_maxspeed) = (int)sv_maxspeed.value; } extern cvar_t spectator_password, password; extern void MVD_PlayerReset(int player); extern void CountPlayersSpecsVips(int *clients_ptr, int *spectators_ptr, int *vips_ptr, client_t **newcl_ptr); extern qbool SpectatorCanConnect(int vip, int spass, int spectators, int vips); extern qbool PlayerCanConnect(int clients); /* ================== Cmd_Join_f Set client to player mode without reconnecting ================== */ static void Cmd_Join_f (void) { int i; int clients; if (sv_client->state != cs_spawned) return; if (!sv_client->spectator) return; // already a player if (!(sv_client->extensions & Z_EXT_JOIN_OBSERVE)) { SV_ClientPrintf (sv_client, PRINT_HIGH, "Your QW client doesn't support this command\n"); return; } if (password.string[0] && strcmp (password.string, "none")) { SV_ClientPrintf (sv_client, PRINT_HIGH, "This server requires a %s password. Please disconnect, set the password and reconnect as %s.\n", "player", "player"); return; } // Might have been 'not necessary' for spectator but needed for player if (SV_LoginBlockJoinRequest(sv_client)) { return; } if (SV_ClientConnectedTime(sv_client) < 5) { SV_ClientPrintf(sv_client, PRINT_HIGH, "Wait %d seconds\n", 5 - (int)SV_ClientConnectedTime(sv_client)); return; } // count players already on server CountPlayersSpecsVips(&clients, NULL, NULL, NULL); if (!PlayerCanConnect(clients)) { SV_ClientPrintf (sv_client, PRINT_HIGH, "Can't join, all player slots full\n"); return; } if (!PlayerCheckPing()) { return; } // call the prog function for removing a client // this will set the body to a dead frame, among other things pr_global_struct->self = EDICT_TO_PROG(sv_player); PR_GameClientDisconnect(1); // this is like SVC_DirectConnect. // turn the spectator into a player sv_client->old_frags = 0; SV_SetClientConnectionTime(sv_client); sv_client->spectator = false; sv_client->spec_track = 0; Info_Remove(&sv_client->_userinfo_ctx_, "*spectator"); Info_Remove(&sv_client->_userinfoshort_ctx_, "*spectator"); // like Cmd_Spawn_f() SetUpClientEdict (sv_client, sv_client->edict); // call the progs to get default spawn parms for the new client PR_GameSetNewParms(); // copy spawn parms out of the client_t for (i=0 ; ispawn_parms[i] = (&PR_GLOBAL(parm1))[i]; // call the spawn function pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); G_FLOAT(OFS_PARM0) = (float) sv_client->vip; PR_GameClientConnect(0); // actually spawn the player pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); G_FLOAT(OFS_PARM0) = (float) sv_client->vip; PR_GamePutClientInServer(0); // look in SVC_DirectConnect() for for extended comment whats this for MVD_PlayerReset(NUM_FOR_EDICT(sv_player) - 1); // send notification to all clients sv_client->sendinfo = true; } /* ================== Cmd_Observe_f Set client to spectator mode without reconnecting ================== */ static void Cmd_Observe_f (void) { int i; int spectators, vips; if (sv_client->state != cs_spawned) return; if (sv_client->spectator) return; // already a spectator if (!(sv_client->extensions & Z_EXT_JOIN_OBSERVE)) { SV_ClientPrintf (sv_client, PRINT_HIGH, "Your QW client doesn't support this command\n"); return; } if (spectator_password.string[0] && strcmp (spectator_password.string, "none")) { SV_ClientPrintf (sv_client, PRINT_HIGH, "This server requires a %s password. Please disconnect, set the password and reconnect as %s.\n", "spectator", "spectator"); return; } if (SV_ClientConnectedTime(sv_client) < 5) { SV_ClientPrintf (sv_client, PRINT_HIGH, "Wait %d seconds\n", 5 - (int)SV_ClientConnectedTime(sv_client)); return; } // count spectators already on server CountPlayersSpecsVips(NULL, &spectators, &vips, NULL); if (!SpectatorCanConnect(sv_client->vip, true/*kinda HACK*/, spectators, vips)) { SV_ClientPrintf (sv_client, PRINT_HIGH, "Can't join, all spectator/vip slots full\n"); return; } // call the prog function for removing a client // this will set the body to a dead frame, among other things pr_global_struct->self = EDICT_TO_PROG(sv_player); PR_GameClientDisconnect(0); // this is like SVC_DirectConnect. // turn the player into a spectator sv_client->old_frags = 0; SV_SetClientConnectionTime(sv_client); sv_client->spectator = true; sv_client->spec_track = 0; Info_SetStar (&sv_client->_userinfo_ctx_, "*spectator", "1"); Info_SetStar (&sv_client->_userinfoshort_ctx_, "*spectator", "1"); // like Cmd_Spawn_f() SetUpClientEdict (sv_client, sv_client->edict); // call the progs to get default spawn parms for the new client PR_GameSetNewParms(); SV_SpawnSpectator (); // copy spawn parms out of the client_t for (i=0 ; ispawn_parms[i] = (&PR_GLOBAL(parm1))[i]; // call the spawn function pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); G_FLOAT(OFS_PARM0) = (float) sv_client->vip; PR_GameClientConnect(1); pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); PR_GamePutClientInServer(1); // let mod know we put spec not player // look in SVC_DirectConnect() for for extended comment whats this for MVD_PlayerReset(NUM_FOR_EDICT(sv_player) - 1); // send notification to all clients sv_client->sendinfo = true; } #ifdef FTE_PEXT2_VOICECHAT /* Privacy issues: By sending voice chat to a server, you are unsure who might be listening. Voice can be recorded to an mvd, potentially including voice. Spectators tracking you are able to hear team chat of your team. You're never quite sure if anyone might join the server and your team before you finish saying a sentance. You run the risk of sounds around you being recorded by quake, including but not limited to: TV channels, loved ones, phones, YouTube videos featuring certain moans. Default on non-team games is to broadcast. */ // FTEQW type and naming compatibility // it's not really necessary, simple find & replace would do the job too #define qboolean qbool #define host_client sv_client #define ival value // for cvars compatibility #define VOICE_RING_SIZE 512 /*POT*/ struct { struct voice_ring_s { unsigned int sender; unsigned char receiver[MAX_CLIENTS/8]; unsigned char gen; unsigned char seq; unsigned int datalen; unsigned char data[1024]; } ring[VOICE_RING_SIZE]; unsigned int write; } voice; void SV_VoiceReadPacket(void) { unsigned int vt = host_client->voice_target; unsigned int j; struct voice_ring_s *ring; unsigned short bytes; client_t *cl; unsigned char gen = MSG_ReadByte(); unsigned char seq = MSG_ReadByte(); /*read the data from the client*/ bytes = MSG_ReadShort(); ring = &voice.ring[voice.write & (VOICE_RING_SIZE-1)]; if (bytes > sizeof(ring->data) || curtime < host_client->lockedtill || !sv_voip.ival) { MSG_ReadSkip(bytes); return; } else { voice.write++; MSG_ReadData(ring->data, bytes); } ring->datalen = bytes; ring->sender = host_client - svs.clients; ring->gen = gen; ring->seq = seq; /*broadcast it its to their team, and its not teamplay*/ if (vt == VT_TEAM && !teamplay.ival) vt = VT_ALL; /*figure out which team members are meant to receive it*/ for (j = 0; j < MAX_CLIENTS/8; j++) ring->receiver[j] = 0; for (j = 0, cl = svs.clients; j < MAX_CLIENTS; j++, cl++) { if (cl->state != cs_spawned && cl->state != cs_connected) continue; /*spectators may only talk to spectators*/ if (host_client->spectator && !sv_spectalk.ival) if (!cl->spectator) continue; if (vt == VT_TEAM) { // the spectator team if (host_client->spectator) { if (!cl->spectator) continue; } else { if (strcmp(cl->team, host_client->team) || cl->spectator) continue; // on different teams } } else if (vt == VT_NONMUTED) { if (host_client->voice_mute[j>>3] & (1<<(j&3))) continue; } else if (vt >= VT_PLAYERSLOT0) { if (j != vt - VT_PLAYERSLOT0) continue; } ring->receiver[j>>3] |= 1<<(j&3); } if (sv.mvdrecording && sv_voip_record.ival && !(sv_voip_record.ival == 2 && !host_client->spectator)) { // non-team messages should be seen always, even if not tracking any player if (vt == VT_ALL && (!host_client->spectator || sv_spectalk.ival)) { MVDWrite_Begin (dem_all, 0, ring->datalen+6); } else { unsigned int cls; cls = ring->receiver[0] | (ring->receiver[1]<<8) | (ring->receiver[2]<<16) | (ring->receiver[3]<<24); if (!cls) { // prevent dem_multiple(0) being sent return; } MVDWrite_Begin(dem_multiple, cls, ring->datalen + 6); } MVD_MSG_WriteByte( svc_fte_voicechat); MVD_MSG_WriteByte( ring->sender); MVD_MSG_WriteByte( ring->gen); MVD_MSG_WriteByte( ring->seq); MVD_MSG_WriteShort( ring->datalen); MVD_SZ_Write( ring->data, ring->datalen); } } void SV_VoiceInitClient(client_t *client) { client->voice_target = VT_TEAM; client->voice_active = false; client->voice_read = voice.write; memset(client->voice_mute, 0, sizeof(client->voice_mute)); } void SV_VoiceSendPacket(client_t *client, sizebuf_t *buf) { unsigned int clno; qboolean send; struct voice_ring_s *ring; // client_t *split; // if (client->controller) // client = client->controller; clno = client - svs.clients; if (!(client->fteprotocolextensions2 & FTE_PEXT2_VOICECHAT)) return; if (!client->voice_active || client->num_backbuf) { client->voice_read = voice.write; return; } while(client->voice_read < voice.write) { /*they might be too far behind*/ if (client->voice_read+VOICE_RING_SIZE < voice.write) client->voice_read = voice.write - VOICE_RING_SIZE; ring = &voice.ring[(client->voice_read) & (VOICE_RING_SIZE-1)]; /*figure out if it was for us*/ send = false; if (ring->receiver[clno>>3] & (1<<(clno&3))) send = true; // FIXME: qqshka: well, is it RIGHTWAY at all??? #if 0 // qqshka: I am turned it off. /*if you're spectating, you can hear whatever your tracked player can hear*/ if (client->spectator && client->spec_track) if (ring->receiver[(client->spec_track-1)>>3] & (1<<((client->spec_track-1)&3))) send = true; #endif if (client->voice_mute[ring->sender>>3] & (1<<(ring->sender&3))) send = false; if (ring->sender == clno && !sv_voip_echo.ival) send = false; /*additional ways to block voice*/ if (client->download) send = false; if (send) { if (buf->maxsize - buf->cursize < ring->datalen+5) break; MSG_WriteByte(buf, svc_fte_voicechat); MSG_WriteByte(buf, ring->sender); MSG_WriteByte(buf, ring->gen); MSG_WriteByte(buf, ring->seq); MSG_WriteShort(buf, ring->datalen); SZ_Write(buf, ring->data, ring->datalen); } client->voice_read++; } } void SV_Voice_Ignore_f(void) { unsigned int other; int type = 0; if (Cmd_Argc() < 2) { /*only a name = toggle*/ type = 0; } else { /*mute if 1, unmute if 0*/ if (atoi(Cmd_Argv(2))) type = 1; else type = -1; } other = atoi(Cmd_Argv(1)); if (other >= MAX_CLIENTS) return; switch(type) { case -1: host_client->voice_mute[other>>3] &= ~(1<<(other&3)); break; case 0: host_client->voice_mute[other>>3] ^= (1<<(other&3)); break; case 1: host_client->voice_mute[other>>3] |= (1<<(other&3)); } } void SV_Voice_Target_f(void) { unsigned int other; char *t = Cmd_Argv(1); if (!strcmp(t, "team")) host_client->voice_target = VT_TEAM; else if (!strcmp(t, "all")) host_client->voice_target = VT_ALL; else if (!strcmp(t, "nonmuted")) host_client->voice_target = VT_NONMUTED; else if (*t >= '0' && *t <= '9') { other = atoi(t); if (other >= MAX_CLIENTS) return; host_client->voice_target = VT_PLAYERSLOT0 + other; } else { /*don't know who you mean, futureproofing*/ host_client->voice_target = VT_TEAM; } } void SV_Voice_MuteAll_f(void) { host_client->voice_active = false; } void SV_Voice_UnmuteAll_f(void) { host_client->voice_active = true; } #endif // FTE_PEXT2_VOICECHAT /* * Parse protocol extensions which supported by client. * This is workaround for the proxy case, like: qwfwd. We can't use it in case of qizmo thought. */ void Cmd_PEXT_f(void) { int idx; int proto_ver, proto_value; if (!sv_client->process_pext) return; // sorry, we do not expect it right now. sv_client->process_pext = false; for ( idx = 1; idx < Cmd_Argc(); ) { proto_ver = Q_atoi(Cmd_Argv(idx++)); proto_value = Q_atoi(Cmd_Argv(idx++)); switch( proto_ver ) { #ifdef PROTOCOL_VERSION_FTE case PROTOCOL_VERSION_FTE: // do not reset it. if (!sv_client->fteprotocolextensions) { sv_client->fteprotocolextensions = proto_value & svs.fteprotocolextensions; if (sv_client->fteprotocolextensions) Con_DPrintf("PEXT: Client supports 0x%x fte extensions\n", sv_client->fteprotocolextensions); } break; #endif // PROTOCOL_VERSION_FTE #ifdef PROTOCOL_VERSION_FTE2 case PROTOCOL_VERSION_FTE2: // do not reset it. if (!sv_client->fteprotocolextensions2) { sv_client->fteprotocolextensions2 = proto_value & svs.fteprotocolextensions2; if (sv_client->fteprotocolextensions2) Con_DPrintf("PEXT: Client supports 0x%x fte extensions2\n", sv_client->fteprotocolextensions2); } break; #endif // PROTOCOL_VERSION_FTE2 #ifdef PROTOCOL_VERSION_MVD1 case PROTOCOL_VERSION_MVD1: if (!sv_client->mvdprotocolextensions1) { sv_client->mvdprotocolextensions1 = proto_value & svs.mvdprotocolextension1; if (sv_client->mvdprotocolextensions1) Con_DPrintf("PEXT: Client supports 0x%x mvdsv extensions\n", sv_client->mvdprotocolextensions1); } break; #endif } } // we are ready for new command now. MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, "cmd new\n"); } #if defined(SERVERONLY) && defined(WWW_INTEGRATION) // { Central login void Cmd_Login_f(void) { extern cvar_t sv_login; if (sv_client->state != cs_spawned && !(int)sv_login.value) { SV_ClientPrintf2(sv_client, PRINT_HIGH, "Cannot login during connection\n"); return; } if (Cmd_Argc() != 2) { SV_ClientPrintf2(sv_client, PRINT_HIGH, "Usage: login \n"); return; } if (curtime - sv_client->login_request_time < LOGIN_MIN_RETRY_TIME) { SV_ClientPrintf2(sv_client, PRINT_HIGH, "Please wait and try again\n"); return; } if (sv_client->logged_in_via_web) { SV_ClientPrintf2(sv_client, PRINT_HIGH, "You are already logged in as '%s'\n", sv_client->login); return; } if (sv_client->state != cs_spawned) { SV_ParseWebLogin(sv_client); } else { Central_GenerateChallenge(sv_client, Cmd_Argv(1), false); } } void Cmd_ChallengeResponse_f(void) { if (Cmd_Argc() != 2) { MSG_WriteByte(&sv_client->netchan.message, svc_print); MSG_WriteByte(&sv_client->netchan.message, PRINT_HIGH); MSG_WriteString(&sv_client->netchan.message, "Usage: challenge-response \n"); return; } if (!sv_client->login_challenge[0]) { MSG_WriteByte(&sv_client->netchan.message, svc_print); MSG_WriteByte(&sv_client->netchan.message, PRINT_HIGH); MSG_WriteString(&sv_client->netchan.message, "Please wait and try again\n"); return; } Central_VerifyChallengeResponse(sv_client, sv_client->login_challenge, Cmd_Argv(1)); } void Cmd_Logout_f(void) { extern cvar_t sv_login; if (sv_client->logged_in_via_web) { if (sv_client->login[0]) { SV_BroadcastPrintf(PRINT_HIGH, "%s logged out\n", sv_client->name); } SV_Logout(sv_client); // if (!(int)sv_login.value || ((int)sv_login.value == 1 && sv_client->spectator)) { sv_client->logged = -1; } } // If logins are mandatory then treat as disconnect if ((int)sv_login.value > 1 || ((int)sv_login.value == 1 && !sv_client->spectator)) { SV_DropClient(sv_client); } } // } Central login #endif void SV_DemoList_f(void); void SV_DemoListRegex_f(void); void SV_MVDInfo_f(void); void SV_LastScores_f(void); // { bans void SV_Cmd_Ban_f(void); void SV_Cmd_Banip_f(void); void SV_Cmd_Banremove_f(void); // } bans // { qtv void Cmd_Qtvusers_f (void); // } // { cheats void SV_God_f (void); void SV_Give_f (void); void SV_Noclip_f (void); void SV_Fly_f (void); // } #if defined(SERVERONLY) && defined(WWW_INTEGRATION) // { central login void Cmd_Login_f(void); void Cmd_Logout_f(void); void Cmd_ChallengeResponse_f(void); // } #endif typedef struct { char *name; void (*func) (void); qbool overrideable; } ucmd_t; static ucmd_t ucmds[] = { {"new", Cmd_New_f, false}, {"modellist", Cmd_Modellist_f, false}, {"soundlist", Cmd_Soundlist_f, false}, {"prespawn", Cmd_PreSpawn_f, false}, {"spawn", Cmd_Spawn_f, false}, {"begin", Cmd_Begin_f, false}, {"drop", Cmd_Drop_f, false}, {"pings", Cmd_Pings_f, false}, // issued by hand at client consoles {"rate", Cmd_Rate_f, true}, {"kill", Cmd_Kill_f, true}, {"pause", Cmd_Pause_f, true}, {"say", Cmd_Say_f, true}, {"say_team", Cmd_Say_Team_f, true}, {"setinfo", Cmd_SetInfo_f, false}, {"serverinfo", Cmd_ShowServerinfo_f, false}, {"download", Cmd_Download_f, false}, {"nextdl", Cmd_NextDownload_f, false}, {"dl", Cmd_DemoDownload_f, false}, {"ptrack", Cmd_PTrack_f, false}, //ZOID - used with autocam //bliP: file upload -> {"techlogin", Cmd_TechLogin_f, false}, {"upload", Cmd_Upload_f, false}, //<- {"snap", Cmd_NoSnap_f, false}, {"stopdownload", Cmd_StopDownload_f, false}, {"stopdl", Cmd_StopDownload_f, false}, {"dlist", SV_DemoList_f, false}, {"dlistr", SV_DemoListRegex_f, false}, {"dlistregex", SV_DemoListRegex_f, false}, {"demolist", SV_DemoList_f, false}, {"demolistr", SV_DemoListRegex_f, false}, {"demolistregex", SV_DemoListRegex_f, false}, {"demoinfo", SV_MVDInfo_f, false}, {"lastscores", SV_LastScores_f, false}, {"minping", Cmd_MinPing_f, true}, {"airstep", Cmd_AirStep_f, true}, {"maps", Cmd_ShowMapsList_f, true}, {"ban", SV_Cmd_Ban_f, true}, // internal server ban support {"banip", SV_Cmd_Banip_f, true}, // internal server ban support {"banrem", SV_Cmd_Banremove_f, true}, // internal server ban support {"join", Cmd_Join_f, true}, {"observe", Cmd_Observe_f, true}, {"qtvusers", Cmd_Qtvusers_f, true}, // cheat commands {"god", SV_God_f, true}, {"give", SV_Give_f, true}, {"noclip", SV_Noclip_f, true}, {"fly", SV_Fly_f, true}, #ifdef FTE_PEXT2_VOICECHAT {"voicetarg", SV_Voice_Target_f, false}, {"vignore", SV_Voice_Ignore_f, false}, /*ignore/mute specific player*/ {"muteall", SV_Voice_MuteAll_f, false}, /*disables*/ {"unmuteall", SV_Voice_UnmuteAll_f, false}, /*reenables*/ #endif {"pext", Cmd_PEXT_f, false}, // user reply with supported protocol extensions. #if defined(SERVERONLY) && defined(WWW_INTEGRATION) {"login", Cmd_Login_f, false}, {"login-response", Cmd_ChallengeResponse_f, false}, {"logout", Cmd_Logout_f, false}, #endif {NULL, NULL} }; static qbool SV_ExecutePRCommand (void) { pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); return PR_ClientCmd(); } /* ================== SV_ExecuteUserCommand ================== */ static void SV_ExecuteUserCommand (char *s) { ucmd_t *u; Cmd_TokenizeString (s); sv_player = sv_client->edict; SV_BeginRedirect (RD_CLIENT); for (u=ucmds ; u->name ; u++) { if (!strcmp (Cmd_Argv(0), u->name) ) { if (!u->overrideable) { u->func(); goto out; } break; } } if (SV_ExecutePRCommand()) goto out; if (u->name) u->func(); else Con_Printf("Bad user command: %s\n", Cmd_Argv(0)); out: SV_EndRedirect (); } /* =========================================================================== USER CMD EXECUTION =========================================================================== */ /* ==================== AddLinksToPmove ==================== */ static void AddLinksToPmove ( areanode_t *node ) { link_t *l, *next; edict_t *check; int pl; int i; physent_t *pe; vec3_t pmove_mins, pmove_maxs; for (i=0 ; i<3 ; i++) { pmove_mins[i] = pmove.origin[i] - 256; pmove_maxs[i] = pmove.origin[i] + 256; } pl = EDICT_TO_PROG(sv_player); // touch linked edicts for (l = node->solid_edicts.next ; l != &node->solid_edicts ; l = next) { next = l->next; check = EDICT_FROM_AREA(l); if (check->v->owner == pl) continue; // player's own missile if (check->v->solid == SOLID_BSP || check->v->solid == SOLID_BBOX || check->v->solid == SOLID_SLIDEBOX) { if (check == sv_player) continue; for (i=0 ; i<3 ; i++) if (check->v->absmin[i] > pmove_maxs[i] || check->v->absmax[i] < pmove_mins[i]) break; if (i != 3) continue; if (pmove.numphysent == MAX_PHYSENTS) return; pe = &pmove.physents[pmove.numphysent]; pmove.numphysent++; VectorCopy (check->v->origin, pe->origin); pe->info = NUM_FOR_EDICT(check); if (check->v->solid == SOLID_BSP) { if ((unsigned)check->v->modelindex >= MAX_MODELS) SV_Error ("AddLinksToPmove: check->v->modelindex >= MAX_MODELS"); pe->model = sv.models[(int)(check->v->modelindex)]; if (!pe->model) SV_Error ("SOLID_BSP with a non-bsp model"); } else { pe->model = NULL; VectorCopy (check->v->mins, pe->mins); VectorCopy (check->v->maxs, pe->maxs); } } } // recurse down both sides if (node->axis == -1) return; if ( pmove_maxs[node->axis] > node->dist ) AddLinksToPmove ( node->children[0] ); if ( pmove_mins[node->axis] < node->dist ) AddLinksToPmove ( node->children[1] ); } int SV_PMTypeForClient (client_t *cl) { if (cl->edict->v->movetype == MOVETYPE_NOCLIP) { if (cl->extensions & Z_EXT_PM_TYPE_NEW) return PM_SPECTATOR; return PM_OLD_SPECTATOR; } if (cl->edict->v->movetype == MOVETYPE_FLY) return PM_FLY; if (cl->edict->v->movetype == MOVETYPE_NONE) return PM_NONE; if (cl->edict->v->movetype == MOVETYPE_LOCK) return PM_LOCK; if (cl->edict->v->health <= 0) return PM_DEAD; return PM_NORMAL; } /* =========== SV_PreRunCmd =========== Done before running a player command. Clears the touch array */ static byte playertouch[(MAX_EDICTS+7)/8]; void SV_PreRunCmd(void) { memset(playertouch, 0, sizeof(playertouch)); } /* =========== SV_RunCmd =========== */ void SV_RunCmd (usercmd_t *ucmd, qbool inside, qbool second_attempt) //bliP: 24/9 { int i, n; vec3_t originalvel, offset; qbool onground; //bliP: 24/9 anti speed -> int tmp_time; int blocked; if (!inside && (int)sv_speedcheck.value #ifdef USE_PR2 && !sv_client->isBot #endif ) { /* AM101 method */ tmp_time = Q_rint((realtime - sv_client->last_check) * 1000); // ie. Old 'timepassed' if (tmp_time) { if (ucmd->msec > tmp_time) { tmp_time += sv_client->msecs; // use accumulated msecs if (ucmd->msec > tmp_time) { // If still over... ucmd->msec = tmp_time; sv_client->msecs = 0; } else { sv_client->msecs = tmp_time - ucmd->msec; // readjust to leftovers } } else { // Add up extra msecs sv_client->msecs += (tmp_time - ucmd->msec); } } sv_client->last_check = realtime; /* Cap it */ if (sv_client->msecs > 500) sv_client->msecs = 500; else if (sv_client->msecs < 0) sv_client->msecs = 0; } //<- cmd = *ucmd; // chop up very long command if (cmd.msec > 50) { int oldmsec; oldmsec = ucmd->msec; cmd.msec = oldmsec/2; SV_RunCmd (&cmd, true, second_attempt); cmd.msec = oldmsec/2; cmd.impulse = 0; SV_RunCmd (&cmd, true, second_attempt); return; } // copy humans' intentions to progs sv_player->v->button0 = ucmd->buttons & 1; sv_player->v->button2 = (ucmd->buttons & 2) >> 1; sv_player->v->button1 = (ucmd->buttons & 4) >> 2; if (ucmd->impulse) sv_player->v->impulse = ucmd->impulse; if (fofs_movement) { EdictFieldVector(sv_player, fofs_movement)[0] = ucmd->forwardmove; EdictFieldVector(sv_player, fofs_movement)[1] = ucmd->sidemove; EdictFieldVector(sv_player, fofs_movement)[2] = ucmd->upmove; } // bliP: cuff if (sv_client->cuff_time > curtime) sv_player->v->button0 = sv_player->v->impulse = 0; //<- // clamp view angles ucmd->angles[PITCH] = bound(sv_minpitch.value, ucmd->angles[PITCH], sv_maxpitch.value); if (!sv_player->v->fixangle && ! second_attempt) VectorCopy (ucmd->angles, sv_player->v->v_angle); // model angles // show 1/3 the pitch angle and all the roll angle if (sv_player->v->health > 0) { if (!sv_player->v->fixangle) { sv_player->v->angles[PITCH] = -sv_player->v->v_angle[PITCH]/3; sv_player->v->angles[YAW] = sv_player->v->v_angle[YAW]; } sv_player->v->angles[ROLL] = 0; } sv_frametime = ucmd->msec * 0.001; if (sv_frametime > 0.1) sv_frametime = 0.1; // Don't run think function twice... if (!sv_client->spectator && !second_attempt) { vec3_t oldvelocity; float old_teleport_time; VectorCopy (sv_player->v->velocity, originalvel); onground = (int) sv_player->v->flags & FL_ONGROUND; VectorCopy (sv_player->v->velocity, oldvelocity); old_teleport_time = sv_player->v->teleport_time; PR_GLOBAL(frametime) = sv_frametime; pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); PR_GameClientPreThink(0); if (pr_nqprogs) { sv_player->v->teleport_time = old_teleport_time; VectorCopy (oldvelocity, sv_player->v->velocity); } if ( onground && originalvel[2] < 0 && sv_player->v->velocity[2] == 0 && originalvel[0] == sv_player->v->velocity[0] && originalvel[1] == sv_player->v->velocity[1] ) { // don't let KTeams mess with physics sv_player->v->velocity[2] = originalvel[2]; } SV_RunThink (sv_player); } // copy player state to pmove VectorSubtract (sv_player->v->mins, player_mins, offset); VectorAdd (sv_player->v->origin, offset, pmove.origin); VectorCopy (sv_player->v->velocity, pmove.velocity); VectorCopy (sv_player->v->v_angle, pmove.angles); pmove.waterjumptime = sv_player->v->teleport_time; pmove.cmd = *ucmd; pmove.pm_type = SV_PMTypeForClient (sv_client); pmove.onground = ((int)sv_player->v->flags & FL_ONGROUND) != 0; pmove.jump_held = sv_client->jump_held; pmove.jump_msec = 0; // let KTeams "broken ankle" code work if ( #if 0 FIXME PR_GetEdictFieldValue(sv_player, "brokenankle") && #endif (pmove.velocity[2] == -270) && (pmove.cmd.buttons & BUTTON_JUMP)) pmove.jump_held = true; // build physent list pmove.numphysent = 1; pmove.physents[0].model = sv.worldmodel; AddLinksToPmove ( sv_areanodes ); // fill in movevars movevars.entgravity = sv_client->entgravity; movevars.maxspeed = sv_client->maxspeed; movevars.bunnyspeedcap = pm_bunnyspeedcap.value; movevars.ktjump = pm_ktjump.value; movevars.slidefix = ((int)pm_slidefix.value != 0); movevars.airstep = ((int)pm_airstep.value != 0); movevars.pground = ((int)pm_pground.value != 0); movevars.rampjump = ((int)pm_rampjump.value != 0); // do the move blocked = PM_PlayerMove (); #ifdef USE_PR2 // This is a temporary hack for Frogbots, who adjust after bumping into things // Better would be to provide a way to simulate a move command, but at least this doesn't require API change if (blocked && !second_attempt && sv_client->isBot && sv_player->v->blocked) { pr_global_struct->self = EDICT_TO_PROG(sv_player); // Don't store in the bot's entity as we will run this again VectorSubtract (pmove.origin, offset, pr_global_struct->trace_endpos); VectorCopy (pmove.velocity, pr_global_struct->trace_plane_normal); if (pmove.onground) { pr_global_struct->trace_allsolid = (int) sv_player->v->flags | FL_ONGROUND; pr_global_struct->trace_ent = EDICT_TO_PROG(EDICT_NUM(pmove.physents[pmove.groundent].info)); } else { pr_global_struct->trace_allsolid = (int) sv_player->v->flags & ~FL_ONGROUND; } // Give the mod a chance to replace the command PR_EdictBlocked (sv_player->v->blocked); // Run the command again SV_RunCmd (ucmd, false, true); return; } #endif // get player state back out of pmove sv_client->jump_held = pmove.jump_held; sv_player->v->teleport_time = pmove.waterjumptime; if (pr_nqprogs) sv_player->v->flags = ((int)sv_player->v->flags & ~FL_WATERJUMP) | (pmove.waterjumptime ? FL_WATERJUMP : 0); sv_player->v->waterlevel = pmove.waterlevel; sv_player->v->watertype = pmove.watertype; if (pmove.onground) { sv_player->v->flags = (int) sv_player->v->flags | FL_ONGROUND; sv_player->v->groundentity = EDICT_TO_PROG(EDICT_NUM(pmove.physents[pmove.groundent].info)); } else { sv_player->v->flags = (int) sv_player->v->flags & ~FL_ONGROUND; } VectorSubtract (pmove.origin, offset, sv_player->v->origin); VectorCopy (pmove.velocity, sv_player->v->velocity); VectorCopy (pmove.angles, sv_player->v->v_angle); if (sv_player->v->solid != SOLID_NOT) { // link into place and touch triggers SV_LinkEdict (sv_player, true); // touch other objects for (i=0 ; iv->touch || (playertouch[n/8]&(1<<(n%8)))) continue; pr_global_struct->self = EDICT_TO_PROG(ent); pr_global_struct->other = EDICT_TO_PROG(sv_player); PR_EdictTouch (ent->v->touch); playertouch[n/8] |= 1 << (n%8); } } } #ifdef MVD_PEXT1_SERVERSIDEWEAPON typedef struct ssw_info_s { int impulse_set; int hide_weapon; int best_weapon; qbool hiding; qbool firing; } ssw_info_t; // Ranks best weapon for player void SV_ServerSideWeaponRank(client_t* client, int* best_weapon, int* best_impulse, int* hide_weapon, int* hide_impulse) { entvars_t* ent = client->edict->v; int i; int items = (int)ent->items; int weapon = (int)ent->weapon; int shells = (int)ent->ammo_shells; int nails = (int)ent->ammo_nails; int rockets = (int)ent->ammo_rockets; int cells = (int)ent->ammo_cells; if (client->weaponswitch_hide == 2 && shells > 0) { *hide_weapon = IT_SHOTGUN; *hide_impulse = 2; } else if (client->weaponswitch_hide == 1) { *hide_weapon = IT_AXE; *hide_impulse = 1; } else { *hide_weapon = 0; *hide_impulse = 0; } // Default to staying on the current weapon, regardless of ammo *best_weapon = weapon; *best_impulse = 0; for (i = 0; i < sizeof(client->weaponswitch_priority) / sizeof(client->weaponswitch_priority[0]); ++i) { switch (client->weaponswitch_priority[i]) { case 0: // end of list return; case 1: if (items & IT_AXE) { *best_weapon = IT_AXE; *best_impulse = 1; return; } break; case 2: if ((items & IT_SHOTGUN) && shells > 0) { *best_weapon = IT_SHOTGUN; *best_impulse = 2; return; } break; case 3: if ((items & IT_SUPER_SHOTGUN) && shells > 1) { *best_weapon = IT_SUPER_SHOTGUN; *best_impulse = 3; return; } break; case 4: if ((items & IT_NAILGUN) && nails > 0) { *best_weapon = IT_NAILGUN; *best_impulse = 4; return; } break; case 5: if ((items & IT_SUPER_NAILGUN) && nails > 1) { *best_weapon = IT_SUPER_NAILGUN; *best_impulse = 5; return; } break; case 6: if ((items & IT_GRENADE_LAUNCHER) && rockets > 0) { *best_weapon = IT_GRENADE_LAUNCHER; *best_impulse = 6; return; } break; case 7: if ((items & IT_ROCKET_LAUNCHER) && rockets > 0) { *best_weapon = IT_ROCKET_LAUNCHER; *best_impulse = 7; return; } break; case 8: if ((items & IT_LIGHTNING) && cells > 0) { *best_weapon = IT_LIGHTNING; *best_impulse = 8; return; } break; } } return; } static void SV_NotifyUserOfBestWeapon(client_t* sv_client, int new_impulse) { char stuffcmd_buffer[64]; strlcpy(stuffcmd_buffer, va("//mvdsv_ssw %d %d\n", sv_client->weaponswitch_sequence_set, new_impulse), sizeof(stuffcmd_buffer)); ClientReliableWrite_Begin(sv_client, svc_stufftext, 2 + strlen(stuffcmd_buffer)); ClientReliableWrite_String(sv_client, stuffcmd_buffer); } static void SV_ExecuteServerSideWeaponForgetOrder(client_t* sv_client, int best_impulse, int hide_impulse) { char new_wrank[16] = { 0 }; SV_DebugServerSideWeaponScript(sv_client, best_impulse); SV_NotifyUserOfBestWeapon(sv_client, best_impulse); // Over-write the list sent with the result { if (Info_Get(&sv_client->_userinfo_ctx_, "dev")[0] == '1') { SV_ClientPrintf(sv_client, PRINT_HIGH, "Best: %d, forgetorder enabled\n", best_impulse); } } sv_client->weaponswitch_priority[0] = best_impulse; sv_client->weaponswitch_priority[1] = (hide_impulse == 1 || best_impulse == 2 ? 1 : 2); sv_client->weaponswitch_priority[2] = (sv_client->weaponswitch_priority[1] != 1 ? 1 : 0); sv_client->weaponswitch_priority[3] = 0; new_wrank[0] = '0' + best_impulse; if (hide_impulse) { new_wrank[1] = '0' + hide_impulse; if (hide_impulse == 2) { new_wrank[2] = '1'; } } else { new_wrank[1] = '2'; new_wrank[2] = '1'; } SV_UserSetWeaponRank(sv_client, new_wrank); } static void SV_ExecuteServerSideWeaponHideOnDeath(client_t* sv_client, int hide_impulse, int hide_weapon) { char new_wrank[16] = { 0 }; if (sv_client->edict->v->health > 0.0f || !sv_client->weaponswitch_hide_on_death) { return; } // might not have general hiding enabled... hide_impulse = (hide_impulse == 1 ? 1 : 2); hide_weapon = (hide_impulse == 1 ? IT_AXE : IT_SHOTGUN); new_wrank[0] = (hide_impulse == 1 ? '1' : '2'); new_wrank[1] = (hide_impulse == 1 ? '0' : '1'); sv_client->weaponswitch_priority[0] = (hide_impulse == 1 ? 1 : 2); sv_client->weaponswitch_priority[1] = (hide_impulse == 1 ? 1 : 2); sv_client->weaponswitch_priority[2] = 0; SV_DebugServerSideWeaponScript(sv_client, hide_impulse); SV_NotifyUserOfBestWeapon(sv_client, hide_impulse); SV_UserSetWeaponRank(sv_client, new_wrank); if (Info_Get(&sv_client->_userinfo_ctx_, "dev")[0] == '1' && sv_client->edict->v->weapon != hide_weapon) { SV_ClientPrintf(sv_client, PRINT_HIGH, "Hiding on death: %d\n", hide_impulse); } } static void SV_ServerSideWeaponLogic_PrePostThink(client_t* sv_client, ssw_info_t* ssw) { entvars_t* ent = sv_client->edict->v; qbool dev_trace = (Info_Get(&sv_client->_userinfo_ctx_, "dev")[0] == '1'); ssw->firing = (ent->button0 != 0); if ((sv_client->mvdprotocolextensions1 & MVD_PEXT1_SERVERSIDEWEAPON) && sv_client->weaponswitch_enabled) { int best_impulse, hide_impulse; qbool switch_to_best_weapon = false; int mode = sv_client->weaponswitch_mode; // modes: 0 immediately choose best, 1 preselect (wait until fire), 2 immediate if firing // mode 2 = "preselect(1) when not holding +attack, else immediate(0)" if (mode == 2) { mode = (ssw->firing ? 0 : 1); } switch_to_best_weapon = sv_client->weaponswitch_pending && (mode == 0 || ssw->firing) && (ent->health >= 1.0f); SV_ServerSideWeaponRank(sv_client, &ssw->best_weapon, &best_impulse, &ssw->hide_weapon, &hide_impulse); ssw->hiding = (sv_client->weaponswitch_wasfiring && !ssw->firing && hide_impulse); sv_client->weaponswitch_wasfiring |= ssw->firing; if (switch_to_best_weapon && sv_client->weaponswitch_forgetorder) { SV_ExecuteServerSideWeaponForgetOrder(sv_client, best_impulse, hide_impulse); } SV_ExecuteServerSideWeaponHideOnDeath(sv_client, hide_impulse, ssw->hide_weapon); if (!ent->impulse) { if (switch_to_best_weapon) { if (best_impulse && ent->weapon != ssw->best_weapon) { SV_DebugServerSideWeaponScript(sv_client, best_impulse); if (dev_trace) { SV_ClientPrintf(sv_client, PRINT_HIGH, "Switching to best weapon: %d\n", best_impulse); } ent->impulse = best_impulse; ssw->impulse_set = 2; } else { sv_client->weaponswitch_pending = false; } } else if (ssw->hiding) { if (ent->weapon != ssw->hide_weapon) { if (dev_trace) { SV_ClientPrintf(sv_client, PRINT_HIGH, "Hiding: %d\n", hide_impulse); } ent->impulse = hide_impulse; ssw->impulse_set = 1; } else { sv_client->weaponswitch_pending = false; } } } else { if (dev_trace) { SV_ClientPrintf(sv_client, PRINT_HIGH, "Non-wp impulse: %f\n", ent->impulse); } } sv_client->weaponswitch_pending &= (ent->health >= 1.0f); } } static void SV_ServerSideWeaponLogic_PostPostThink(client_t* sv_client, ssw_info_t* ssw) { entvars_t* ent = sv_client->edict->v; qbool dev_trace = (Info_Get(&sv_client->_userinfo_ctx_, "dev")[0] == '1'); if (ssw->impulse_set) { qbool hide_failed = (ssw->impulse_set == 1 && ent->weapon != ssw->hide_weapon); qbool pickbest_failed = (ssw->impulse_set == 2 && ent->weapon != ssw->best_weapon); qbool failure = (hide_failed || pickbest_failed); ent->impulse = 0; if (dev_trace) { if (failure) { SV_ClientPrintf(sv_client, PRINT_HIGH, "... %s failed, will try again\n", ssw->impulse_set == 1 ? "hide" : "pickbest"); } else { SV_ClientPrintf(sv_client, PRINT_HIGH, "... %s successful, stopping\n", ssw->impulse_set == 1 ? "hide" : "pickbest"); } } sv_client->weaponswitch_pending &= failure; } if (ssw->hiding && ent->weapon == ssw->hide_weapon) { if (dev_trace) { SV_ClientPrintf(sv_client, PRINT_HIGH, "Hide successful\n"); } sv_client->weaponswitch_wasfiring = false; } else if (!(ssw->hiding || ssw->firing)) { if (sv_client->weaponswitch_wasfiring && dev_trace) { SV_ClientPrintf(sv_client, PRINT_HIGH, "No longer firing...\n"); } sv_client->weaponswitch_wasfiring = false; } } #endif /* =========== SV_PostRunCmd =========== Done after running a player command. */ void SV_PostRunCmd(void) { vec3_t originalvel; qbool onground; // run post-think #ifdef MVD_PEXT1_SERVERSIDEWEAPON ssw_info_t ssw = { 0 }; #endif if (!sv_client->spectator) { #ifdef MVD_PEXT1_SERVERSIDEWEAPON SV_ServerSideWeaponLogic_PrePostThink(sv_client, &ssw); #endif onground = (int) sv_player->v->flags & FL_ONGROUND; pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); VectorCopy (sv_player->v->velocity, originalvel); PR_GameClientPostThink(0); if ( onground && originalvel[2] < 0 && sv_player->v->velocity[2] == 0 && originalvel[0] == sv_player->v->velocity[0] && originalvel[1] == sv_player->v->velocity[1] ) { // don't let KTeams mess with physics sv_player->v->velocity[2] = originalvel[2]; } if (pr_nqprogs) VectorCopy (originalvel, sv_player->v->velocity); if (pr_nqprogs) SV_RunNQNewmis (); else SV_RunNewmis (); #ifdef MVD_PEXT1_SERVERSIDEWEAPON SV_ServerSideWeaponLogic_PostPostThink(sv_client, &ssw); #endif } else { pr_global_struct->time = sv.time; pr_global_struct->self = EDICT_TO_PROG(sv_player); PR_GameClientPostThink(1); } } /* SV_UserSetWeaponRank Sets wrank userinfo for mods to pick best weapon based on user's preferences */ static void SV_UserSetWeaponRank(client_t* cl, const char* new_wrank) { char old_wrank[128] = { 0 }; strlcpy(old_wrank, Info_Get(&cl->_userinfo_ctx_, "w_rank"), sizeof(old_wrank)); if (strcmp(old_wrank, new_wrank)) { Info_Set(&cl->_userinfo_ctx_, "w_rank", new_wrank); ProcessUserInfoChange(cl, "w_rank", old_wrank); if (Info_Get(&cl->_userinfo_ctx_, "dev")[0] == '1') { SV_ClientPrintf(cl, PRINT_HIGH, "Setting new w_rank: %s\n", new_wrank); } } } // SV_RotateCmd // Rotates client command so a high-ping player can better control direction as they exit teleporters on high-ping void SV_RotateCmd(client_t* cl, usercmd_t* cmd_) { if (cl->lastteleport_teleport) { static vec3_t up = { 0, 0, 1 }; vec3_t direction = { cmd_->sidemove, cmd_->forwardmove, 0 }; vec3_t result; RotatePointAroundVector(result, up, direction, cl->lastteleport_teleportyaw); cmd_->sidemove = result[0]; cmd_->forwardmove = result[1]; } else { cmd_->angles[YAW] = (cl->edict)->v->angles[YAW]; } } /* =================== SV_ExecuteClientMove Run one or more client move commands (more than one if some packets were dropped) =================== */ static void SV_ExecuteClientMove(client_t* cl, usercmd_t oldest, usercmd_t oldcmd, usercmd_t newcmd) { int net_drop; int playernum = cl - svs.clients; if (sv.paused) { return; } SV_PreRunCmd(); net_drop = cl->netchan.dropped; if (net_drop < 20) { while (net_drop > 2) { SV_DebugClientCommand(playernum, &cl->lastcmd, net_drop); SV_RunCmd(&cl->lastcmd, false, false); net_drop--; } } if (net_drop > 1) { SV_DebugClientCommand(playernum, &oldest, 2); SV_RunCmd(&oldest, false, false); } if (net_drop > 0) { SV_DebugClientCommand(playernum, &oldcmd, 1); SV_RunCmd(&oldcmd, false, false); } SV_DebugClientCommand(playernum, &newcmd, 0); #ifdef MVD_PEXT1_SERVERSIDEWEAPON { // This is necessary to interrupt LG/SNG where the firing takes place inside animation frames if (sv_client->weaponswitch_enabled && sv_client->weaponswitch_pending && !sv_client->edict->v->impulse) { sv_client->edict->v->impulse = 255; SV_RunCmd(&newcmd, false, false); sv_client->edict->v->impulse = 0; } else { SV_RunCmd(&newcmd, false, false); } } #else SV_RunCmd(&newcmd, false, false); #endif SV_PostRunCmd(); } #ifdef MVD_PEXT1_DEBUG_ANTILAG /* SV_DebugWriteServerAntilagPositions Writes the position of clients, as rewound by antilag */ static void SV_DebugWriteServerAntilagPositions(client_t* cl, int present) { mvdhidden_block_header_t header; mvdhidden_antilag_position_header_t antilag_header; int i; float target_time = cl->laggedents_time; header.type_id = mvdhidden_antilag_position; header.length = sizeof_mvdhidden_antilag_position_header_t + present * sizeof_mvdhidden_antilag_position_t; antilag_header.incoming_seq = LittleLong(cl->netchan.incoming_sequence); antilag_header.playernum = cl - svs.clients; antilag_header.players = present; antilag_header.server_time = LittleFloat(sv.time); antilag_header.target_time = LittleFloat(target_time); if (MVDWrite_HiddenBlockBegin(sizeof_mvdhidden_block_header_t_range0 + header.length)) { header.length = LittleLong(header.length); MVD_SZ_Write(&header.length, sizeof(header.length)); MVD_SZ_Write(&header.type_id, sizeof(header.type_id)); MVD_SZ_Write(&antilag_header.playernum, sizeof(antilag_header.playernum)); MVD_SZ_Write(&antilag_header.players, sizeof(antilag_header.players)); MVD_SZ_Write(&antilag_header.incoming_seq, sizeof(antilag_header.incoming_seq)); MVD_SZ_Write(&antilag_header.server_time, sizeof(antilag_header.server_time)); MVD_SZ_Write(&antilag_header.target_time, sizeof(antilag_header.target_time)); for (i = 0; i < MAX_CLIENTS; i++) { if (cl->laggedents[i].present) { mvdhidden_antilag_position_t pos = { 0 }; int j; pos.playernum = i; #ifdef MVD_PEXT1_DEBUG if (debug_info.antilag_clients[i].present) { pos.playernum |= MVD_PEXT1_ANTILAG_CLIENTPOS; pos.msec = debug_info.antilag_clients[i].msec; pos.predmodel = debug_info.antilag_clients[i].model; VectorCopy(debug_info.antilag_clients[i].pos, pos.clientpos); } #endif MVD_SZ_Write(&pos.playernum, sizeof(pos.playernum)); for (j = 0; j < 3; ++j) { pos.pos[j] = LittleFloat(cl->laggedents[i].laggedpos[j]); MVD_SZ_Write(&pos.pos[j], sizeof(pos.pos[j])); } MVD_SZ_Write(&pos.msec, sizeof(pos.msec)); MVD_SZ_Write(&pos.predmodel, sizeof(pos.predmodel)); for (j = 0; j < 3; ++j) { pos.clientpos[j] = LittleFloat(pos.clientpos[j]); MVD_SZ_Write(&pos.clientpos[j], sizeof(pos.clientpos[j])); } } } } } #endif // MVD_PEXT1_DEBUG_ANTILAG #ifdef MVD_PEXT1_DEBUG_WEAPON static void SV_DebugWriteWeaponScript(byte playernum, qbool server_side, int items, byte shells, byte nails, byte rockets, byte cells, byte choice, const char* weaponlist) { if (sv_debug_weapons.value >= 1) { mvdhidden_block_header_t header; // Write out immediately header.type_id = (server_side ? mvdhidden_usercmd_weapons_ss : mvdhidden_usercmd_weapons); header.length = LittleLong(10 + strlen(weaponlist) + 1); if (MVDWrite_HiddenBlockBegin(sizeof_mvdhidden_block_header_t_range0 + header.length)) { MVD_SZ_Write(&header.length, sizeof(header.length)); MVD_SZ_Write(&header.type_id, sizeof(header.type_id)); MVD_SZ_Write(&playernum, sizeof(playernum)); MVD_SZ_Write(&items, sizeof(items)); MVD_SZ_Write(&shells, sizeof(shells)); MVD_SZ_Write(&nails, sizeof(nails)); MVD_SZ_Write(&rockets, sizeof(rockets)); MVD_SZ_Write(&cells, sizeof(cells)); MVD_SZ_Write(&choice, sizeof(choice)); MVD_SZ_Write(weaponlist, strlen(weaponlist) + 1); } } } static void SV_DebugClientSideWeaponScript(client_t* cl) { byte playernum = cl - svs.clients; int items = LittleLong(MSG_ReadLong()); byte shells = MSG_ReadByte(); byte nails = MSG_ReadByte(); byte rockets = MSG_ReadByte(); byte cells = MSG_ReadByte(); byte choice = MSG_ReadByte(); const char* weaponlist = MSG_ReadString(); SV_DebugWriteWeaponScript(playernum, false, items, shells, nails, rockets, cells, choice, weaponlist); } static void SV_DebugServerSideWeaponInstruction(client_t* cl) { if (sv_debug_weapons.value >= 1) { mvdhidden_block_header_t header; // Write out immediately header.type_id = mvdhidden_usercmd_weapon_instruction; header.length = sizeof_mvdhidden_usercmd_weapon_instruction; if (MVDWrite_HiddenBlockBegin(sizeof_mvdhidden_block_header_t_range0 + header.length)) { byte playernum = cl - svs.clients; byte flags = 0; int sequence_set = cl->weaponswitch_sequence_set; int mode = cl->weaponswitch_mode; byte weaponlist[10] = { 0 }; flags |= (cl->weaponswitch_pending ? MVDHIDDEN_SSWEAPON_PENDING : 0); flags |= (cl->weaponswitch_hide == 1 ? MVDHIDDEN_SSWEAPON_HIDE_AXE : (cl->weaponswitch_hide == 2 ? MVDHIDDEN_SSWEAPON_HIDE_SG : 0)); flags |= (cl->weaponswitch_hide_on_death ? MVDHIDDEN_SSWEAPON_HIDEONDEATH : 0); flags |= (cl->weaponswitch_wasfiring ? MVDHIDDEN_SSWEAPON_WASFIRING : 0); flags |= (cl->weaponswitch_enabled ? MVDHIDDEN_SSWEAPON_ENABLED : 0); flags |= (cl->weaponswitch_forgetorder ? MVDHIDDEN_SSWEAPON_FORGETORDER : 0); memcpy(weaponlist, cl->weaponswitch_priority, min(10, sizeof(cl->weaponswitch_priority))); MVD_SZ_Write(&header.length, sizeof(header.length)); MVD_SZ_Write(&header.type_id, sizeof(header.type_id)); MVD_SZ_Write(&playernum, sizeof(playernum)); MVD_SZ_Write(&flags, sizeof(flags)); MVD_SZ_Write(&sequence_set, sizeof(sequence_set)); MVD_SZ_Write(&mode, sizeof(mode)); MVD_SZ_Write(weaponlist, sizeof(weaponlist)); } } } static void SV_DebugServerSideWeaponScript(client_t* cl, int best_impulse) { if (sv_debug_weapons.value >= 1) { char old_wrank[128] = { 0 }; char encoded[128] = { 0 }; char* w; char* o; entvars_t* ent = cl->edict->v; strlcpy(old_wrank, Info_Get(&cl->_userinfo_ctx_, "w_rank"), sizeof(old_wrank)); w = old_wrank; o = encoded; while (*w) { *o = (*w - '0'); ++w; ++o; } SV_DebugWriteWeaponScript(cl - svs.clients, true, ent->items, ent->ammo_shells, ent->ammo_nails, ent->ammo_rockets, ent->ammo_cells, best_impulse, encoded); } } #endif /* =================== SV_ExecuteClientMessage The current net_message is parsed for the given client =================== */ void SV_ExecuteClientMessage (client_t *cl) { int c, i; char *s; usercmd_t oldest, oldcmd, newcmd; client_frame_t *frame; vec3_t o; qbool move_issued = false; //only allow one move command int checksumIndex; byte checksum, calculatedChecksum; int seq_hash; #ifdef MVD_PEXT1_DEBUG int antilag_players_present = 0; #endif if (!Netchan_Process(&cl->netchan)) return; // if (cl->state == cs_zombie) // FIXME // return; // FIXME // this is a valid, sequenced packet, so process it svs.stats.packets++; cl->send_message = true; // reply at end of frame #ifdef FTE_PEXT_CHUNKEDDOWNLOADS cl->download_chunks_perframe = 0; #endif // calc ping time frame = &cl->frames[cl->netchan.incoming_acknowledged & UPDATE_MASK]; frame->ping_time = curtime - frame->senttime; // update delay based on ping and sv_minping if (!cl->spectator && !sv.paused) { if (frame->ping_time * 1000 > sv_minping.value + 1) cl->delay -= 0.001; else if (frame->ping_time * 1000 < sv_minping.value) cl->delay += 0.001; cl->delay = bound(0, cl->delay, 1); } cl->laggedents_count = 0; // init at least this cl->laggedents_frac = 1; // sv_antilag_frac.value; if (sv_antilag.value) { //#pragma msg("FIXME: make antilag optionally support non-player ents too") #define MAX_PREDICTION 0.02 #define MAX_EXTRAPOLATE 0.02 double target_time, max_physfps = sv_maxfps.value; if (max_physfps < 20 || max_physfps > 1000) max_physfps = 77.0; if (sv_antilag_no_pred.value) { target_time = frame->sv_time; } else { // try to figure out what time client is currently predicting, basically this is just 6.5ms with 13ms ping and 13.5ms with higher // might be off with different max_physfps values target_time = min(frame->sv_time + (frame->ping_time < MAX_PREDICTION ? 1/max_physfps : MAX_PREDICTION), sv.time); } for (i = 0; i < MAX_CLIENTS; i++) { client_t *target_cl = &svs.clients[i]; antilag_position_t *base, *interpolate = NULL; double factor; int j; // don't hit dead players if (target_cl->state != cs_spawned || target_cl->antilag_position_next == 0 || (target_cl->spectator == 0 && target_cl->edict->v->health <= 0)) { cl->laggedents[i].present = false; continue; } cl->laggedents[i].present = true; ++antilag_players_present; // target player's movement commands are late, extrapolate his position based on velocity if (target_time > target_cl->localtime) { VectorMA(target_cl->edict->v->origin, min(target_time - target_cl->localtime, MAX_EXTRAPOLATE), target_cl->edict->v->velocity, cl->laggedents[i].laggedpos); continue; } // we have only one antilagged position, use that if (target_cl->antilag_position_next == 1) { VectorCopy(target_cl->antilag_positions[0].origin, cl->laggedents[i].laggedpos); continue; } // find the position before target time (base) and the one after that (interpolate) for (j = target_cl->antilag_position_next - 2; j > target_cl->antilag_position_next - 1 - MAX_ANTILAG_POSITIONS && j >= 0; j--) { if (target_cl->antilag_positions[j % MAX_ANTILAG_POSITIONS].localtime < target_time) break; } base = &target_cl->antilag_positions[j % MAX_ANTILAG_POSITIONS]; interpolate = &target_cl->antilag_positions[(j + 1) % MAX_ANTILAG_POSITIONS]; // we have two positions, just interpolate between them factor = (target_time - base->localtime) / (interpolate->localtime - base->localtime); VectorInterpolate(base->origin, factor, interpolate->origin, cl->laggedents[i].laggedpos); } cl->laggedents_count = MAX_CLIENTS; // FIXME: well, FTE do it a bit different way... cl->laggedents_time = target_time; } // make sure the reply sequence number matches the incoming // sequence number if (cl->netchan.incoming_sequence >= cl->netchan.outgoing_sequence) cl->netchan.outgoing_sequence = cl->netchan.incoming_sequence; else cl->send_message = false; // don't reply, sequences have slipped // save time for ping calculations cl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].senttime = curtime; cl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].ping_time = -1; sv_client = cl; sv_player = sv_client->edict; seq_hash = cl->netchan.incoming_sequence; // mark time so clients will know how much to predict // other players cl->localtime = sv.time; cl->delta_sequence = -1; // no delta unless requested #ifdef MVD_PEXT1_DEBUG memset(&debug_info, 0, sizeof(debug_info)); #endif while (1) { if (msg_badread) { Con_Printf ("SV_ReadClientMessage: badread\n"); SV_DropClient (cl); return; } c = MSG_ReadByte (); if (c == -1) break; switch (c) { default: Con_Printf ("SV_ReadClientMessage: unknown command char\n"); SV_DropClient (cl); return; case clc_nop: break; case clc_delta: cl->delta_sequence = MSG_ReadByte (); break; #ifdef MVD_PEXT1_DEBUG case clc_mvd_debug: { byte type = MSG_ReadByte(); if (type == clc_mvd_debug_type_antilag) { int players = MSG_ReadByte(); for (i = 0; i < players; ++i) { int num = MSG_ReadByte(); int msec = MSG_ReadByte(); int model = MSG_ReadByte(); float x = LittleFloat(MSG_ReadFloat()); float y = LittleFloat(MSG_ReadFloat()); float z = LittleFloat(MSG_ReadFloat()); if (num >= 0 && num < MAX_CLIENTS) { debug_info.antilag_clients[num].present = true; debug_info.antilag_clients[num].model = model; debug_info.antilag_clients[num].msec = msec; VectorSet(debug_info.antilag_clients[num].pos, x, y, z); } } } else if (type == clc_mvd_debug_type_weapon) { SV_DebugClientSideWeaponScript(cl); } else { Con_Printf("SV_ReadClientMessage: unknown debug message type %d\n", type); SV_DropClient(cl); } break; } #endif #ifdef MVD_PEXT1_SERVERSIDEWEAPON case clc_mvd_weapon: { if (!SV_ClientExtensionWeaponSwitch(cl)) { Con_Printf("SV_ClientExtensionWeaponSwitch: corrupt data string\n"); SV_DropClient(cl); return; } break; } #endif case clc_move: if (move_issued) return; // someone is trying to cheat... move_issued = true; checksumIndex = MSG_GetReadCount(); checksum = (byte)MSG_ReadByte (); // read loss percentage //bliP: file percent -> cl->lossage = MSG_ReadByte(); if (cl->file_percent) cl->lossage = cl->file_percent; /*if (cl->state < cs_spawned && cl->download != NULL) { if (cl->downloadsize) cl->lossage = cl->downloadcount*100/cl->downloadsize; else cl->lossage = 100; }*/ //<- #ifndef SERVERONLY MSG_ReadDeltaUsercmd (&nullcmd, &oldest, PROTOCOL_VERSION); MSG_ReadDeltaUsercmd (&oldest, &oldcmd, PROTOCOL_VERSION); MSG_ReadDeltaUsercmd (&oldcmd, &newcmd, PROTOCOL_VERSION); #else MSG_ReadDeltaUsercmd (&nullcmd, &oldest); MSG_ReadDeltaUsercmd (&oldest, &oldcmd); MSG_ReadDeltaUsercmd (&oldcmd, &newcmd); #endif if ( cl->state != cs_spawned ) break; #ifdef CHAT_ICON_EXPERIMENTAL s = Info_Get(&cl->_userinfoshort_ctx_, "chat"); if ( s[0] ) { // allow movement while in console // newcmd.forwardmove = newcmd.sidemove = newcmd.upmove = 0; newcmd.buttons &= BUTTON_JUMP; // only jump button allowed while in console // somemods uses impulses for commands, so let them use // newcmd.impulse = 0; } #endif // if the checksum fails, ignore the rest of the packet calculatedChecksum = COM_BlockSequenceCRCByte( net_message.data + checksumIndex + 1, MSG_GetReadCount() - checksumIndex - 1, seq_hash ); if (calculatedChecksum != checksum) { Con_DPrintf ("Failed command checksum for %s(%d) (%d != %d)\n", cl->name, cl->netchan.incoming_sequence, checksum, calculatedChecksum); return; } #ifdef MVD_PEXT1_HIGHLAGTELEPORT if (cl->mvdprotocolextensions1 & MVD_PEXT1_HIGHLAGTELEPORT) { if (cl->lastteleport_outgoingseq && cl->netchan.incoming_acknowledged < cl->lastteleport_outgoingseq) { if (cl->netchan.incoming_sequence - 2 > cl->lastteleport_incomingseq) { SV_RotateCmd(cl, &oldest); } if (cl->netchan.incoming_sequence - 1 > cl->lastteleport_incomingseq) { SV_RotateCmd(cl, &oldcmd); } SV_RotateCmd(cl, &newcmd); } else { cl->lastteleport_outgoingseq = 0; } } #endif SV_ExecuteClientMove(cl, oldest, oldcmd, newcmd); cl->lastcmd = newcmd; cl->lastcmd.buttons = 0; // avoid multiple fires on lag if (sv_antilag.value) { if (cl->antilag_position_next == 0 || cl->antilag_positions[(cl->antilag_position_next - 1) % MAX_ANTILAG_POSITIONS].localtime < cl->localtime) { cl->antilag_positions[cl->antilag_position_next % MAX_ANTILAG_POSITIONS].localtime = cl->localtime; VectorCopy(cl->edict->v->origin, cl->antilag_positions[cl->antilag_position_next % MAX_ANTILAG_POSITIONS].origin); cl->antilag_position_next++; } } else { cl->antilag_position_next = 0; } break; case clc_stringcmd: s = MSG_ReadString (); s[1023] = 0; SV_ExecuteUserCommand (s); break; case clc_tmove: o[0] = MSG_ReadCoord(); o[1] = MSG_ReadCoord(); o[2] = MSG_ReadCoord(); // only allowed by spectators if (sv_client->spectator) { VectorCopy(o, sv_player->v->origin); SV_LinkEdict(sv_player, false); } break; case clc_upload: SV_NextUpload(); break; #ifdef FTE_PEXT2_VOICECHAT case clc_voicechat: SV_VoiceReadPacket(); break; #endif } } #ifdef MVD_PEXT1_DEBUG_ANTILAG if (antilag_players_present && sv_debug_antilag.value) { SV_DebugWriteServerAntilagPositions(cl, antilag_players_present); } #endif } #ifdef MVD_PEXT1_SERVERSIDEWEAPON static qbool SV_ClientExtensionWeaponSwitch(client_t* cl) { int flags = MSG_ReadByte(); int weap = 0, w = 0; qbool write = true; int sequence_set = cl->netchan.incoming_sequence; int weapon_hide_selection = 0; byte new_selections[MAX_WEAPONSWITCH_OPTIONS] = { 0 }; char new_wrank[MAX_WEAPONSWITCH_OPTIONS + 1] = { 0 }; int wrank_index = 0; if (flags == -1) { return false; } // This might be a duplicate that should be ignored if (flags & clc_mvd_weapon_forget_ranking) { int age = MSG_ReadByte(); if (age < 0) { return false; } sequence_set = cl->netchan.incoming_sequence - age; write = (sequence_set > cl->weaponswitch_sequence_set); } while ((weap = MSG_ReadByte()) > 0) { if (flags & clc_mvd_weapon_full_impulse) { if (weap && write && w < MAX_WEAPONSWITCH_OPTIONS) { // only add 1-8 to the wrank weapon preference string... if (weap >= 1 && weap <= 8 && wrank_index < MAX_WEAPONSWITCH_OPTIONS) { new_wrank[wrank_index++] = '0' + weap; } new_selections[w++] = weap; } } else { int weap1 = (weap >> 4) & 15; int weap2 = (weap & 15); weap1 = bound(0, weap1, 9); weap2 = bound(0, weap2, 9); if (weap1 && write && w < MAX_WEAPONSWITCH_OPTIONS) { new_wrank[w] = '0' + weap1; new_selections[w++] = weap1; } if (weap1 && weap2 && write && w < MAX_WEAPONSWITCH_OPTIONS) { new_wrank[w] = '0' + weap2; new_selections[w++] = weap2; } if (!weap1 || !weap2) { break; } } } if (flags & clc_mvd_weapon_hide_axe) { weapon_hide_selection = 1; } else if (flags & clc_mvd_weapon_hide_sg) { weapon_hide_selection = 2; } if (weap < 0) { return false; } cl->weaponswitch_enabled = (flags & clc_mvd_weapon_switching); if (write) { cl->weaponswitch_sequence_set = sequence_set; cl->weaponswitch_forgetorder = (flags & clc_mvd_weapon_forget_ranking); cl->weaponswitch_mode = (flags & (clc_mvd_weapon_mode_presel | clc_mvd_weapon_mode_iffiring)); cl->weaponswitch_hide = weapon_hide_selection; cl->weaponswitch_hide_on_death = (flags & clc_mvd_weapon_reset_on_death); memcpy(cl->weaponswitch_priority, new_selections, sizeof(new_selections)); SV_DebugServerSideWeaponInstruction(cl); if (!cl->weaponswitch_forgetorder) { SV_UserSetWeaponRank(cl, new_wrank); } } cl->weaponswitch_pending |= write; cl->weaponswitch_sequence_set = sequence_set; return true; } #endif // MVD_PEXT1_SERVERSIDEWEAPON /* ============== SV_UserInit ============== */ void SV_UserInit (void) { Cvar_Register (&sv_spectalk); Cvar_Register (&sv_sayteam_to_spec); Cvar_Register (&sv_mapcheck); Cvar_Register (&sv_minping); Cvar_Register (&sv_maxping); Cvar_Register (&sv_enable_cmd_minping); Cvar_Register (&sv_kickuserinfospamtime); Cvar_Register (&sv_kickuserinfospamcount); Cvar_Register (&sv_maxuploadsize); #ifdef FTE_PEXT_CHUNKEDDOWNLOADS Cvar_Register (&sv_downloadchunksperframe); #endif #ifdef FTE_PEXT2_VOICECHAT Cvar_Register (&sv_voip); Cvar_Register (&sv_voip_echo); Cvar_Register (&sv_voip_record); #endif #ifdef MVD_PEXT1_DEBUG Cvar_Register(&sv_debug_weapons); #endif Cvar_Register(&sv_debug_antilag); Cvar_Register(&sv_debug_usercmd); } static void SV_DebugClientCommand(byte playernum, const usercmd_t* usercmd, int dropnum_) { if (playernum >= MAX_CLIENTS) { return; } if (sv_debug_usercmd.value >= 1 || svs.clients[playernum].mvd_write_usercmds) { mvdhidden_block_header_t header; byte dropnum = min(dropnum_, 255); header.type_id = mvdhidden_usercmd; header.length = sizeof_mvdhidden_block_header_t_usercmd; if (MVDWrite_HiddenBlockBegin(sizeof_mvdhidden_block_header_t_range0 + header.length)) { MVD_SZ_Write(&header.length, sizeof(header.length)); MVD_SZ_Write(&header.type_id, sizeof(header.type_id)); MVD_SZ_Write(&playernum, sizeof(playernum)); MVD_SZ_Write(&dropnum, sizeof(dropnum)); MVD_SZ_Write(&usercmd->msec, sizeof(usercmd->msec)); MVD_SZ_Write(&usercmd->angles[0], sizeof(usercmd->angles[0])); MVD_SZ_Write(&usercmd->angles[1], sizeof(usercmd->angles[1])); MVD_SZ_Write(&usercmd->angles[2], sizeof(usercmd->angles[2])); MVD_SZ_Write(&usercmd->forwardmove, sizeof(usercmd->forwardmove)); MVD_SZ_Write(&usercmd->sidemove, sizeof(usercmd->sidemove)); MVD_SZ_Write(&usercmd->upmove, sizeof(usercmd->upmove)); MVD_SZ_Write(&usercmd->buttons, sizeof(usercmd->buttons)); MVD_SZ_Write(&usercmd->impulse, sizeof(usercmd->impulse)); } } } static void SV_ClientDownloadComplete(client_t* cl) { if (cl->download) { const char* val; VFS_CLOSE(cl->download); cl->download = NULL; cl->file_percent = 0; //bliP: file percent // set normal rate val = Info_Get(&cl->_userinfo_ctx_, "rate"); cl->netchan.rate = 1.0 / SV_BoundRate(false, Q_atoi(*val ? val : "99999")); // set normal duplicate packets cl->netchan.dupe = cl->dupe; } } #endif // !CLIENTONLY mvdsv-0.35/src/sv_windows.c000066400000000000000000000173241427146041000157430ustar00rootroot00000000000000/* 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. */ #ifndef _CONSOLE //bliP: console compile #include "qwsvdef.h" #include COLORREF EditBoxBgColor, EditBoxColor; HBRUSH g_hbrBackground; HINSTANCE global_hInstance; HWND DlgHwnd; HWND HEdit1 = NULL, HEdit2 = NULL; HMENU Menu; unsigned int HEdit1_size = 0; char *HEdit1_buf = NULL; qbool minimized = false; qbool DrawConsole = false; /* ================= ConsoleAddText Appends text to the main console Changes will be redrawn in CheckIdle function So that screen doesn't blink when there are more updates in one frame also works much faster ================= */ void ConsoleAddText(char *text) { int l, size; DrawConsole = true; SendMessage(HEdit1, WM_SETREDRAW, 0, 0); SendMessage(HEdit1, EM_SETREADONLY, 0, 0); // set the carriage in the end of the text l = SendMessage(HEdit1, WM_GETTEXTLENGTH, 0, 0); size = l + strlen(text) + strchrn(text, '\n'); if (HEdit1_size <= size) { SendMessage(HEdit1, WM_GETTEXT, HEdit1_size, (LPARAM)HEdit1_buf); SendMessage(HEdit1, WM_SETTEXT, 0, (LPARAM)(HEdit1_buf + size + 1 - HEdit1_size)); l = SendMessage(HEdit1, WM_GETTEXTLENGTH, 0, 0); } SendMessage(HEdit1, EM_SETSEL, l, l); while (*text) SendMessage(HEdit1, WM_CHAR, *text++, 1); SendMessage(HEdit1, EM_SETREADONLY, 1, 0); SendMessage(HEdit1, WM_SETREDRAW, 1, 0); } #define WM_TRAY WM_USER + 19 static HICON icon; /* ================= CreateMainWindow ================= */ BOOL CreateMainWindow(HINSTANCE hInstance, int nCmdShow) { WNDCLASS wc; icon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON2)); /* Register the frame class */ wc.style = 0; wc.lpfnWndProc = DefDlgProc; wc.cbClsExtra = 0; wc.cbWndExtra = DLGWINDOWEXTRA; wc.hInstance = hInstance; wc.hIcon = icon; wc.hCursor = LoadCursor (NULL,IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wc.lpszMenuName = 0; wc.lpszClassName = SERVER_NAME; if (!RegisterClass (&wc) ) Sys_Error ("Couldn't register window class"); global_hInstance = hInstance; DlgHwnd = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG2), NULL, (DLGPROC)DialogFunc); if (!DlgHwnd) { MessageBox(NULL, TEXT("Could not create dialog window"), TEXT("Error"), MB_ICONEXCLAMATION|MB_OK); return 0; } EditBoxBgColor = RGB(0, 64, 64); EditBoxColor = RGB(255, 255, 255); g_hbrBackground = CreateSolidBrush(EditBoxBgColor); // is started with -minimize option, start in tray if (COM_CheckParm("-minimize") || (nCmdShow == SW_SHOWMINNOACTIVE)) { ShowNotifyIcon(); ShowWindow(DlgHwnd,SW_HIDE); UpdateWindow(DlgHwnd); } else { ShowWindow(DlgHwnd,SW_SHOWNORMAL); UpdateWindow(DlgHwnd); } // popup menu of tray icon Menu = CreatePopupMenu(); AppendMenu(Menu, MF_STRING, IDC_RESTORE, "Restore"); //AppendMenu(Menu, MF_STRING, 0, "about"); AppendMenu(Menu, MF_SEPARATOR, 0, NULL); AppendMenu(Menu, MF_STRING, IDC_QUIT, "Quit"); return 1; } /* ================= RemoveNotifyIcon ================= */ void RemoveNotifyIcon(void) { NOTIFYICONDATA tnid; tnid.cbSize = sizeof(NOTIFYICONDATA); tnid.hWnd = DlgHwnd; tnid.uID = IDI_ICON2; Shell_NotifyIcon(NIM_DELETE, &tnid); minimized = false; } /* ================= ShowNotifyIcon ================= */ void ShowNotifyIcon(void) { NOTIFYICONDATA tnid; extern int sv_port; tnid.cbSize = sizeof(NOTIFYICONDATA); tnid.hWnd = DlgHwnd; tnid.uID = IDI_ICON2; tnid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE; tnid.uCallbackMessage = WM_TRAY; tnid.hIcon = icon; strlcpy(tnid.szTip, va(SERVER_NAME ":%d", NET_UDPSVPort()), sizeof(tnid.szTip)); Shell_NotifyIcon(NIM_ADD, &tnid); minimized = true; } void UpdateNotifyIconMessage(char *msg) { NOTIFYICONDATA tnid; extern int sv_port; tnid.cbSize = sizeof(NOTIFYICONDATA); tnid.hWnd = DlgHwnd; tnid.uID = IDI_ICON2; tnid.uFlags = NIF_TIP; strlcpy(tnid.szTip, msg, sizeof(tnid.szTip)); Shell_NotifyIcon(NIM_MODIFY, &tnid); } /* ================= TrackPopup Tracks popup menu, this is called as thread, so it doesn't lag server ================= */ DWORD WINAPI TrackPopup(LPVOID param) { static qbool running = false; POINT point; HWND win; int result; // one pup at one time is much enough if (running) return 0; running = true; // we can't create menu of a window from another thread, // so we need to make a temporary window here win = CreateWindow(SERVER_NAME, "", 0,0,0,0,0,NULL, NULL, global_hInstance, NULL); GetCursorPos(&point); result = TrackPopupMenu(Menu, TPM_RETURNCMD|TPM_LEFTALIGN|TPM_LEFTBUTTON, point.x, point.y, 0, win, NULL); DestroyWindow( win ); if (result) PostMessage(DlgHwnd, WM_COMMAND, result, 0); running = false; return 0; } /* ================= SetWindowText_ ================= */ void SetWindowText_(char *text) { SetWindowText(DlgHwnd, text); } /* ================= CheckIdle Called every frame ================= */ void CheckIdle(void) { // we update scroll bar position here, and draw console edit box if (DrawConsole) { int i; i = SendMessage(HEdit1, EM_GETLINECOUNT , 0,0) - 22; if (i > 0) SendMessage(HEdit1, EM_LINESCROLL, 0, (LPARAM)i); //SendMessage(HEdit1, WM_SETREDRAW, 1, 0); DrawConsole = false; } } /* ================= DialogFunc Main window procedure ================= */ void SV_Quit_f(void); BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: HEdit1 = GetDlgItem(hwndDlg, IDC_EDIT1); HEdit2 = GetDlgItem(hwndDlg, IDC_EDIT2); SetFocus(HEdit2); // SendMessage(HEdit1, EM_LIMITTEXT, 1000, 0); HEdit1_size = SendMessage(HEdit1, EM_GETLIMITTEXT, 0, 0) + 1; HEdit1_buf = (char *) Q_malloc (HEdit1_size); //Sys_Printf("%d\n", HEdit1_size); break; case WM_CTLCOLORSTATIC: if ((HWND)lParam != HEdit1) break; SetTextColor((HDC)wParam, EditBoxColor); SetBkColor((HDC)wParam, EditBoxBgColor); return (LONG)g_hbrBackground; case WM_TRAY: switch (lParam) { case 515: ShowWindow(hwndDlg,SW_RESTORE); SetForegroundWindow(hwndDlg); RemoveNotifyIcon(); break; case 516: { static DWORD id; CreateThread(NULL, 0, TrackPopup, NULL, 0, &id); break; } } break; case WM_SIZE: // we don't care until window is fully created if (DlgHwnd == NULL) break; if ((int)wParam == SIZE_MINIMIZED) { ShowWindow(hwndDlg,SW_HIDE); ShowNotifyIcon(); } break; case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_OK: { char str[1024]; SendMessage(HEdit2, WM_GETTEXT, (WPARAM)sizeof(str),(LPARAM)str); if (!str[0]) break; SendMessage(HEdit2, WM_SETTEXT, 0, (LPARAM)0); // normalize text before add to console. ConsoleAddText(Q_normalizetext(va("] %s\n", str))); Cbuf_AddText (str); Cbuf_AddText ("\n"); return TRUE; } case IDC_QUIT: Cbuf_AddText("quit\n"); return TRUE; case IDC_RESTORE: ShowWindow(hwndDlg,SW_RESTORE); RemoveNotifyIcon(); return TRUE; case IDC_CLEAR: SendMessage(HEdit1, WM_SETTEXT, 0, (LPARAM)0); SetFocus(HEdit2); break; } break; case WM_ACTIVATE: break; case WM_CLOSE: SV_Quit_f(); break; } return FALSE; } #endif /* !_CONSOLE */ //bliP: console compile mvdsv-0.35/src/sv_windows.h000066400000000000000000000025371427146041000157500ustar00rootroot00000000000000/* 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. */ #ifndef __SV_WINDOWS_H__ #define __SV_WINDOWS_H__ #ifndef _CONSOLE #define MAX_NUM_ARGVS 50 extern HWND HEdit1; extern COLORREF EditBoxBgColor, EditBoxColor; extern HBRUSH g_hbrBackground; extern HINSTANCE global_hInstance; extern HWND DlgHwnd, mainWindow; extern HWND HEdit1, HEdit2; extern HMENU Menu; extern qbool minimized; BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL CreateMainWindow(HINSTANCE hInstance, int nCmdShow); void ConsoleAddText(char *text); void ShowNotifyIcon(void); void RemoveNotifyIcon(void); void UpdateNotifyIconMessage(char *msg); void CheckIdle(void); #endif /* !_CONSOLE */ #endif /* !__SV_WINDOWS_H__ */ mvdsv-0.35/src/sys.h000066400000000000000000000072401427146041000143600ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // sys.h -- non-portable functions // // file IO // #ifndef __SYS_H__ #define __SYS_H__ #define DEFAULT_MEM_SIZE (32 * 1024 * 1024) // 32 Mb // returns the file size // return -1 if file is not present // the file should be in BINARY mode for stupid OSs that care #define MAX_DIRFILES 4096 #define MAX_DEMO_NAME 196 #include "q_platform.h" typedef struct { char name[MAX_DEMO_NAME]; int size; int time; qbool isdir; //bliP: list dir } file_t; typedef struct { file_t *files; int size; int numfiles; int numdirs; } dir_t; int Sys_FileTime (const char *path); void Sys_mkdir (const char *path); int Sys_rmdir (const char *path); int Sys_remove (const char *path); dir_t Sys_listdir (const char *path, const char *ext, int sort_type); int Sys_EnumerateFiles (char *gpath, char *match, int (*func)(char *, int, void *), void *parm); int Sys_compare_by_date (const void *a, const void *b); int Sys_compare_by_name (const void *a, const void *b); #define SORT_NO 0 #define SORT_BY_DATE 1 #define SORT_BY_NAME 2 // // system IO // void Sys_Error (const char *error, ...); // an error will cause the entire program to exit void Sys_Printf (char *fmt, ...); // send text to the console void Sys_Quit (qbool restart); double Sys_DoubleTime (void); char *Sys_ConsoleInput (void); //void Sys_Sleep (void); // called to yield for a little bit so as // not to hog cpu when paused or debugging void Sys_Init (void); void Sys_Sleep (unsigned long ms); int Sys_Script (const char *path, const char *args); typedef union floatint_u { int i; unsigned int u; float f; byte b[4]; } floatint_t; #define ARRAY_LEN(x) (sizeof(x) / sizeof(*(x))) #ifdef _WIN32 #include #include // _mkdir #include #include #include #include "resource.h" #include "sv_windows.h" typedef HMODULE DL_t; #else #include #include #include #include #include #include #include /* for intptr_t */ #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__sun__) || defined(__GNUC__) || defined(__APPLE__) #include #include #if (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)) && defined(KQUEUE) #include #include #ifdef __OpenBSD__ // OpenBSD tell me it want it for kqueue #include #endif /* !__OpenBSD__ */ #endif #else #include #endif #ifndef _PATH_DEVNULL #define _PATH_DEVNULL "/dev/null" #else #include #endif // __sun__ have no _PATH_DEVNULL typedef void *DL_t; #endif /* _WIN32 */ DL_t Sys_DLOpen (const char *path); qbool Sys_DLClose( DL_t dl); void *Sys_DLProc (DL_t dl, const char *name); #ifndef _WIN32 #define DWORD unsigned int #define WINAPI #include #endif int Sys_CreateThread(DWORD (WINAPI *func)(void *), void *param); #endif /* !__SYS_H__ */ mvdsv-0.35/src/version.h000066400000000000000000000036631427146041000152340ustar00rootroot00000000000000/* 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. */ // version.h #ifndef __VERSION_H__ #define __VERSION_H__ #if defined(_WIN32) #define QW_PLATFORM "Windows" #define QW_PLATFORM_SHORT "w" #elif defined(__FreeBSD__) #define QW_PLATFORM "FreeBSD" #define QW_PLATFORM_SHORT "f" #elif defined(__OpenBSD__) #define QW_PLATFORM "OpenBSD" #define QW_PLATFORM_SHORT "o" #elif defined(__NetBSD__) #define QW_PLATFORM "NetBSD" #define QW_PLATFORM_SHORT "n" #elif defined(__DragonFly__) #define QW_PLATFORM "DragonFly" #define QW_PLATFORM_SHORT "d" #elif defined(__linux__) #define QW_PLATFORM "Linux" #define QW_PLATFORM_SHORT "l" #elif defined(__sun__) #define QW_PLATFORM "SunOS" #define QW_PLATFORM_SHORT "s" #elif defined(__APPLE__) #define QW_PLATFORM "MacOS" #define QW_PLATFORM_SHORT "m" #else #define QW_PLATFORM "Unknown" #define QW_PLATFORM_SHORT "u" #endif #define QW_VERSION "2.40" #define SERVER_VERSION "0.35" #define VERSION_NUM 0.35 #define VERSION_NUM_STR "0.35" #define SERVER_NAME "MVDSV" #define SERVER_FULLNAME "MVDSV: MultiView Demo SerVer" #define SERVER_HOME_URL "https://github.com/QW-Group/mvdsv" #define BUILD_DATE __DATE__ ", " __TIME__ #define GIT_COMMIT "" char *VersionStringFull (void); #endif /* !__VERSION_H__ */ mvdsv-0.35/src/vfs.h000066400000000000000000000114161427146041000143400ustar00rootroot00000000000000/* * 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. * * */ #ifndef __VFS_H__ #define __VFS_H__ // FIXME: probably we can move some of that to .c files. //================================= // Quake filesystem //================================= extern hashtable_t *filesystemhash; extern int fs_hash_dups; extern int fs_hash_files; typedef struct { struct searchpath_s * search; int index; char rawname[MAX_OSPATH]; int offset; int len; } flocation_t; // FS_FLocateFile return type. typedef enum { FSLFRT_IFFOUND, // return true if file found, false if not found. FSLFRT_LENGTH, // return file length if found, -1 if not found. FSLFRT_DEPTH_OSONLY, // return depth (no paks), 0x7fffffff if not found. FSLFRT_DEPTH_ANYPATH // return depth, 0x7fffffff if not found. } FSLF_ReturnType_e; typedef enum { VFSERR_NONE, VFSERR_EOF } vfserrno_t; typedef struct vfsfile_s { int (*ReadBytes) (struct vfsfile_s *file, void *buffer, int bytestoread, vfserrno_t *err); int (*WriteBytes) (struct vfsfile_s *file, const void *buffer, int bytestowrite); int (*Seek) (struct vfsfile_s *file, unsigned long pos, int whence); // Returns 0 on sucess, -1 otherwise unsigned long (*Tell) (struct vfsfile_s *file); unsigned long (*GetLen) (struct vfsfile_s *file); // Could give some lag void (*Close) (struct vfsfile_s *file); void (*Flush) (struct vfsfile_s *file); qbool seekingisabadplan; qbool copyprotected; // File found was in a pak } vfsfile_t; typedef struct { void (*PrintPath)(void *handle); void (*ClosePath)(void *handle); void (*BuildHash)(void *handle); // true if found (hashedresult can be NULL) // note that if rawfile and offset are set, many Com_FileOpens will // read the raw file otherwise ReadFile will be called instead. qbool (*FindFile)(void *handle, flocation_t *loc, const char *name, void *hashedresult); // reads the entire file void (*ReadFile)(void *handle, flocation_t *loc, char *buffer); int (*EnumerateFiles)(void *handle, char *match, int (*func)(char *, int, void *), void *parm); // returns a handle to a new pak/path void *(*OpenNew)(vfsfile_t *file, const char *desc); int (*GeneratePureCRC) (void *handle, int seed, int usepure); vfsfile_t *(*OpenVFS)(void *handle, flocation_t *loc, char *mode); } searchpathfuncs_t; typedef struct searchpath_s { searchpathfuncs_t *funcs; qbool copyprotected; // don't allow downloads from here. qbool istemporary; void *handle; struct searchpath_s *next; } searchpath_t; // mostly analogs for stdio functions void VFS_CLOSE (struct vfsfile_s *vf); unsigned long VFS_TELL (struct vfsfile_s *vf); unsigned long VFS_GETLEN (struct vfsfile_s *vf); int VFS_SEEK (struct vfsfile_s *vf, unsigned long pos, int whence); int VFS_READ (struct vfsfile_s *vf, void *buffer, int bytestoread, vfserrno_t *err); int VFS_WRITE (struct vfsfile_s *vf, const void *buffer, int bytestowrite); void VFS_FLUSH (struct vfsfile_s *vf); // return null terminated string char *VFS_GETS (struct vfsfile_s *vf, char *buffer, int buflen); qbool VFS_COPYPROTECTED(struct vfsfile_s *vf); typedef enum { FS_NONE_OS, // FIXME: probably must be removed, as not so secure... // Opened with OS functions (no paks). // filename. FS_GAME_OS, // Opened with OS functions (no paks). // fs_basedir/fs_gamedirfile/filename. FS_GAME, // Searched on path as filename, including packs. FS_BASE_OS, // Opened with OS functions (no paks). // fs_basedir/filename. FS_ANY // That slightly evil, derived from ezquake. // 1) FS_GAME. // 2) FS_NONE_OS. } relativeto_t; void FS_FlushFSHash(void); vfsfile_t *FS_OpenVFS(const char *filename, char *mode, relativeto_t relativeto); int FS_FLocateFile(const char *filename, FSLF_ReturnType_e returntype, flocation_t *loc); //================================= // STDIO Files (OS) //================================= vfsfile_t *FS_OpenTemp(void); vfsfile_t *VFSOS_Open(char *osname, char *mode); extern searchpathfuncs_t osfilefuncs; //==================== // PACK (*pak) Support //==================== extern searchpathfuncs_t packfilefuncs; #endif /* __VFS_H__ */ mvdsv-0.35/src/vfs_os.c000066400000000000000000000143331427146041000150350ustar00rootroot00000000000000/* * 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 "qwsvdef.h" typedef struct { vfsfile_t funcs; // <= must be at top/begining of struct FILE *handle; } vfsosfile_t; //================================== // STDIO files (OS) - VFS Functions //================================== static int VFSOS_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread, vfserrno_t *err) { int r; vfsosfile_t *intfile = (vfsosfile_t*)file; if (bytestoread < 0) Sys_Error("VFSOS_ReadBytes: bytestoread < 0"); // ffs r = fread(buffer, 1, bytestoread, intfile->handle); if (err) // if bytestoread <= 0 it will be treated as non error even we read zero bytes *err = ((r || bytestoread <= 0) ? VFSERR_NONE : VFSERR_EOF); return r; } static int VFSOS_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestowrite) { vfsosfile_t *intfile = (vfsosfile_t*)file; return fwrite(buffer, 1, bytestowrite, intfile->handle); } static int VFSOS_Seek (struct vfsfile_s *file, unsigned long pos, int whence) { vfsosfile_t *intfile = (vfsosfile_t*)file; return fseek(intfile->handle, pos, whence); } static unsigned long VFSOS_Tell (struct vfsfile_s *file) { vfsosfile_t *intfile = (vfsosfile_t*)file; return ftell(intfile->handle); } static unsigned long VFSOS_GetSize (struct vfsfile_s *file) { vfsosfile_t *intfile = (vfsosfile_t*)file; unsigned long curpos, maxlen; // FIXME: add error checks here? curpos = ftell(intfile->handle); fseek(intfile->handle, 0, SEEK_END); maxlen = ftell(intfile->handle); fseek(intfile->handle, curpos, SEEK_SET); return maxlen; } static void VFSOS_Close(vfsfile_t *file) { vfsosfile_t *intfile = (vfsosfile_t*)file; fclose(intfile->handle); Q_free(file); } vfsfile_t *FS_OpenTemp(void) { FILE *f; vfsosfile_t *file; f = tmpfile(); if (!f) return NULL; file = Q_calloc(1, sizeof(vfsosfile_t)); file->funcs.ReadBytes = VFSOS_ReadBytes; file->funcs.WriteBytes = VFSOS_WriteBytes; file->funcs.Seek = VFSOS_Seek; file->funcs.Tell = VFSOS_Tell; file->funcs.GetLen = VFSOS_GetSize; file->funcs.Close = VFSOS_Close; file->handle = f; return (vfsfile_t*)file; } // VFS-XXX: This is slightly different to fs.c version in that we don't take in a FILE *f vfsfile_t *VFSOS_Open(char *osname, char *mode) { FILE *f; vfsosfile_t *file; qbool read = !!strchr(mode, 'r'); qbool write = !!strchr(mode, 'w'); qbool append = !!strchr(mode, 'a'); qbool text = !!strchr(mode, 't'); char newmode[10]; int modec = 0; if (read) newmode[modec++] = 'r'; if (write) newmode[modec++] = 'w'; if (append) newmode[modec++] = 'a'; if (text) newmode[modec++] = 't'; else newmode[modec++] = 'b'; newmode[modec++] = '\0'; f = fopen(osname, newmode); if (!f) return NULL; file = Q_calloc(1, sizeof(vfsosfile_t)); file->funcs.ReadBytes = ( strchr(mode, 'r') ? VFSOS_ReadBytes : NULL); file->funcs.WriteBytes = ((strchr(mode, 'w') || strchr(mode, 'a'))? VFSOS_WriteBytes : NULL); file->funcs.Seek = VFSOS_Seek; file->funcs.Tell = VFSOS_Tell; file->funcs.GetLen = VFSOS_GetSize; file->funcs.Close = VFSOS_Close; file->handle = f; return (vfsfile_t*)file; } //================================== // STDIO files (OS) - Search functions //================================== vfsfile_t *FSOS_OpenVFS(void *handle, flocation_t *loc, char *mode) { char diskname[MAX_OSPATH]; snprintf(diskname, sizeof(diskname), "%s/%s", (char*)handle, loc->rawname); return VFSOS_Open(diskname, mode); } static void FSOS_PrintPath(void *handle) { Con_Printf("%s\n", handle); } static void FSOS_ClosePath(void *handle) { Q_free(handle); } static int FSOS_RebuildFSHash(char *filename, int filesize, void *data) { if (filename[strlen(filename)-1] == '/') { //this is actually a directory char childpath[256]; snprintf(childpath, sizeof (childpath), "%s*", filename); Sys_EnumerateFiles((char*)data, childpath, FSOS_RebuildFSHash, data); return true; } if (!Hash_GetInsensitive(filesystemhash, filename)) { Hash_AddInsensitive(filesystemhash, filename, data); fs_hash_files++; } else fs_hash_dups++; return true; } static void FSOS_BuildHash(void *handle) { Sys_EnumerateFiles(handle, "*", FSOS_RebuildFSHash, handle); } static qbool FSOS_FLocate(void *handle, flocation_t *loc, const char *filename, void *hashedresult) { FILE *f; int len; char netpath[MAX_OSPATH]; if (hashedresult && (void *)hashedresult != handle) return false; /* if (!static_registered) { // if not a registered version, don't ever go beyond base if ( strchr (filename, '/') || strchr (filename,'\\')) continue; } */ // check a file in the directory tree snprintf (netpath, sizeof(netpath)-1, "%s/%s",(char*)handle, filename); // VFS-FIXME: This could be optimised to only do this once and save the result! f = fopen(netpath, "rb"); if (!f) return false; fseek(f, 0, SEEK_END); len = ftell(f); fclose(f); if (loc) { loc->len = len; loc->offset = 0; loc->index = 0; strlcpy (loc->rawname, filename, sizeof (loc->rawname)); } return true; } void FSOS_ReadFile(void *handle, flocation_t *loc, char *buffer) { FILE *f; f = fopen(loc->rawname, "rb"); if (!f) //err... return; fseek(f, loc->offset, SEEK_SET); fread(buffer, 1, loc->len, f); fclose(f); } static int FSOS_EnumerateFiles (void *handle, char *match, int (*func)(char *, int, void *), void *parm) { return Sys_EnumerateFiles(handle, match, func, parm); } searchpathfuncs_t osfilefuncs = { FSOS_PrintPath, FSOS_ClosePath, FSOS_BuildHash, FSOS_FLocate, FSOS_ReadFile, FSOS_EnumerateFiles, NULL, NULL, FSOS_OpenVFS }; mvdsv-0.35/src/vfs_pak.c000066400000000000000000000212461427146041000151700ustar00rootroot00000000000000/* * 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 "qwsvdef.h" //#define MAX_FILES_IN_PACK 2048 // on disk typedef struct { char name[56]; int filepos, filelen; } dpackfile_t; // on disk typedef struct { char id[4]; int dirofs; int dirlen; } dpackheader_t; // Packages in memory typedef struct packfile_s { char name[MAX_QPATH]; int filepos, filelen; } packfile_t; typedef struct pack_s { char filename[MAX_OSPATH]; vfsfile_t *handle; unsigned int filepos; // the pos the subfiles left it at // (to optimize calls to vfs_seek) int references; // seeing as all vfiles from a pak file use the // parent's vfsfile, we need to keep the parent // open until all subfiles are closed. int numfiles; packfile_t *files; } pack_t; typedef struct { vfsfile_t funcs; // <= must be at top/begining of struct pack_t *parentpak; unsigned long startpos; unsigned long length; unsigned long currentpos; } vfspack_t; //===================================== //PACK files (*.pak) - VFS functions //===================================== static int VFSPAK_ReadBytes (struct vfsfile_s *vfs, void *buffer, int bytestoread, vfserrno_t *err) { vfspack_t *vfsp = (vfspack_t*)vfs; int read; if (vfsp->currentpos - vfsp->startpos + bytestoread > vfsp->length) bytestoread = vfsp->length - (vfsp->currentpos - vfsp->startpos); if (bytestoread <= 0) return -1; if (vfsp->parentpak->filepos != vfsp->currentpos) { VFS_SEEK(vfsp->parentpak->handle, vfsp->currentpos, SEEK_SET); } read = VFS_READ(vfsp->parentpak->handle, buffer, bytestoread, err); vfsp->currentpos += read; vfsp->parentpak->filepos = vfsp->currentpos; // VFS-FIXME: Need to handle errors return read; } static int VFSPAK_WriteBytes (struct vfsfile_s *vfs, const void *buffer, int bytestoread) { //not supported. Sys_Error("VFSPAK_WriteBytes: Cannot write to pak files"); return 0; } static int VFSPAK_Seek (struct vfsfile_s *vfs, unsigned long offset, int whence) { vfspack_t *vfsp = (vfspack_t*)vfs; // VFS-FIXME Support other whence types switch(whence) { case SEEK_SET: vfsp->currentpos = vfsp->startpos + offset; break; case SEEK_CUR: vfsp->currentpos += offset; break; case SEEK_END: vfsp->currentpos = vfsp->startpos + vfsp->length + offset; break; default: Sys_Error("VFSTAR_Seek: Unknown whence value(%d)\n", whence); return -1; } if (vfsp->currentpos > vfsp->length) { Con_Printf("VFSPAK_Seek: Warning seeking past the file's size\n"); } return 0; } static unsigned long VFSPAK_Tell (struct vfsfile_s *vfs) { vfspack_t *vfsp = (vfspack_t*)vfs; return vfsp->currentpos - vfsp->startpos; } static unsigned long VFSPAK_GetLen (struct vfsfile_s *vfs) { vfspack_t *vfsp = (vfspack_t*)vfs; return vfsp->length; } static void FSPAK_ClosePath(void *handle); static void VFSPAK_Close(vfsfile_t *vfs) { vfspack_t *vfsp = (vfspack_t*)vfs; FSPAK_ClosePath(vfsp->parentpak); // tell the parent that we don't need it open any // more (reference counts) Q_free(vfsp); } static vfsfile_t *FSPAK_OpenVFS(void *handle, flocation_t *loc, char *mode) { pack_t *pack = (pack_t*)handle; vfspack_t *vfsp; if (strcmp(mode, "rb")) return NULL; //urm, unable to write/append vfsp = Q_calloc(1, sizeof(*vfsp)); vfsp->parentpak = pack; vfsp->parentpak->references++; vfsp->startpos = loc->offset; vfsp->length = loc->len; vfsp->currentpos = vfsp->startpos; vfsp->funcs.ReadBytes = strcmp(mode, "rb") ? NULL : VFSPAK_ReadBytes; vfsp->funcs.WriteBytes = strcmp(mode, "wb") ? NULL : VFSPAK_WriteBytes; vfsp->funcs.Seek = VFSPAK_Seek; vfsp->funcs.Tell = VFSPAK_Tell; vfsp->funcs.GetLen = VFSPAK_GetLen; vfsp->funcs.Close = VFSPAK_Close; vfsp->funcs.Flush = NULL; if (loc->search) vfsp->funcs.copyprotected = loc->search->copyprotected; return (vfsfile_t *)vfsp; } //====================================== // PACK files (*.pak) - Search functions //====================================== static void FSPAK_PrintPath(void *handle) { pack_t *pak = handle; if (pak->references != 1) Con_Printf("%s (%i)\n", pak->filename, pak->references-1); else Con_Printf("%s\n", pak->filename); } static void FSPAK_ClosePath(void *handle) { pack_t *pak = handle; pak->references--; if (pak->references > 0) return; //not free yet VFS_CLOSE (pak->handle); if (pak->files) Q_free(pak->files); Q_free(pak); } static void FSPAK_BuildHash(void *handle) { pack_t *pak = handle; int i; for (i = 0; i < pak->numfiles; i++) { if (!Hash_GetInsensitive(filesystemhash, pak->files[i].name)) { Hash_AddInsensitive(filesystemhash, pak->files[i].name, &pak->files[i]); fs_hash_files++; } else fs_hash_dups++; } } static qbool FSPAK_FLocate(void *handle, flocation_t *loc, const char *filename, void *hashedresult) { packfile_t *pf = hashedresult; int i; pack_t *pak = handle; // look through all the pak file elements if (pf) { //is this a pointer to a file in this pak? if (pf < pak->files || pf > pak->files + pak->numfiles) return false; //was found in a different path } else { for (i=0 ; inumfiles ; i++) //look for the file { if (!strcmp (pak->files[i].name, filename)) { pf = &pak->files[i]; break; } } } if (pf) { if (loc) { loc->index = pf - pak->files; snprintf(loc->rawname, sizeof(loc->rawname), "%s/%s", pak->filename, filename); loc->offset = pf->filepos; loc->len = pf->filelen; } return true; } return false; } static int FSPAK_EnumerateFiles (void *handle, char *match, int (*func)(char *, int, void *), void *parm) { pack_t *pak = handle; int num; for (num = 0; num<(int)pak->numfiles; num++) { if (wildcmp(match, pak->files[num].name)) { if (!func(pak->files[num].name, pak->files[num].filelen, parm)) return false; } } return true; } /* ================= FSPAK_LoadPackFile Takes an explicit (not game tree related) path to a pak file. Loads the header and directory, adding the files at the beginning of the list so they override previous pack files. ================= */ static void *FSPAK_LoadPackFile (vfsfile_t *file, const char *desc) { dpackheader_t header; int i; packfile_t *newfiles; int numpackfiles; pack_t *pack; vfsfile_t *packhandle; dpackfile_t info; int read; vfserrno_t err; packhandle = file; if (packhandle == NULL) return NULL; VFS_READ(packhandle, &header, sizeof(header), &err); if (header.id[0] != 'P' || header.id[1] != 'A' || header.id[2] != 'C' || header.id[3] != 'K') { return NULL; } header.dirofs = LittleLong (header.dirofs); header.dirlen = LittleLong (header.dirlen); numpackfiles = header.dirlen / sizeof(dpackfile_t); // if (numpackfiles != PAK0_COUNT) // com_modified = true; // not the original file newfiles = (packfile_t*)Q_malloc (numpackfiles * sizeof(packfile_t)); VFS_SEEK(packhandle, header.dirofs, SEEK_SET); // fread (&info, 1, header.dirlen, packhandle); // crc the directory to check for modifications // crc = QCRC_Block((qbyte *)info, header.dirlen); // QCRC_Init (&crc); pack = (pack_t *)Q_calloc(1, sizeof (pack_t)); // parse the directory for (i=0 ; ifilename, desc, sizeof (pack->filename)); pack->handle = packhandle; pack->numfiles = numpackfiles; pack->files = newfiles; pack->filepos = 0; VFS_SEEK(packhandle, pack->filepos, SEEK_SET); pack->references++; return pack; } extern void FSOS_ReadFile(void *handle, flocation_t *loc, char *buffer); searchpathfuncs_t packfilefuncs = { FSPAK_PrintPath, FSPAK_ClosePath, FSPAK_BuildHash, FSPAK_FLocate, FSOS_ReadFile, FSPAK_EnumerateFiles, FSPAK_LoadPackFile, NULL, FSPAK_OpenVFS }; mvdsv-0.35/src/vm.c000066400000000000000000001072151427146041000141620ustar00rootroot00000000000000/* * 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 * * */ // vm.c -- virtual machine /* intermix code and data symbol table a dll has one imported function: VM_SystemCall and one exported function: Perform */ #ifdef USE_PR2 #include "qwsvdef.h" #include "vm_local.h" opcode_info_t ops[ OP_MAX ] = { { 0, 0, 0, 0 }, // undef { 0, 0, 0, 0 }, // ignore { 0, 0, 0, 0 }, // break { 4, 0, 0, 0 }, // enter { 4,-4, 0, 0 }, // leave { 0, 0, 1, 0 }, // call { 0, 4, 0, 0 }, // push { 0,-4, 1, 0 }, // pop { 4, 4, 0, 0 }, // const { 4, 4, 0, 0 }, // local { 0,-4, 1, 0 }, // jump { 4,-8, 2, JUMP }, // eq { 4,-8, 2, JUMP }, // ne { 4,-8, 2, JUMP }, // lti { 4,-8, 2, JUMP }, // lei { 4,-8, 2, JUMP }, // gti { 4,-8, 2, JUMP }, // gei { 4,-8, 2, JUMP }, // ltu { 4,-8, 2, JUMP }, // leu { 4,-8, 2, JUMP }, // gtu { 4,-8, 2, JUMP }, // geu { 4,-8, 2, JUMP }, // eqf { 4,-8, 2, JUMP }, // nef { 4,-8, 2, JUMP }, // ltf { 4,-8, 2, JUMP }, // lef { 4,-8, 2, JUMP }, // gtf { 4,-8, 2, JUMP }, // gef { 0, 0, 1, 0 }, // load1 { 0, 0, 1, 0 }, // load2 { 0, 0, 1, 0 }, // load4 { 0,-8, 2, 0 }, // store1 { 0,-8, 2, 0 }, // store2 { 0,-8, 2, 0 }, // store4 { 1,-4, 1, 0 }, // arg { 4,-8, 2, 0 }, // bcopy { 0, 0, 1, 0 }, // sex8 { 0, 0, 1, 0 }, // sex16 { 0, 0, 1, 0 }, // negi { 0,-4, 3, 0 }, // add { 0,-4, 3, 0 }, // sub { 0,-4, 3, 0 }, // divi { 0,-4, 3, 0 }, // divu { 0,-4, 3, 0 }, // modi { 0,-4, 3, 0 }, // modu { 0,-4, 3, 0 }, // muli { 0,-4, 3, 0 }, // mulu { 0,-4, 3, 0 }, // band { 0,-4, 3, 0 }, // bor { 0,-4, 3, 0 }, // bxor { 0, 0, 1, 0 }, // bcom { 0,-4, 3, 0 }, // lsh { 0,-4, 3, 0 }, // rshi { 0,-4, 3, 0 }, // rshu { 0, 0, 1, 0 }, // negf { 0,-4, 3, 0 }, // addf { 0,-4, 3, 0 }, // subf { 0,-4, 3, 0 }, // divf { 0,-4, 3, 0 }, // mulf { 0, 0, 1, 0 }, // cvif { 0, 0, 1, 0 } // cvfi }; const char *opname[ 256 ] = { "OP_UNDEF", "OP_IGNORE", "OP_BREAK", "OP_ENTER", "OP_LEAVE", "OP_CALL", "OP_PUSH", "OP_POP", "OP_CONST", "OP_LOCAL", "OP_JUMP", //------------------- "OP_EQ", "OP_NE", "OP_LTI", "OP_LEI", "OP_GTI", "OP_GEI", "OP_LTU", "OP_LEU", "OP_GTU", "OP_GEU", "OP_EQF", "OP_NEF", "OP_LTF", "OP_LEF", "OP_GTF", "OP_GEF", //------------------- "OP_LOAD1", "OP_LOAD2", "OP_LOAD4", "OP_STORE1", "OP_STORE2", "OP_STORE4", "OP_ARG", "OP_BLOCK_COPY", //------------------- "OP_SEX8", "OP_SEX16", "OP_NEGI", "OP_ADD", "OP_SUB", "OP_DIVI", "OP_DIVU", "OP_MODI", "OP_MODU", "OP_MULI", "OP_MULU", "OP_BAND", "OP_BOR", "OP_BXOR", "OP_BCOM", "OP_LSH", "OP_RSHI", "OP_RSHU", "OP_NEGF", "OP_ADDF", "OP_SUBF", "OP_DIVF", "OP_MULF", "OP_CVIF", "OP_CVFI" }; cvar_t vm_rtChecks = { "vm_rtChecks", "1"}; int vm_debugLevel; // used by SV_Error to get rid of running vm's before longjmp static int forced_unload; struct vm_s vmTable[ VM_COUNT ]; void VM_VmInfo_f( void ); void VM_VmProfile_f( void ); void VM_Debug( int level ) { vm_debugLevel = level; } /* ============== VM_CheckBounds ============== */ void VM_CheckBounds( const vm_t *vm, unsigned int address, unsigned int length ) { //if ( !vm->entryPoint ) { if ( (address | length) > vm->dataMask || (address + length) > vm->dataMask ) { SV_Error( "program tried to bypass data segment bounds" ); } } } /* ============== VM_CheckBounds2 ============== */ void VM_CheckBounds2( const vm_t *vm, unsigned int addr1, unsigned int addr2, unsigned int length ) { //if ( !vm->entryPoint ) { if ( (addr1 | addr2 | length) > vm->dataMask || (addr1 + length) > vm->dataMask || (addr2+length) > vm->dataMask ) { SV_Error( "program tried to bypass data segment bounds" ); } } } /* ============== VM_Init ============== */ void ED2_PrintEdicts (void); void PR2_Profile_f (void); void ED2_PrintEdict_f (void); void ED_Count (void); void PR_CleanLogText_Init(void); vm_t *currentVM = NULL; // bk001212 vm_t *lastVM = NULL; // bk001212 int vm_debugLevel; void *VM_ArgPtr( intptr_t intValue ) { if ( !intValue ) { return NULL; } // bk001220 - currentVM is missing on reconnect if ( currentVM==NULL ) return NULL; if ( currentVM->entryPoint ) { return (void *)(currentVM->dataBase + intValue); } else { return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask)); } } void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ) { if ( !intValue ) { return NULL; } // bk010124 - currentVM is missing on reconnect here as well? if ( vm==NULL ) return NULL; // if ( vm->entryPoint ) { return (void *)(vm->dataBase + intValue); } else { return (void *)(vm->dataBase + (intValue & vm->dataMask)); } } intptr_t VM_Ptr2VM( void* ptr ) { if ( !ptr ) { return 0; } // bk001220 - currentVM is missing on reconnect if ( currentVM==NULL ) return 0; if ( currentVM->entryPoint ) { return (intptr_t)ptr; } else { return (((byte*)ptr - currentVM->dataBase )) & currentVM->dataMask; } } intptr_t VM_ExplicitPtr2VM( vm_t *vm, void* ptr ) { if ( !ptr ) { return 0; } // bk001220 - currentVM is missing on reconnect if ( vm==NULL ) return 0; if ( vm->entryPoint ) { return (intptr_t)ptr; } else { return (((byte*)ptr - vm->dataBase )) & vm->dataMask; } } /* =============== VM_ValueToSymbol Assumes a program counter value =============== */ const char *VM_ValueToSymbol( vm_t *vm, int value ) { vmSymbol_t *sym; static char text[MAX_COM_TOKEN]; sym = vm->symbols; if ( !sym ) { return "NO SYMBOLS"; } // find the symbol while ( sym->next && sym->next->symValue <= value ) { sym = sym->next; } if ( value == sym->symValue ) { return sym->symName; } snprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue ); return text; } /* =============== VM_ValueToFunctionSymbol For profiling, find the symbol behind this value =============== */ vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) { vmSymbol_t *sym; static vmSymbol_t nullSym; sym = vm->symbols; if ( !sym ) { return &nullSym; } while ( sym->next && sym->next->symValue <= value ) { sym = sym->next; } return sym; } /* =============== VM_SymbolToValue =============== */ int VM_SymbolToValue( vm_t *vm, const char *symbol ) { vmSymbol_t *sym; for ( sym = vm->symbols ; sym ; sym = sym->next ) { if ( !strcmp( symbol, sym->symName ) ) { return sym->symValue; } } return 0; } /* =============== ParseHex =============== */ int ParseHex( const char *text ) { int value; int c; value = 0; while ( ( c = *text++ ) != 0 ) { if ( c >= '0' && c <= '9' ) { value = value * 16 + c - '0'; continue; } if ( c >= 'a' && c <= 'f' ) { value = value * 16 + 10 + c - 'a'; continue; } if ( c >= 'A' && c <= 'F' ) { value = value * 16 + 10 + c - 'A'; continue; } } return value; } /* =============== VM_LoadSymbols =============== */ void VM_LoadSymbols( vm_t *vm ) { union { char *c; void *v; } mapfile; char *text_p; //char name[MAX_QPATH]; char symbols[MAX_QPATH]; vmSymbol_t **prev, *sym; int count; int value; int chars; int segment; int numInstructions; // don't load symbols if not developer //if ( !com_developer->integer ) { return; } //COM_StripExtension((char*)vm->name, name); snprintf( symbols, sizeof( symbols ), "%s.map", vm->name ); mapfile.v = FS_LoadTempFile( symbols, NULL ); if ( !mapfile.c ) { Con_Printf( "Couldn't load symbol file: %s\n", symbols ); return; } numInstructions = vm->instructionCount; // parse the symbols text_p = mapfile.c; prev = &vm->symbols; count = 0; while ( 1 ) { text_p = COM_Parse( text_p ); if ( !text_p ) { break; } segment = ParseHex( com_token ); if ( segment ) { COM_Parse( text_p ); COM_Parse( text_p ); continue; // only load code segment values } text_p = COM_Parse( text_p ); if ( !text_p ) { Con_Printf( "WARNING: incomplete line at end of file\n" ); break; } value = ParseHex( com_token ); text_p = COM_Parse( text_p ); if ( !text_p ) { Con_Printf( "WARNING: incomplete line at end of file\n" ); break; } chars = strlen( com_token ); sym = Hunk_Alloc( sizeof( *sym ) + chars); *prev = sym; prev = &sym->next; sym->next = NULL; // convert value from an instruction number to a code offset if ( vm->instructionPointers && value >= 0 && value < numInstructions ) { value = vm->instructionPointers[value]; } sym->symValue = value; strlcpy( sym->symName, com_token, chars + 1 ); count++; } vm->numSymbols = count; Con_Printf( "%i symbols parsed from %s\n", count, symbols ); } static void VM_SwapLongs( void *data, int length ) { int i, *ptr; ptr = (int *) data; length /= sizeof( int ); for ( i = 0; i < length; i++ ) { ptr[ i ] = LittleLong( ptr[ i ] ); } } /* ============ VM_DllSyscall Dlls will call this directly rcg010206 The horror; the horror. The syscall mechanism relies on stack manipulation to get its args. This is likely due to C's inability to pass "..." parameters to a function in one clean chunk. On PowerPC Linux, these parameters are not necessarily passed on the stack, so while (&arg[0] == arg) is true, (&arg[1] == 2nd function parameter) is not necessarily accurate, as arg's value might have been stored to the stack or other piece of scratch memory to give it a valid address, but the next parameter might still be sitting in a register. Quake's syscall system also assumes that the stack grows downward, and that any needed types can be squeezed, safely, into a signed int. This hack below copies all needed values for an argument to a array in memory, so that Quake can get the correct values. This can also be used on systems where the stack grows upwards, as the presumably standard and safe stdargs.h macros are used. As for having enough space in a signed int for your datatypes, well, it might be better to wait for DOOM 3 before you start porting. :) The original code, while probably still inherently dangerous, seems to work well enough for the platforms it already works on. Rather than add the performance hit for those platforms, the original code is still in use there. For speed, we just grab 15 arguments, and don't worry about exactly how many the syscall actually needs; the extra is thrown away. ============ */ #if 1 // - disabled because now is different for each module intptr_t QDECL VM_DllSyscall( intptr_t arg, ... ) { #if !idx386 || defined __clang__ // rcg010206 - see commentary above intptr_t args[16]; va_list ap; int i; args[0] = arg; va_start( ap, arg ); for (i = 1; i < ARRAY_LEN( args ); i++ ) args[ i ] = va_arg( ap, intptr_t ); va_end( ap ); return currentVM->systemCall( args ); #else // original id code return currentVM->systemCall( &arg ); #endif } #endif /* ================= VM_ValidateHeader ================= */ static char *VM_ValidateHeader( vmHeader_t *header, int fileSize ) { static char errMsg[128]; int n; // truncated if ( fileSize < ( sizeof( vmHeader_t ) - sizeof( int ) ) ) { sprintf( errMsg, "truncated image header (%i bytes long)", fileSize ); return errMsg; } // bad magic if ( LittleLong( header->vmMagic ) != VM_MAGIC && LittleLong( header->vmMagic ) != VM_MAGIC_VER2 ) { sprintf( errMsg, "bad file magic %08x", LittleLong( header->vmMagic ) ); return errMsg; } // truncated if ( fileSize < sizeof( vmHeader_t ) && LittleLong( header->vmMagic ) != VM_MAGIC_VER2 ) { sprintf( errMsg, "truncated image header (%i bytes long)", fileSize ); return errMsg; } if ( LittleLong( header->vmMagic ) == VM_MAGIC_VER2 ) n = sizeof( vmHeader_t ); else n = ( sizeof( vmHeader_t ) - sizeof( int ) ); // byte swap the header VM_SwapLongs( header, n ); // bad code offset if ( header->codeOffset >= fileSize ) { sprintf( errMsg, "bad code segment offset %i", header->codeOffset ); return errMsg; } // bad code length if ( header->codeLength <= 0 || header->codeOffset + header->codeLength > fileSize ) { sprintf( errMsg, "bad code segment length %i", header->codeLength ); return errMsg; } // bad data offset if ( header->dataOffset >= fileSize || header->dataOffset != header->codeOffset + header->codeLength ) { sprintf( errMsg, "bad data segment offset %i", header->dataOffset ); return errMsg; } // bad data length if ( header->dataOffset + header->dataLength > fileSize ) { sprintf( errMsg, "bad data segment length %i", header->dataLength ); return errMsg; } if ( header->vmMagic == VM_MAGIC_VER2 ) { // bad lit/jtrg length if ( header->dataOffset + header->dataLength + header->litLength + header->jtrgLength != fileSize ) { sprintf( errMsg, "bad lit/jtrg segment length" ); return errMsg; } } // bad lit length else if ( header->dataOffset + header->dataLength + header->litLength != fileSize ) { sprintf( errMsg, "bad lit segment length %i", header->litLength ); return errMsg; } return NULL; } /* ================= VM_LoadQVM Load a .qvm file if ( alloc ) - Validate header, swap data - Alloc memory for data/instructions - Alloc memory for instructionPointers - NOT NEEDED - Load instructions - Clear/load data else - Check for header changes - Clear/load data ================= */ static vmHeader_t *VM_LoadQVM( vm_t *vm, qbool alloc ) { int length; unsigned int dataLength; unsigned int dataAlloc; int i; char filename[MAX_QPATH], *errorMsg; unsigned int crc32sum; //qbool tryjts; vmHeader_t *header; char num[32]; // load the image snprintf( filename, sizeof( filename ), "%s.qvm", vm->name ); Con_Printf( "Loading vm file %s...\n", filename ); header = ( vmHeader_t*)FS_LoadTempFile( filename, &length ); if ( !header ) { Con_Printf( "Failed.\n" ); VM_Free( vm ); return NULL; } crc32sum = CRC_Block( ( byte * ) header, length ); sprintf( num, "%i", crc32sum ); Info_SetValueForStarKey( svs.info, "*progs", num, MAX_SERVERINFO_STRING ); // will also swap header errorMsg = VM_ValidateHeader( header, length ); if ( errorMsg ) { VM_Free( vm ); Con_Printf( "%s\n", errorMsg ); return NULL; } vm->crc32sum = crc32sum; //tryjts = false; if( header->vmMagic == VM_MAGIC_VER2 ) { Con_Printf( "...which has vmMagic VM_MAGIC_VER2\n" ); } else { // tryjts = true; } vm->exactDataLength = header->dataLength + header->litLength + header->bssLength; dataLength = vm->exactDataLength + PROGRAM_STACK_EXTRA; vm->dataLength = dataLength; // round up to next power of 2 so all data operations can // be mask protected for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) ; dataLength = 1 << i; // reserve some space for effective LOCAL+LOAD* checks dataAlloc = dataLength + 1024; if ( dataLength >= (1U<<31) || dataAlloc >= (1U<<31) ) { VM_Free( vm ); Con_Printf( "%s: data segment is too large\n", __func__ ); return NULL; } if ( alloc ) { // allocate zero filled space for initialized and uninitialized data vm->dataBase = Hunk_Alloc( dataAlloc); vm->dataMask = dataLength - 1; vm->dataAlloc = dataAlloc; } else { // clear the data, but make sure we're not clearing more than allocated if ( vm->dataAlloc != dataAlloc ) { VM_Free( vm ); Con_Printf( "Warning: Data region size of %s not matching after" "VM_Restart()\n", filename ); return NULL; } memset( vm->dataBase, 0, vm->dataAlloc ); } // copy the intialized data memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength ); // byte swap the longs VM_SwapLongs( vm->dataBase, header->dataLength ); if( header->vmMagic == VM_MAGIC_VER2 ) { int previousNumJumpTableTargets = vm->numJumpTableTargets; header->jtrgLength &= ~0x03; vm->numJumpTableTargets = header->jtrgLength >> 2; Con_Printf( "Loading %d jump table targets\n", vm->numJumpTableTargets ); if ( alloc ) { vm->jumpTableTargets = Hunk_Alloc( header->jtrgLength); } else { if ( vm->numJumpTableTargets != previousNumJumpTableTargets ) { VM_Free( vm ); Con_Printf( "Warning: Jump table size of %s not matching after " "VM_Restart()\n", filename ); return NULL; } memset( vm->jumpTableTargets, 0, header->jtrgLength ); } memcpy( vm->jumpTableTargets, (byte *)header + header->dataOffset + header->dataLength + header->litLength, header->jtrgLength ); // byte swap the longs VM_SwapLongs( vm->jumpTableTargets, header->jtrgLength ); } /*if ( tryjts == true && (length = Load_JTS( vm, crc32sum, NULL, vmPakIndex )) >= 0 ) { // we are trying to load newer file? if ( vm->jumpTableTargets && vm->numJumpTableTargets != length >> 2 ) { Con_Printf( "Reload jts file\n" ); vm->jumpTableTargets = NULL; alloc = true; } vm->numJumpTableTargets = length >> 2; Con_Printf( "Loading %d external jump table targets\n", vm->numJumpTableTargets ); if ( alloc == true ) { vm->jumpTableTargets = Hunk_Alloc( length); } else { memset( vm->jumpTableTargets, 0, length ); } Load_JTS( vm, crc32sum, vm->jumpTableTargets, vmPakIndex ); }*/ return header; } /* ================= VM_LoadInstructions loads instructions in structured format ================= */ const char *VM_LoadInstructions( const byte *code_pos, int codeLength, int instructionCount, instruction_t *buf ) { static char errBuf[ 128 ]; const byte *code_start, *code_end; int i, n, op0, op1, opStack; instruction_t *ci; code_start = code_pos; // for printing code_end = code_pos + codeLength; ci = buf; opStack = 0; op1 = OP_UNDEF; // load instructions and perform some initial calculations/checks for ( i = 0; i < instructionCount; i++, ci++, op1 = op0 ) { op0 = *code_pos; if ( op0 < 0 || op0 >= OP_MAX ) { sprintf( errBuf, "bad opcode %02X at offset %d", op0, (int)(code_pos - code_start) ); return errBuf; } n = ops[ op0 ].size; if ( code_pos + 1 + n > code_end ) { sprintf( errBuf, "code_pos > code_end" ); return errBuf; } code_pos++; ci->op = op0; if ( n == 4 ) { ci->value = LittleLong( *((int*)code_pos) ); code_pos += 4; } else if ( n == 1 ) { ci->value = *((unsigned char*)code_pos); code_pos += 1; } else { ci->value = 0; } // setup jump value from previous const if ( op0 == OP_JUMP && op1 == OP_CONST ) { ci->value = (ci-1)->value; } ci->opStack = opStack; opStack += ops[ op0 ].stack; } return NULL; } /* =============================== VM_CheckInstructions performs additional consistency and security checks =============================== */ const char *VM_CheckInstructions( instruction_t *buf, int instructionCount, const byte *jumpTableTargets, int numJumpTableTargets, int dataLength ) { static char errBuf[ 128 ]; int i, n, v, op0, op1, opStack, pstack; instruction_t *ci, *proc; int startp, endp; ci = buf; opStack = 0; // opstack checks for ( i = 0; i < instructionCount; i++, ci++ ) { opStack += ops[ ci->op ].stack; if ( opStack < 0 ) { sprintf( errBuf, "opStack underflow at %i", i ); return errBuf; } if ( opStack >= PROC_OPSTACK_SIZE * 4 ) { sprintf( errBuf, "opStack overflow at %i", i ); return errBuf; } } ci = buf; pstack = 0; op1 = OP_UNDEF; proc = NULL; startp = 0; endp = instructionCount - 1; // Additional security checks for ( i = 0; i < instructionCount; i++, ci++, op1 = op0 ) { op0 = ci->op; // function entry if ( op0 == OP_ENTER ) { // missing block end if ( proc || ( pstack && op1 != OP_LEAVE ) ) { sprintf( errBuf, "missing proc end before %i", i ); return errBuf; } if ( ci->opStack != 0 ) { v = ci->opStack; sprintf( errBuf, "bad entry opstack %i at %i", v, i ); return errBuf; } v = ci->value; if ( v < 0 || v >= PROGRAM_STACK_SIZE || (v & 3) ) { sprintf( errBuf, "bad entry programStack %i at %i", v, i ); return errBuf; } pstack = ci->value; // mark jump target ci->jused = 1; proc = ci; startp = i + 1; // locate endproc for ( endp = 0, n = i+1 ; n < instructionCount; n++ ) { if ( buf[n].op == OP_PUSH && buf[n+1].op == OP_LEAVE ) { endp = n; break; } } if ( endp == 0 ) { sprintf( errBuf, "missing end proc for %i", i ); return errBuf; } continue; } // proc opstack will carry max.possible opstack value if ( proc && ci->opStack > proc->opStack ) proc->opStack = ci->opStack; // function return if ( op0 == OP_LEAVE ) { // bad return programStack if ( pstack != ci->value ) { v = ci->value; sprintf( errBuf, "bad programStack %i at %i", v, i ); return errBuf; } // bad opStack before return if ( ci->opStack != 4 ) { v = ci->opStack; sprintf( errBuf, "bad opStack %i at %i", v, i ); return errBuf; } v = ci->value; if ( v < 0 || v >= PROGRAM_STACK_SIZE || (v & 3) ) { sprintf( errBuf, "bad return programStack %i at %i", v, i ); return errBuf; } if ( op1 == OP_PUSH ) { if ( proc == NULL ) { sprintf( errBuf, "unexpected proc end at %i", i ); return errBuf; } proc = NULL; startp = i + 1; // next instruction endp = instructionCount - 1; // end of the image } continue; } // conditional jumps if ( ops[ ci->op ].flags & JUMP ) { v = ci->value; // conditional jumps should have opStack == 8 if ( ci->opStack != 8 ) { sprintf( errBuf, "bad jump opStack %i at %i", ci->opStack, i ); return errBuf; } //if ( v >= header->instructionCount ) { // allow only local proc jumps if ( v < startp || v > endp ) { sprintf( errBuf, "jump target %i at %i is out of range (%i,%i)", v, i-1, startp, endp ); return errBuf; } if ( buf[v].opStack != 0 ) { n = buf[v].opStack; sprintf( errBuf, "jump target %i has bad opStack %i", v, n ); return errBuf; } // mark jump target buf[v].jused = 1; continue; } // unconditional jumps if ( op0 == OP_JUMP ) { // jumps should have opStack == 4 if ( ci->opStack != 4 ) { sprintf( errBuf, "bad jump opStack %i at %i", ci->opStack, i ); return errBuf; } if ( op1 == OP_CONST ) { v = buf[i-1].value; // allow only local jumps if ( v < startp || v > endp ) { sprintf( errBuf, "jump target %i at %i is out of range (%i,%i)", v, i-1, startp, endp ); return errBuf; } if ( buf[v].opStack != 0 ) { n = buf[v].opStack; sprintf( errBuf, "jump target %i has bad opStack %i", v, n ); return errBuf; } if ( buf[v].op == OP_ENTER ) { n = buf[v].op; sprintf( errBuf, "jump target %i has bad opcode %i", v, n ); return errBuf; } if ( v == (i-1) ) { sprintf( errBuf, "self loop at %i", v ); return errBuf; } // mark jump target buf[v].jused = 1; } else { if ( proc ) proc->swtch = 1; else ci->swtch = 1; } continue; } if ( op0 == OP_CALL ) { if ( ci->opStack < 4 ) { sprintf( errBuf, "bad call opStack at %i", i ); return errBuf; } if ( op1 == OP_CONST ) { v = buf[i-1].value; // analyse only local function calls if ( v >= 0 ) { if ( v >= instructionCount ) { sprintf( errBuf, "call target %i is out of range", v ); return errBuf; } if ( buf[v].op != OP_ENTER ) { n = buf[v].op; sprintf( errBuf, "call target %i has bad opcode %i", v, n ); return errBuf; } if ( v == 0 ) { sprintf( errBuf, "explicit vmMain call inside VM" ); return errBuf; } // mark jump target buf[v].jused = 1; } } continue; } if ( ci->op == OP_ARG ) { v = ci->value & 255; // argument can't exceed programStack frame if ( v < 8 || v > pstack - 4 || (v & 3) ) { sprintf( errBuf, "bad argument address %i at %i", v, i ); return errBuf; } continue; } if ( ci->op == OP_LOCAL ) { v = ci->value; if ( proc == NULL ) { sprintf( errBuf, "missing proc frame for local %i at %i", v, i ); return errBuf; } if ( (ci+1)->op == OP_LOAD1 || (ci+1)->op == OP_LOAD2 || (ci+1)->op == OP_LOAD4 || (ci+1)->op == OP_ARG ) { // FIXME: alloc 256 bytes of programStack in VM_CallCompiled()? if ( v < 8 || v >= proc->value + 256 ) { sprintf( errBuf, "bad local address %i at %i", v, i ); return errBuf; } } } if ( ci->op == OP_LOAD4 && op1 == OP_CONST ) { v = (ci-1)->value; if ( v < 0 || v > dataLength - 4 ) { sprintf( errBuf, "bad load4 address %i at %i", v, i - 1 ); return errBuf; } } if ( ci->op == OP_LOAD2 && op1 == OP_CONST ) { v = (ci-1)->value; if ( v < 0 || v > dataLength - 2 ) { sprintf( errBuf, "bad load2 address %i at %i", v, i - 1 ); return errBuf; } } if ( ci->op == OP_LOAD1 && op1 == OP_CONST ) { v = (ci-1)->value; if ( v < 0 || v > dataLength - 1 ) { sprintf( errBuf, "bad load1 address %i at %i", v, i - 1 ); return errBuf; } } if ( ci->op == OP_BLOCK_COPY ) { v = ci->value; if ( v >= dataLength ) { sprintf( errBuf, "bad count %i for block copy at %i", v, i - 1 ); return errBuf; } } // op1 = op0; // ci++; } if ( op1 != OP_UNDEF && op1 != OP_LEAVE ) { sprintf( errBuf, "missing return instruction at the end of the image" ); return errBuf; } // ensure that the optimization pass knows about all the jump table targets if ( jumpTableTargets ) { // first pass - validate for( i = 0; i < numJumpTableTargets; i++ ) { n = *(int *)(jumpTableTargets + ( i * sizeof( int ) ) ); if ( n < 0 || n >= instructionCount ) { Con_Printf( "jump target %i set on instruction %i that is out of range [0..%i]", i, n, instructionCount - 1 ); break; } if ( buf[n].opStack != 0 ) { Con_Printf( "jump target %i set on instruction %i (%s) with bad opStack %i\n", i, n, opname[ buf[n].op ], buf[n].opStack ); break; } } if ( i != numJumpTableTargets ) { // we may trap this on buggy VM_MAGIC_VER2 images // but we can safely optimize code even without JTRGSEG // so just switch to VM_MAGIC path here goto __noJTS; } // second pass - apply for( i = 0; i < numJumpTableTargets; i++ ) { n = *(int *)(jumpTableTargets + ( i * sizeof( int ) ) ); buf[n].jused = 1; } } else { __noJTS: v = 0; // instructions with opStack > 0 can't be jump labels so its safe to optimize/merge for ( i = 0, ci = buf; i < instructionCount; i++, ci++ ) { if ( ci->op == OP_ENTER ) { v = ci->swtch; continue; } // if there is a switch statement in function - // mark all potential jump labels if ( ci->swtch ) v = ci->swtch; if ( ci->opStack > 0 ) ci->jused = 0; else if ( v ) ci->jused = 1; } } return NULL; } qbool VM_LoadNative( vm_t * vm) { char name[MAX_OSPATH]; char *gpath = NULL; void ( *dllEntry ) ( void * ); while ( ( gpath = FS_NextPath( gpath ) ) ) { snprintf( name, sizeof( name ), "%s/%s." DLEXT, gpath, vm->name ); vm->dllHandle = Sys_DLOpen( name ); if ( vm->dllHandle ) { Con_Printf( "LoadLibrary (%s)\n", name ); break; } } if ( !vm->dllHandle ) return false; dllEntry = Sys_DLProc( vm->dllHandle, "dllEntry" ); vm->entryPoint = Sys_DLProc( vm->dllHandle, "vmMain" ); if ( !dllEntry || !vm->entryPoint ) { Sys_DLClose( vm->dllHandle ); SV_Error( "VM_LoadNative: couldn't initialize module %s", name ); } dllEntry( vm->dllSyscall ); Info_SetValueForStarKey( svs.info, "*qvm", DLEXT, MAX_SERVERINFO_STRING ); Info_SetValueForStarKey( svs.info, "*progs", DLEXT, MAX_SERVERINFO_STRING ); vm->type = VMI_NATIVE; return true; } /* ================ VM_Create If image ends in .qvm it will be interpreted, otherwise it will attempt to load as a system dll ================ */ vm_t *VM_Create( vmIndex_t index, const char *name, syscall_t systemCalls, /*dllSyscall_t dllSyscalls,*/ vmInterpret_t interpret ) { //int remaining; vmHeader_t *header; vm_t *vm; if ( !systemCalls ) { SV_Error( "VM_Create: bad parms" ); } if ( (unsigned)index >= VM_COUNT ) { SV_Error( "VM_Create: bad vm index %i", index ); } //remaining = Hunk_MemoryRemaining(); vm = &vmTable[ index ]; // see if we already have the VM if ( vm->name ) { if ( vm->index != index ) { SV_Error( "VM_Create: bad allocated vm index %i", vm->index ); return NULL; } return vm; } vm->name = name; vm->index = index; vm->systemCall = systemCalls; vm->dllSyscall = VM_DllSyscall;//dllSyscalls; //vm->privateFlag = CVAR_PRIVATE; // never allow dll loading with a demo /*if ( interpret == VMI_NATIVE ) { if ( Cvar_VariableIntegerValue( "fs_restrict" ) ) { interpret = VMI_COMPILED; } }*/ if ( interpret == VMI_NATIVE ) { // try to load as a system dll //Con_Printf( "Loading dll file %s.\n", name ); if ( VM_LoadNative( vm ) ) { //vm->privateFlag = 0; // allow reading private cvars vm->dataAlloc = ~0U; vm->dataMask = ~0U; vm->dataBase = 0; return vm; } Con_Printf( "Failed to load dll, looking for qvm.\n" ); interpret = VMI_COMPILED; } // load the image if( ( header = VM_LoadQVM( vm, true ) ) == NULL ) { return NULL; } // allocate space for the jump targets, which will be filled in by the compile/prep functions vm->instructionCount = header->instructionCount; //vm->instructionPointers = Hunk_Alloc(vm->instructionCount * sizeof(*vm->instructionPointers), h_high); vm->instructionPointers = NULL; // copy or compile the instructions vm->codeLength = header->codeLength; // the stack is implicitly at the end of the image vm->programStack = vm->dataMask + 1; vm->stackBottom = vm->programStack - PROGRAM_STACK_SIZE - PROGRAM_STACK_EXTRA; vm->compiled = false; #ifdef NO_VM_COMPILED if(interpret >= VMI_COMPILED) { Con_Printf("Architecture doesn't have a bytecode compiler, using interpreter\n"); interpret = VMI_BYTECODE; } #else if ( interpret >= VMI_COMPILED ) { if ( VM_Compile( vm, header ) ) { vm->compiled = true; } } #endif // VM_Compile may have reset vm->compiled if compilation failed if ( !vm->compiled ) { if ( !VM_PrepareInterpreter2( vm, header ) ) { //FS_FreeFile( header ); // free the original file VM_Free( vm ); return NULL; } } vm->type = interpret; // free the original file //FS_FreeFile( header ); // load the map file VM_LoadSymbols( vm ); //Con_Printf( "%s loaded in %d bytes on the hunk\n", vm->name, remaining - Hunk_MemoryRemaining() ); return vm; } /* ============== VM_Free ============== */ void VM_Free( vm_t *vm ) { if( !vm ) { return; } /* if ( vm->callLevel ) { if ( !forced_unload ) { SV_Error( ERR_FATAL, "VM_Free(%s) on running vm", vm->name ); return; } else { Con_Printf( "forcefully unloading %s vm\n", vm->name ); } }*/ if ( vm->destroy ) vm->destroy( vm ); if ( vm->dllHandle ) Sys_DLClose( vm->dllHandle ); #if 0 // now automatically freed by hunk if ( vm->codeBase.ptr ) { Z_Free( vm->codeBase.ptr ); } if ( vm->dataBase ) { Z_Free( vm->dataBase ); } if ( vm->instructionPointers ) { Z_Free( vm->instructionPointers ); } #endif currentVM = NULL; lastVM = NULL; memset( vm, 0, sizeof( *vm ) ); } void VM_Clear( void ) { int i; for ( i = 0; i < VM_COUNT; i++ ) { VM_Free( &vmTable[ i ] ); } currentVM = NULL; lastVM = NULL; } /* ============== VM_Call Upon a system call, the stack will look like: sp+32 parm1 sp+28 parm0 sp+24 return value sp+20 return address sp+16 local1 sp+14 local0 sp+12 arg1 sp+8 arg0 sp+4 return stack sp return address An interpreted function will immediately execute an OP_ENTER instruction, which will subtract space for locals from sp ============== */ intptr_t QDECL VM_Call( vm_t *vm, int nargs, int callnum, ... ) { vm_t *oldVM; intptr_t r; int i; if ( !vm ) { SV_Error( "VM_Call with NULL vm" ); } oldVM = currentVM; currentVM = vm; lastVM = vm; if ( vm_debugLevel ) { Con_Printf( "VM_Call( %d )\n", callnum ); } #ifdef DEBUG if ( nargs >= MAX_VMMAIN_CALL_ARGS ) { SV_Error( "VM_Call: nargs >= MAX_VMMAIN_CALL_ARGS" ); } #endif ++vm->callLevel; // if we have a dll loaded, call it directly if ( vm->entryPoint ) { //rcg010207 - see dissertation at top of VM_DllSyscall() in this file. int args[MAX_VMMAIN_CALL_ARGS-1]; va_list ap; va_start( ap, callnum ); for ( i = 0; i < nargs; i++ ) { args[i] = va_arg( ap, int ); } va_end(ap); // add more agruments if you're changed MAX_VMMAIN_CALL_ARGS: r = vm->entryPoint( callnum, args[0], args[1], args[2] ); } else { #if idx386 && !defined __clang__ // calling convention doesn't need conversion in some cases #ifndef NO_VM_COMPILED if ( vm->compiled ) r = VM_CallCompiled( vm, nargs+1, (int*)&callnum ); else #endif r = VM_CallInterpreted2( vm, nargs+1, (int*)&callnum ); #else int args[MAX_VMMAIN_CALL_ARGS]; va_list ap; args[0] = callnum; va_start( ap, callnum ); for ( i = 0; i < nargs; i++ ) { args[i+1] = va_arg( ap, int ); } va_end(ap); #ifndef NO_VM_COMPILED if ( vm->compiled ) r = VM_CallCompiled( vm, nargs+1, &args[0] ); else #endif r = VM_CallInterpreted2( vm, nargs+1, &args[0] ); #endif } --vm->callLevel; if ( oldVM != NULL ) // bk001220 - assert(currentVM!=NULL) for oldVM==NULL currentVM = oldVM; return r; } //================================================================= static int QDECL VM_ProfileSort( const void *a, const void *b ) { vmSymbol_t *sa, *sb; sa = *(vmSymbol_t **)a; sb = *(vmSymbol_t **)b; if ( sa->profileCount < sb->profileCount ) { return -1; } if ( sa->profileCount > sb->profileCount ) { return 1; } return 0; } /* ============== VM_VmProfile_f ============== */ void VM_VmProfile_f( void ) { vm_t *vm; vmSymbol_t **sorted, *sym; int i; double total; vm = &vmTable[VM_GAME]; if ( !vm->name ) { Con_Printf( " VM is not running.\n" ); return; } if ( vm == NULL ) { return; } if ( !vm->numSymbols ) { return; } sorted = Q_malloc( vm->numSymbols * sizeof( *sorted ) ); sorted[0] = vm->symbols; total = sorted[0]->profileCount; for ( i = 1 ; i < vm->numSymbols ; i++ ) { sorted[i] = sorted[i-1]->next; total += sorted[i]->profileCount; } qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort ); for ( i = 0 ; i < vm->numSymbols ; i++ ) { int perc; sym = sorted[i]; perc = 100 * (float) sym->profileCount / total; Con_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName ); sym->profileCount = 0; } Con_Printf(" %9.0f total\n", total ); Q_free( sorted ); } /* ============== VM_VmInfo_f ============== */ void VM_VmInfo_f( void ) { vm_t *vm; int i; Con_Printf( "Registered virtual machines:\n" ); for ( i = 0 ; i < VM_COUNT ; i++ ) { vm = &vmTable[i]; if ( !vm->name ) { continue; } Con_Printf( "%s : ", vm->name ); if ( vm->dllHandle ) { Con_Printf( "native\n" ); continue; } if ( vm->compiled ) { Con_Printf( "compiled on load\n" ); } else { Con_Printf( "interpreted\n" ); } Con_Printf( " code length : %7i\n", vm->codeLength ); Con_Printf( " table length: %7i\n", vm->instructionCount*4 ); Con_Printf( " data length : %7i\n", vm->dataMask + 1 ); } } /* =============== VM_LogSyscalls Insert calls to this while debugging the vm compiler =============== */ void VM_LogSyscalls(int *args) { #if 1 static int callnum; static FILE *f; if (!f) { f = fopen("syscalls.log", "w"); if (!f) { return; } } callnum++; fprintf(f, "%i: %p (%i) = %i %i %i %i\n", callnum, (void*)(args - (int *)currentVM->dataBase), args[0], args[1], args[2], args[3], args[4]); #endif } #endif /* USE_PR2 */ mvdsv-0.35/src/vm.h000066400000000000000000000034751427146041000141720ustar00rootroot00000000000000#ifndef VM_H #define VM_H /* ============================================================== VIRTUAL MACHINE ============================================================== */ #ifdef _WIN32 #define QDECL __cdecl #else #define QDECL #endif typedef struct vm_s vm_t; typedef enum { VMI_NONE, VMI_NATIVE, VMI_BYTECODE, VMI_COMPILED } vmInterpret_t; typedef enum { VM_BAD = -1, VM_GAME = 0, VM_COUNT } vmIndex_t; extern vm_t *currentVM; typedef intptr_t (*syscall_t)( intptr_t *parms ); typedef intptr_t (QDECL *dllSyscall_t)( intptr_t callNum, ... ); typedef void (QDECL *dllEntry_t)( dllSyscall_t syscallptr ); void VM_Init( void ); vm_t *VM_Create( vmIndex_t index, const char* name, syscall_t systemCalls, /*dllSyscall_t dllSyscalls,*/ vmInterpret_t interpret ); extern vm_t *currentVM; // module should be bare: "cgame", not "cgame.dll" or "vm/cgame.qvm" void VM_Free( vm_t *vm ); void VM_Clear(void); void VM_Forced_Unload_Start(void); void VM_Forced_Unload_Done(void); vm_t *VM_Restart( vm_t *vm ); intptr_t QDECL VM_Call( vm_t *vm, int nargs, int callNum, ... ); void VM_Debug( int level ); void VM_CheckBounds( const vm_t *vm, unsigned int address, unsigned int length ); void VM_CheckBounds2( const vm_t *vm, unsigned int addr1, unsigned int addr2, unsigned int length ); #if 1 #define VM_CHECKBOUNDS VM_CheckBounds #define VM_CHECKBOUNDS2 VM_CheckBounds2 #else // for performance evaluation purposes #define VM_CHECKBOUNDS(vm,a,b) #define VM_CHECKBOUNDS2(vm,a,b,c) #endif void *VM_ArgPtr( intptr_t intValue ); void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ); intptr_t VM_Ptr2VM( void* ptr ) ; intptr_t VM_ExplicitPtr2VM( vm_t *vm, void* ptr ); #define VMA(x) VM_ArgPtr(args[x]) static inline float _vmf(intptr_t x) { floatint_t v; v.i = (int)x; return v.f; } #define VMF(x) _vmf(args[x]) #endif mvdsv-0.35/src/vm_interpreted.c000066400000000000000000000316221427146041000165650ustar00rootroot00000000000000/* * 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 */ #ifdef USE_PR2 #ifdef SERVERONLY #include "qwsvdef.h" #else #include "quakedef.h" #endif #include "vm_local.h" char *VM_Indent( vm_t *vm ) { static char *string = " "; if ( vm->callLevel > 20 ) { return string; } return string + 2 * ( 20 - vm->callLevel ); } void VM_StackTrace( vm_t *vm, int programCounter, int programStack ) { int count; count = 0; do { Con_Printf( "%s\n", VM_ValueToSymbol( vm, programCounter ) ); programStack = *(int *)&vm->dataBase[programStack+4]; programCounter = *(int *)&vm->dataBase[programStack]; } while ( programCounter != -1 && ++count < 32 ); } // macro opcode sequences typedef enum { MOP_LOCAL_LOAD4 = OP_MAX, MOP_LOCAL_LOAD4_CONST, MOP_LOCAL_LOCAL, MOP_LOCAL_LOCAL_LOAD4, } macro_op_t; /* ================= VM_FindMOps Search for known macro-op sequences ================= */ static void VM_FindMOps( instruction_t *buf, int instructionCount ) { int i, op0; instruction_t *ci; ci = buf; i = 0; while ( i < instructionCount ) { op0 = ci->op; if ( op0 == OP_LOCAL && (ci+1)->op == OP_LOAD4 && (ci+2)->op == OP_CONST ) { ci->op = MOP_LOCAL_LOAD4_CONST; ci += 3; i += 3; continue; } if ( op0 == OP_LOCAL && (ci+1)->op == OP_LOAD4 ) { ci->op = MOP_LOCAL_LOAD4; ci += 2; i += 2; continue; } if ( op0 == OP_LOCAL && (ci+1)->op == OP_LOCAL && (ci+2)->op == OP_LOAD4 ) { ci->op = MOP_LOCAL_LOCAL_LOAD4; ci += 3; i += 3; continue; } if ( op0 == OP_LOCAL && (ci+1)->op == OP_LOCAL ) { ci->op = MOP_LOCAL_LOCAL; ci += 2; i += 2; continue; } ci++; i++; } } /* ==================== VM_PrepareInterpreter2 ==================== */ qbool VM_PrepareInterpreter2( vm_t *vm, vmHeader_t *header ) { const char *errMsg; instruction_t *buf; buf = ( instruction_t *) Hunk_Alloc( (vm->instructionCount + 8) * sizeof( instruction_t )); errMsg = VM_LoadInstructions( (byte *) header + header->codeOffset, header->codeLength, header->instructionCount, buf ); if ( !errMsg ) { errMsg = VM_CheckInstructions( buf, vm->instructionCount, vm->jumpTableTargets, vm->numJumpTableTargets, vm->exactDataLength ); } if ( errMsg ) { Con_Printf( "VM_PrepareInterpreter2 error: %s\n", errMsg ); return false; } //VM_ReplaceInstructions( vm, buf ); VM_FindMOps( buf, vm->instructionCount ); vm->codeBase.ptr = (void*)buf; return true; } /* ============== VM_CallInterpreted2 Upon a system call, the stack will look like: sp+32 parm1 sp+28 parm0 sp+24 return stack sp+20 return address sp+16 local1 sp+14 local0 sp+12 arg1 sp+8 arg0 sp+4 return stack sp return address An interpreted function will immediately execute an OP_ENTER instruction, which will subtract space for locals from sp ============== */ int VM_CallInterpreted2( vm_t *vm, int nargs, int *args ) { int stack[MAX_OPSTACK_SIZE]; int *opStack, *opStackTop; unsigned int programStack; unsigned int stackOnEntry; byte *image; int v1, v0; int dataMask; instruction_t *inst, *ci; floatint_t r0, r1; int opcode; int *img; int i; // interpret the code //vm->currentlyInterpreting = true; // we might be called recursively, so this might not be the very top programStack = stackOnEntry = vm->programStack; // set up the stack frame image = vm->dataBase; inst = (instruction_t *)vm->codeBase.ptr; dataMask = vm->dataMask; // leave a free spot at start of stack so // that as long as opStack is valid, opStack-1 will // not corrupt anything opStack = &stack[1]; opStackTop = stack + ARRAY_LEN( stack ) - 1; programStack -= (MAX_VMMAIN_CALL_ARGS+2)*4; img = (int*)&image[ programStack ]; for ( i = 0; i < nargs; i++ ) { img[ i + 2 ] = args[ i ]; } img[ 1 ] = 0; // return stack img[ 0 ] = -1; // will terminate the loop on return ci = inst; // main interpreter loop, will exit when a LEAVE instruction // grabs the -1 program counter while ( 1 ) { r0.i = opStack[0]; r1.i = opStack[-1]; nextInstruction2: v0 = ci->value; opcode = ci->op; ci++; switch ( opcode ) { case OP_BREAK: vm->breakCount++; goto nextInstruction2; case OP_ENTER: // get size of stack frame programStack -= v0; if ( programStack <= vm->stackBottom ) { VM_StackTrace(vm, ci - (instruction_t *)vm->codeBase.ptr, programStack); SV_Error( "VM programStack overflow" ); } if ( opStack + ((ci-1)->opStack/4) >= opStackTop ) { VM_StackTrace(vm, ci - (instruction_t *)vm->codeBase.ptr, programStack); SV_Error( "VM opStack overflow" ); } break; case OP_LEAVE: // remove our stack frame programStack += v0; // grab the saved program counter v1 = *(int *)&image[ programStack ]; // check for leaving the VM if ( v1 == -1 ) { goto done; } else if ( (unsigned)v1 >= vm->instructionCount ) { VM_StackTrace(vm, ci - (instruction_t *)vm->codeBase.ptr, programStack); SV_Error( "VM program counter out of range in OP_LEAVE" ); } ci = inst + v1; break; case OP_CALL: // save current program counter *(int *)&image[ programStack ] = ci - inst; // jump to the location on the stack if ( r0.i < 0 ) { // system call // save the stack to allow recursive VM entry //vm->programStack = programStack - 4; vm->programStack = programStack - 8; *(int *)&image[ programStack + 4 ] = ~r0.i; { #if idx64 //__WORDSIZE == 64 // the vm has ints on the stack, we expect // longs so we have to convert it intptr_t argarr[16]; int argn; for ( argn = 0; argn < ARRAY_LEN( argarr ); ++argn ) { argarr[ argn ] = *(int*)&image[ programStack + 4 + 4*argn ]; } v0 = vm->systemCall( &argarr[0] ); #else //VM_LogSyscalls( (int *)&image[ programStack + 4 ] ); v0 = vm->systemCall( (intptr_t *)&image[ programStack + 4 ] ); #endif } // save return value //opStack++; ci = inst + *(int *)&image[ programStack ]; *opStack = v0; } else if ( r0.u < vm->instructionCount ) { // vm call ci = inst + r0.i; opStack--; } else { VM_StackTrace(vm, ci - (instruction_t *)vm->codeBase.ptr, programStack); SV_Error( "VM program counter out of range in OP_CALL" ); } break; // push and pop are only needed for discarded or bad function return values case OP_PUSH: opStack++; break; case OP_POP: opStack--; break; case OP_CONST: opStack++; r1.i = r0.i; r0.i = *opStack = v0; goto nextInstruction2; case OP_LOCAL: opStack++; r1.i = r0.i; r0.i = *opStack = v0 + programStack; goto nextInstruction2; case OP_JUMP: if ( r0.u >= vm->instructionCount ) { VM_StackTrace(vm, ci - (instruction_t *)vm->codeBase.ptr, programStack); SV_Error( "VM program counter out of range in OP_JUMP" ); } ci = inst + r0.i; opStack--; break; /* =================================================================== BRANCHES =================================================================== */ case OP_EQ: opStack -= 2; if ( r1.i == r0.i ) ci = inst + v0; break; case OP_NE: opStack -= 2; if ( r1.i != r0.i ) ci = inst + v0; break; case OP_LTI: opStack -= 2; if ( r1.i < r0.i ) ci = inst + v0; break; case OP_LEI: opStack -= 2; if ( r1.i <= r0.i ) ci = inst + v0; break; case OP_GTI: opStack -= 2; if ( r1.i > r0.i ) ci = inst + v0; break; case OP_GEI: opStack -= 2; if ( r1.i >= r0.i ) ci = inst + v0; break; case OP_LTU: opStack -= 2; if ( r1.u < r0.u ) ci = inst + v0; break; case OP_LEU: opStack -= 2; if ( r1.u <= r0.u ) ci = inst + v0; break; case OP_GTU: opStack -= 2; if ( r1.u > r0.u ) ci = inst + v0; break; case OP_GEU: opStack -= 2; if ( r1.u >= r0.u ) ci = inst + v0; break; case OP_EQF: opStack -= 2; if ( r1.f == r0.f ) ci = inst + v0; break; case OP_NEF: opStack -= 2; if ( r1.f != r0.f ) ci = inst + v0; break; case OP_LTF: opStack -= 2; if ( r1.f < r0.f ) ci = inst + v0; break; case OP_LEF: opStack -= 2; if ( r1.f <= r0.f ) ci = inst + v0; break; case OP_GTF: opStack -= 2; if ( r1.f > r0.f ) ci = inst + v0; break; case OP_GEF: opStack -= 2; if ( r1.f >= r0.f ) ci = inst + v0; break; //=================================================================== case OP_LOAD1: r0.i = *opStack = image[ r0.i & dataMask ]; goto nextInstruction2; case OP_LOAD2: r0.i = *opStack = *(unsigned short *)&image[ r0.i & dataMask ]; goto nextInstruction2; case OP_LOAD4: r0.i = *opStack = *(int *)&image[ r0.i & dataMask ]; goto nextInstruction2; case OP_STORE1: image[ r1.i & dataMask ] = r0.i; opStack -= 2; break; case OP_STORE2: *(short *)&image[ r1.i & dataMask ] = r0.i; opStack -= 2; break; case OP_STORE4: *(int *)&image[ r1.i & dataMask ] = r0.i; opStack -= 2; break; case OP_ARG: // single byte offset from programStack *(int *)&image[ ( v0 + programStack ) /*& ( dataMask & ~3 ) */ ] = r0.i; opStack--; break; case OP_BLOCK_COPY: { int *src, *dest; int count, srci, desti; count = v0; // MrE: copy range check srci = r0.i & dataMask; desti = r1.i & dataMask; count = ((srci + count) & dataMask) - srci; count = ((desti + count) & dataMask) - desti; src = (int *)&image[ srci ]; dest = (int *)&image[ desti ]; memcpy( dest, src, count ); opStack -= 2; } break; case OP_SEX8: *opStack = (signed char)*opStack; break; case OP_SEX16: *opStack = (short)*opStack; break; case OP_NEGI: *opStack = -r0.i; break; case OP_ADD: opStack[-1] = r1.i + r0.i; opStack--; break; case OP_SUB: opStack[-1] = r1.i - r0.i; opStack--; break; case OP_DIVI: opStack[-1] = r1.i / r0.i; opStack--; break; case OP_DIVU: opStack[-1] = r1.u / r0.u; opStack--; break; case OP_MODI: opStack[-1] = r1.i % r0.i; opStack--; break; case OP_MODU: opStack[-1] = r1.u % r0.u; opStack--; break; case OP_MULI: opStack[-1] = r1.i * r0.i; opStack--; break; case OP_MULU: opStack[-1] = r1.u * r0.u; opStack--; break; case OP_BAND: opStack[-1] = r1.u & r0.u; opStack--; break; case OP_BOR: opStack[-1] = r1.u | r0.u; opStack--; break; case OP_BXOR: opStack[-1] = r1.u ^ r0.u; opStack--; break; case OP_BCOM: *opStack = ~ r0.u; break; case OP_LSH: opStack[-1] = r1.i << r0.i; opStack--; break; case OP_RSHI: opStack[-1] = r1.i >> r0.i; opStack--; break; case OP_RSHU: opStack[-1] = r1.u >> r0.i; opStack--; break; case OP_NEGF: *(float *)opStack = - r0.f; break; case OP_ADDF: *(float *)(opStack-1) = r1.f + r0.f; opStack--; break; case OP_SUBF: *(float *)(opStack-1) = r1.f - r0.f; opStack--; break; case OP_DIVF: *(float *)(opStack-1) = r1.f / r0.f; opStack--; break; case OP_MULF: *(float *)(opStack-1) = r1.f * r0.f; opStack--; break; case OP_CVIF: *(float *)opStack = (float) r0.i; break; case OP_CVFI: *opStack = (int) r0.f; break; case MOP_LOCAL_LOAD4: ci++; opStack++; r1.i = r0.i; r0.i = *opStack = *(int *)&image[ v0 + programStack ]; goto nextInstruction2; case MOP_LOCAL_LOAD4_CONST: r1.i = opStack[1] = *(int *)&image[ v0 + programStack ]; r0.i = opStack[2] = (ci+1)->value; opStack += 2; ci += 2; goto nextInstruction2; case MOP_LOCAL_LOCAL: r1.i = opStack[1] = v0 + programStack; r0.i = opStack[2] = ci->value + programStack; opStack += 2; ci++; goto nextInstruction2; case MOP_LOCAL_LOCAL_LOAD4: r1.i = opStack[1] = v0 + programStack; r0.i /*= opStack[2]*/ = ci->value + programStack; r0.i = opStack[2] = *(int *)&image[ r0.i /*& dataMask*/ ]; opStack += 2; ci += 2; goto nextInstruction2; } } done: //vm->currentlyInterpreting = false; if ( opStack != &stack[2] ) { VM_StackTrace(vm, ci - (instruction_t *)vm->codeBase.ptr, programStack); SV_Error( "Interpreter error: opStack = %ld", (long int) (opStack - stack) ); } vm->programStack = stackOnEntry; // return the result return *opStack; } #endif /* USE_PR2 */ mvdsv-0.35/src/vm_local.h000066400000000000000000000140321427146041000153330ustar00rootroot00000000000000/* * 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 * * */ #ifndef VM_LOCAL_H #define VM_LOCAL_H #include "vm.h" #define MAX_OPSTACK_SIZE 512 #define PROC_OPSTACK_SIZE 30 #define STACK_MASK (MAX_OPSTACK_SIZE-1) #define DEBUGSTR va("%s%i", VM_Indent(vm), opStack-stack ) // we don't need more than 4 arguments (counting callnum) for vmMain, at least in Quake3 #define MAX_VMMAIN_CALL_ARGS 4 // don't change // Hardcoded in q3asm an reserved at end of bss #define PROGRAM_STACK_SIZE 0x10000 // for some buggy mods #define PROGRAM_STACK_EXTRA (32*1024) #define PAD(base, alignment) (((base)+(alignment)-1) & ~((alignment)-1)) #define PADLEN(base, alignment) (PAD((base), (alignment)) - (base)) typedef enum { OP_UNDEF, OP_IGNORE, OP_BREAK, OP_ENTER, OP_LEAVE, OP_CALL, OP_PUSH, OP_POP, OP_CONST, OP_LOCAL, OP_JUMP, //------------------- OP_EQ, OP_NE, OP_LTI, OP_LEI, OP_GTI, OP_GEI, OP_LTU, OP_LEU, OP_GTU, OP_GEU, OP_EQF, OP_NEF, OP_LTF, OP_LEF, OP_GTF, OP_GEF, //------------------- OP_LOAD1, OP_LOAD2, OP_LOAD4, OP_STORE1, OP_STORE2, OP_STORE4, // *(stack[top-1]) = stack[top] OP_ARG, OP_BLOCK_COPY, //------------------- OP_SEX8, OP_SEX16, OP_NEGI, OP_ADD, OP_SUB, OP_DIVI, OP_DIVU, OP_MODI, OP_MODU, OP_MULI, OP_MULU, OP_BAND, OP_BOR, OP_BXOR, OP_BCOM, OP_LSH, OP_RSHI, OP_RSHU, OP_NEGF, OP_ADDF, OP_SUBF, OP_DIVF, OP_MULF, OP_CVIF, OP_CVFI, OP_MAX } opcode_t; typedef struct { int value; // 32 byte op; // 8 byte opStack; // 8 unsigned jused:1; unsigned swtch:1; } instruction_t; typedef struct vmSymbol_s { struct vmSymbol_s *next; int symValue; int profileCount; char symName[1]; // variable sized } vmSymbol_t; //typedef void(*vmfunc_t)(void); typedef union vmFunc_u { byte *ptr; void (*func)(void); } vmFunc_t; #define VM_MAGIC 0x12721444 #define VM_MAGIC_VER2 0x12721445 typedef struct { int vmMagic; int instructionCount; int codeOffset; int codeLength; int dataOffset; int dataLength; int litLength; // ( dataLength - litLength ) should be byteswapped on load int bssLength; // zero filled memory appended to datalength //!!! below here is VM_MAGIC_VER2 !!! int jtrgLength; // number of jump table targets } vmHeader_t; typedef struct vm_s vm_t; struct vm_s { unsigned int programStack; // the vm may be recursively entered syscall_t systemCall; byte *dataBase; int *opStack; // pointer to local function stack int instructionCount; intptr_t *instructionPointers; //------------------------------------ const char *name; vmIndex_t index; // for dynamic linked modules void *dllHandle; dllSyscall_t entryPoint; dllSyscall_t dllSyscall; void (*destroy)(vm_t* self); // for interpreted modules //qbool currentlyInterpreting; qbool compiled; vmFunc_t codeBase; unsigned int codeSize; // code + jump targets, needed for proper munmap() unsigned int codeLength; // just for information unsigned int dataMask; unsigned int dataLength; // data segment length unsigned int exactDataLength; // from qvm header unsigned int dataAlloc; // actually allocated unsigned int stackBottom; // if programStack < stackBottom, error int *opStackTop; int numSymbols; vmSymbol_t *symbols; int callLevel; // counts recursive VM_Call int breakFunction; // increment breakCount on function entry to this int breakCount; byte *jumpTableTargets; int numJumpTableTargets; uint32_t crc32sum; qbool forceDataMask; vmInterpret_t type; qbool pr2_references; //int privateFlag; }; extern int vm_debugLevel; extern cvar_t vm_rtChecks; qbool VM_Compile( vm_t *vm, vmHeader_t *header ); int VM_CallCompiled( vm_t *vm, int nargs, int *args ); qbool VM_PrepareInterpreter2( vm_t *vm, vmHeader_t *header ); int VM_CallInterpreted2( vm_t *vm, int nargs, int *args ); vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ); int VM_SymbolToValue( vm_t *vm, const char *symbol ); const char *VM_ValueToSymbol( vm_t *vm, int value ); void VM_LogSyscalls( int *args ); const char *VM_LoadInstructions( const byte *code_pos, int codeLength, int instructionCount, instruction_t *buf ); const char *VM_CheckInstructions( instruction_t *buf, int instructionCount, const byte *jumpTableTargets, int numJumpTableTargets, int dataLength ); //void VM_ReplaceInstructions( vm_t *vm, instruction_t *buf ); #define JUMP (1<<0) typedef struct opcode_info_s { int size; int stack; int nargs; int flags; } opcode_info_t ; extern opcode_info_t ops[ OP_MAX ]; void VM_Init( void ); vm_t *VM_Create( vmIndex_t index, const char* name, syscall_t systemCalls, /*dllSyscall_t dllSyscalls,*/ vmInterpret_t interpret ); // module should be bare: "cgame", not "cgame.dll" or "vm/cgame.qvm" void VM_Free( vm_t *vm ); void VM_Clear(void); void VM_Forced_Unload_Start(void); void VM_Forced_Unload_Done(void); vm_t *VM_Restart( vm_t *vm ); intptr_t QDECL VM_Call( vm_t *vm, int nargs, int callNum, ... ); void VM_Debug( int level ); void VM_CheckBounds( const vm_t *vm, unsigned int address, unsigned int length ); void VM_CheckBounds2( const vm_t *vm, unsigned int addr1, unsigned int addr2, unsigned int length ); #if 1 #define VM_CHECKBOUNDS VM_CheckBounds #define VM_CHECKBOUNDS2 VM_CheckBounds2 #else // for performance evaluation purposes #define VM_CHECKBOUNDS(vm,a,b) #define VM_CHECKBOUNDS2(vm,a,b,c) #endif #endif // VM_LOCAL_H mvdsv-0.35/src/vm_x86.c000066400000000000000000002174411427146041000146720ustar00rootroot00000000000000/* * 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 * * */ #ifdef USE_PR2 #ifdef SERVERONLY #include "qwsvdef.h" #else #include "quakedef.h" #endif #include "vm_local.h" #if ( idx386) || (idx64) #ifdef _WIN32 #include #endif #ifdef __FreeBSD__ #include #endif #ifndef _WIN32 #include // for PROT_ stuff #endif /* need this on NX enabled systems (i386 with PAE kernel or * noexec32=on x86_64) */ #if defined(__linux__) || defined(__FreeBSD__) #define VM_X86_MMAP #endif //#define VM_LOG_SYSCALLS #define JUMP_OPTIMIZE 0 #if JUMP_OPTIMIZE #define NUM_PASSES 7 #else #define NUM_PASSES 3 #endif /* ** -------------------------------------------------------------------------------- ** ** PROCESSOR STUFF ** ** -------------------------------------------------------------------------------- */ #define CPU_FCOM 0x01 #define CPU_MMX 0x02 #define CPU_SSE 0x04 #define CPU_SSE2 0x08 #define CPU_SSE3 0x10 int CPU_Flags = 0; #if defined _MSC_VER static void CPUID( int func, unsigned int *regs ) { #if _MSC_VER >= 1400 __cpuid( regs, func ); #else __asm { mov edi,regs mov eax,[edi] cpuid mov [edi], eax mov [edi+4], ebx mov [edi+8], ecx mov [edi+12], edx } #endif } #else static void CPUID( int func, unsigned int *regs ) { __asm__ __volatile__( "cpuid" : "=a"(regs[0]), "=b"(regs[1]), "=c"(regs[2]), "=d"(regs[3]) : "a"(func) ); } #endif int Sys_GetProcessorId( char *vendor ) { unsigned int regs[4]; // setup initial features #if idx64 CPU_Flags |= CPU_SSE | CPU_SSE2 | CPU_FCOM; #else CPU_Flags = 0; #endif // get CPU feature bits CPUID( 1, regs ); // bit 15 of EDX denotes CMOV/FCMOV/FCOMI existence if ( regs[3] & ( 1 << 15 ) ) CPU_Flags |= CPU_FCOM; // bit 23 of EDX denotes MMX existence if ( regs[3] & ( 1 << 23 ) ) CPU_Flags |= CPU_MMX; // bit 25 of EDX denotes SSE existence if ( regs[3] & ( 1 << 25 ) ) CPU_Flags |= CPU_SSE; // bit 26 of EDX denotes SSE2 existence if ( regs[3] & ( 1 << 26 ) ) CPU_Flags |= CPU_SSE2; // bit 0 of ECX denotes SSE3 existence if ( regs[2] & ( 1 << 0 ) ) CPU_Flags |= CPU_SSE3; if ( vendor ) { #if idx64 strcpy( vendor, "64-bit " ); vendor += strlen( vendor ); #else vendor[0] = '\0'; #endif // get CPU vendor string CPUID( 0, regs ); memcpy( vendor+0, (char*) ®s[1], 4 ); memcpy( vendor+4, (char*) ®s[3], 4 ); memcpy( vendor+8, (char*) ®s[2], 4 ); vendor[12] = '\0'; vendor += 12; if ( CPU_Flags ) { // print features #if !idx64 // do not print default 64-bit features in 32-bit mode strcat( vendor, " w/" ); if ( CPU_Flags & CPU_FCOM ) strcat( vendor, " CMOV" ); if ( CPU_Flags & CPU_MMX ) strcat( vendor, " MMX" ); if ( CPU_Flags & CPU_SSE ) strcat( vendor, " SSE" ); if ( CPU_Flags & CPU_SSE2 ) strcat( vendor, " SSE2" ); #endif //if ( CPU_Flags & CPU_SSE3 ) // strcat( vendor, " SSE3" ); } } return 1; } static void *VM_Alloc_Compiled( vm_t *vm, int codeLength, int tableLength ); static void VM_Destroy_Compiled( vm_t *vm ); /* ------------- eax scratch ebx* dataBase ecx scratch (required for shifts) edx scratch (required for divisions) esi* program stack edi* opstack ebp* current proc stack ( dataBase + program stack ) ------------- rax scratch rbx* dataBase rcx scratch (required for shifts) rdx scratch (required for divisions) rsi* programStack rdi* opstack rbp* current proc stack ( dataBase + program stack ) r8 instructionPointers r9 dataMask r12* systemCall r13* stackBottom r14* opStackTop xmm0 scratch xmm1 scratch xmm2 scratch xmm3 scratch xmm4 scratch xmm5 scratch Windows ABI: you are required to preserve the XMM6-XMM15 registers System V ABI: you don't have to preserve any of the XMM registers Example how data segment will look like during vmMain execution: | .... | |------| vm->programStack -=36 (8+12+16) // set by vmMain | ???? | +0 - unused, reserved for interpreter | ???? | +4 - unused, reserved for interpreter |------- | arg0 | +8 \ | arg4 | +12 | - passed arguments, accessible from subroutines | arg8 | +16 / |------| | loc0 | +20 \ | loc4 | +24 \ - locals, accessible only from local scope | loc8 | +28 / | lc12 | +32 / |------| vm->programStack -= 24 ( 8 + MAX_VMMAIN_CALL_ARGS*4 ) // set by VM_CallCompiled() | ???? | +0 - unused, reserved for interpreter | ???? | +4 - unused, reserved for interpreter | arg0 | +8 \ | arg1 | +12 \ - passed arguments, accessible from vmMain | arg2 | +16 / | arg3 | +20 / |------| vm->programStack = vm->dataMask + 1 // set by VM_Create() jump/call opStack rules: 1) opStack must be 8 before conditional jump 2) opStack must be 4 before unconditional jump 3) opStack must be >=4 before OP_CALL 4) opStack must remain the same after OP_CALL 5) you may not jump in/call locations with opStack != 0 */ #define ISS8(V) ( (V) >= -128 && (V) <= 127 ) #define ISU8(V) ( (V) >= 0 && (V) <= 127 ) #define REWIND(N) { compiledOfs -= (N); instructionOffsets[ ip-1 ] = compiledOfs; }; typedef enum { REG_EAX = 0, REG_ECX } reg_t; typedef enum { LAST_COMMAND_NONE = 0, LAST_COMMAND_MOV_EDI_EAX, LAST_COMMAND_MOV_EDI_CONST, LAST_COMMAND_MOV_EAX_EDI, LAST_COMMAND_MOV_EAX_EDI_CALL, LAST_COMMAND_SUB_DI_4, LAST_COMMAND_SUB_DI_8, LAST_COMMAND_SUB_DI_12, LAST_COMMAND_STORE_FLOAT_EDI, LAST_COMMAND_STORE_FLOAT_EDI_X87, LAST_COMMAND_STORE_FLOAT_EDI_SSE } ELastCommand; typedef enum { FUNC_ENTR = 0, FUNC_CALL, FUNC_SYSC, FUNC_FTOL, FUNC_BCPY, FUNC_NCPY, FUNC_PSOF, FUNC_OSOF, FUNC_BADJ, FUNC_ERRJ, FUNC_DATA, FUNC_LAST } funcx86_t; // macro opcode sequences typedef enum { MOP_UNDEF = OP_MAX, MOP_IGNORE4, MOP_ADD4, MOP_SUB4, MOP_BAND4, MOP_BOR4, MOP_NCPY, } macro_op_t; static byte *code; static int compiledOfs; static int *instructionOffsets; static intptr_t *instructionPointers; static instruction_t *inst = NULL; static instruction_t *ci; static instruction_t *ni; static int fp_cw[2] = { 0x0000, 0x0F7F }; // [0] - current value, [1] - round towards zero static int ip, pass; static int lastConst; static opcode_t pop1; static ELastCommand LastCommand; int funcOffset[FUNC_LAST]; #ifdef DEBUG_VM static int errParam = 0; #endif static void ErrJump( void ) { SV_Error( "program tried to execute code outside VM" ); } static void BadJump( void ) { SV_Error( "program tried to execute code at bad location inside VM" ); } static void BadStack( void ) { SV_Error( "program tried to overflow program stack" ); } static void BadOpStack( void ) { SV_Error( "program tried to overflow opcode stack" ); } static void BadData( void ) { #ifdef DEBUG_VM SV_Error( "program tried to read/write out of data segment at %i", errParam ); #else SV_Error( "program tried to read/write out of data segment" ); #endif } static void (*const errJumpPtr)(void) = ErrJump; static void (*const badJumpPtr)(void) = BadJump; static void (*const badStackPtr)(void) = BadStack; static void (*const badOpStackPtr)(void) = BadOpStack; static void (*const badDataPtr)(void) = BadData; static void VM_FreeBuffers( void ) { // should be freed in reversed allocation order Q_free( instructionOffsets ); Q_free( inst ); } static const inline qbool HasFCOM( void ) { #if idx386 return ( CPU_Flags & CPU_FCOM ); #else return true; // assume idx64 #endif } static const inline qbool HasSSEFP( void ) { #if idx386 return ( CPU_Flags & CPU_SSE ) && ( CPU_Flags & CPU_SSE2 ); #else return true; // assume idx64 #endif } static void Emit1( int v ) { if ( code ) { code[ compiledOfs ] = v; } compiledOfs++; LastCommand = LAST_COMMAND_NONE; } static void Emit4( int v ) { Emit1( v & 255 ); Emit1( ( v >> 8 ) & 255 ); Emit1( ( v >> 16 ) & 255 ); Emit1( ( v >> 24 ) & 255 ); } #if idx64 static void Emit8( int64_t v ) { Emit1( ( v >> 0 ) & 255 ); Emit1( ( v >> 8 ) & 255 ); Emit1( ( v >> 16 ) & 255 ); Emit1( ( v >> 24 ) & 255 ); Emit1( ( v >> 32 ) & 255 ); Emit1( ( v >> 40 ) & 255 ); Emit1( ( v >> 48 ) & 255 ); Emit1( ( v >> 56 ) & 255 ); } #endif static void EmitPtr( const void *ptr ) { #if idx64 Emit8( (intptr_t)ptr ); #else Emit4( (intptr_t)ptr ); #endif } static int Hex( int c ) { if ( c >= '0' && c <= '9' ) { return c - '0'; } if ( c >= 'A' && c <= 'F' ) { return 10 + c - 'A'; } if ( c >= 'a' && c <= 'f' ) { return 10 + c - 'a'; } VM_FreeBuffers(); SV_Error( "Hex: bad char '%c'", c ); return 0; } static void EmitString( const char *string ) { int c1, c2; int v; while ( 1 ) { c1 = string[0]; c2 = string[1]; v = ( Hex( c1 ) << 4 ) | Hex( c2 ); Emit1( v ); if ( !string[2] ) { break; } string += 3; } } static void EmitRexString( const char *string ) { #if idx64 Emit1( 0x48 ); #endif EmitString( string ); } static void EmitAlign( int align ) { int i, n; n = compiledOfs & ( align - 1 ); for ( i = 0; i < n ; i++ ) EmitString( "90" ); // nop } static void EmitCommand( ELastCommand command ) { switch( command ) { case LAST_COMMAND_MOV_EDI_EAX: EmitString( "89 07" ); // mov dword ptr [edi], eax break; case LAST_COMMAND_MOV_EAX_EDI: EmitString( "8B 07" ); // mov eax, dword ptr [edi] break; case LAST_COMMAND_SUB_DI_4: EmitRexString( "83 EF 04" ); // sub edi, 4 break; case LAST_COMMAND_SUB_DI_8: EmitRexString( "83 EF 08" ); // sub edi, 8 break; case LAST_COMMAND_SUB_DI_12: EmitRexString( "83 EF 0C" ); // sub edi, 12 break; case LAST_COMMAND_STORE_FLOAT_EDI_SSE: EmitString( "F3 0F 11 07" ); // movss dword ptr [edi], xmm0 break; case LAST_COMMAND_STORE_FLOAT_EDI_X87: EmitString( "D9 1F" ); // fstp dword ptr [edi] break; case LAST_COMMAND_STORE_FLOAT_EDI: // meta command if ( HasSSEFP() ) { EmitString( "F3 0F 11 07" );// movss dword ptr [edi], xmm0 command = LAST_COMMAND_STORE_FLOAT_EDI_SSE; } else { EmitString( "D9 1F" ); // fstp dword ptr [edi] command = LAST_COMMAND_STORE_FLOAT_EDI_X87; } break; default: break; } LastCommand = command; } static void EmitAddEDI4( vm_t *vm ) { if ( LastCommand == LAST_COMMAND_NONE ) { EmitRexString( "83 C7 04" ); // add edi,4 return; } if ( LastCommand == LAST_COMMAND_SUB_DI_4 ) // sub edi, 4 { #if idx64 REWIND( 4 ); #else REWIND( 3 ); #endif LastCommand = LAST_COMMAND_NONE; return; } if ( LastCommand == LAST_COMMAND_SUB_DI_8 ) // sub edi, 8 { #if idx64 REWIND( 4 ); #else REWIND( 3 ); #endif EmitCommand( LAST_COMMAND_SUB_DI_4 ); return; } if ( LastCommand == LAST_COMMAND_SUB_DI_12 ) // sub edi, 12 { #if idx64 REWIND( 4 ); #else REWIND( 3 ); #endif EmitCommand( LAST_COMMAND_SUB_DI_8 ); return; } EmitRexString( "83 C7 04" ); // add edi,4 } static void EmitMovEAXEDI( vm_t *vm ) { opcode_t pop = pop1; pop1 = OP_UNDEF; if ( LastCommand == LAST_COMMAND_NONE ) { EmitString( "8B 07" ); // mov eax, dword ptr [edi] return; } if ( LastCommand == LAST_COMMAND_MOV_EAX_EDI ) return; if ( LastCommand == LAST_COMMAND_MOV_EAX_EDI_CALL ) return; if ( LastCommand == LAST_COMMAND_MOV_EDI_EAX ) // mov dword ptr [edi], eax { REWIND( 2 ); LastCommand = LAST_COMMAND_NONE; return; } if ( pop == OP_DIVI || pop == OP_DIVU || pop == OP_MULI || pop == OP_MULU || pop == OP_STORE4 || pop == OP_STORE2 || pop == OP_STORE1 ) { return; } if ( LastCommand == LAST_COMMAND_MOV_EDI_CONST ) // mov dword ptr [edi], 0x12345678 { REWIND( 6 ); if ( lastConst == 0 ) { EmitString( "31 C0" ); // xor eax, eax } else { EmitString( "B8" ); // mov eax, 0x12345678 Emit4( lastConst ); } return; } EmitString( "8B 07" ); // mov eax, dword ptr [edi] } void EmitMovECXEDI( vm_t *vm ) { opcode_t pop = pop1; pop1 = OP_UNDEF; if ( LastCommand == LAST_COMMAND_NONE ) { EmitString( "8B 0F" ); // mov ecx, dword ptr [edi] return; } if ( LastCommand == LAST_COMMAND_MOV_EAX_EDI_CALL ) { EmitString( "89 C1" ); // mov ecx, eax return; } if ( LastCommand == LAST_COMMAND_MOV_EAX_EDI ) // mov eax, dword ptr [edi] { REWIND( 2 ); EmitString( "8B 0F" ); // mov ecx, dword ptr [edi] return; } if ( LastCommand == LAST_COMMAND_MOV_EDI_EAX ) // mov dword ptr [edi], eax { REWIND( 2 ); EmitString( "89 C1" ); // mov ecx, eax return; } if (pop == OP_DIVI || pop == OP_DIVU || pop == OP_MULI || pop == OP_MULU || pop == OP_STORE4 || pop == OP_STORE2 || pop == OP_STORE1 ) { EmitString( "89 C1" ); // mov ecx, eax return; } if ( LastCommand == LAST_COMMAND_MOV_EDI_CONST ) // mov dword ptr [edi], 0x12345678 { REWIND( 6 ); EmitString( "B9" ); // mov ecx, 0x12345678 Emit4( lastConst ); return; } EmitString( "8B 0F" ); // mov ecx, dword ptr [edi] } static void EmitCheckReg( vm_t *vm, int reg, int size ) { int n; if ( !( (int)vm_rtChecks.value & 8 ) || vm->forceDataMask ) { if ( vm->forceDataMask ) { if ( reg == REG_EAX ) EmitString( "25" ); // and eax, 0x12345678 else EmitString( "81 E1" ); // and ecx, 0x12345678 Emit4( vm->dataMask ); } return; } #ifdef DEBUG_VM EmitString( "50" ); // push eax EmitRexString( "B8" ); // mov eax, &errParam EmitPtr( &errParam ); EmitString( "C7 00" ); // mov [rax], ip-1 Emit4( ip-1 ); EmitString( "58" ); // pop eax #endif #if idx64 if ( reg == REG_EAX ) EmitString( "44 39 C8" );// cmp eax, r9d // vm->dataMask else EmitString( "44 39 C9" );// cmp ecx, r9d // vm->dataMask #else if ( reg == REG_EAX ) EmitString( "3D" ); // cmp eax, 0x12345678 else EmitString( "81 F9" ); // cmp ecx, 0x12345678 Emit4( vm->dataMask - (size - 1) ); #endif // error reporting EmitString( "0F 87" ); // ja +errorFunction n = funcOffset[FUNC_DATA] - compiledOfs; Emit4( n - 6 ); } static int EmitLoadFloatEDI_SSE( vm_t *vm ) { // movss dword ptr [edi], xmm0 if ( LastCommand == LAST_COMMAND_STORE_FLOAT_EDI_SSE ) { if ( !vm ) return 1; REWIND( 4 ); LastCommand = LAST_COMMAND_NONE; return 1; } EmitString( "F3 0F 10 07" ); // movss xmm0, dword ptr [edi] return 0; } static int EmitLoadFloatEDI_X87( vm_t *vm ) { // fstp dword ptr [edi] if ( LastCommand == LAST_COMMAND_STORE_FLOAT_EDI_X87 ) { if ( !vm ) return 1; REWIND( 2 ); LastCommand = LAST_COMMAND_NONE; return 1; } EmitString( "D9 07" ); // fld dword ptr [edi] return 0; } static int EmitLoadFloatEDI( vm_t *vm ) { if ( HasSSEFP() ) return EmitLoadFloatEDI_SSE( vm ); else return EmitLoadFloatEDI_X87( vm ); } #if JUMP_OPTIMIZE const char *NearJumpStr( int op ) { switch ( op ) { case OP_EQF: case OP_EQ: return "74"; // je case OP_NEF: case OP_NE: return "75"; // jne case OP_LTI: return "7C"; // jl case OP_LEI: return "7E"; // jle case OP_GTI: return "7F"; // jg case OP_GEI: return "7D"; // jge case OP_LTF: case OP_LTU: return "72"; // jb case OP_LEF: case OP_LEU: return "76"; // jbe case OP_GTF: case OP_GTU: return "77"; // ja case OP_GEF: case OP_GEU: return "73"; // jae case OP_JUMP: return "EB"; // jmp //default: // SV_Error( "Bad opcode %i", op ); }; return NULL; } #endif const char *FarJumpStr( int op, int *n ) { switch ( op ) { case OP_EQF: case OP_EQ: *n = 2; return "0F 84"; // je case OP_NEF: case OP_NE: *n = 2; return "0F 85"; // jne case OP_LTI: *n = 2; return "0F 8C"; // jl case OP_LEI: *n = 2; return "0F 8E"; // jle case OP_GTI: *n = 2; return "0F 8F"; // jg case OP_GEI: *n = 2; return "0F 8D"; // jge case OP_LTF: case OP_LTU: *n = 2; return "0F 82"; // jb case OP_LEF: case OP_LEU: *n = 2; return "0F 86"; // jbe case OP_GTF: case OP_GTU: *n = 2; return "0F 87"; // ja case OP_GEF: case OP_GEU: *n = 2; return "0F 83"; // jae case OP_JUMP: *n = 1; return "E9"; // jmp }; return NULL; } void EmitJump( vm_t *vm, instruction_t *i, int op, int addr ) { const char *str; int v, jump_size = 0; v = instructionOffsets[ addr ] - compiledOfs; #if JUMP_OPTIMIZE if ( i->njump ) { // can happen if ( v < -126 || v > 129 ) { str = FarJumpStr( op, &jump_size ); EmitString( str ); Emit4( v - 4 - jump_size ); i->njump = 0; return; } EmitString( NearJumpStr( op ) ); Emit1( v - 2 ); return; } if ( pass >= 2 && pass < NUM_PASSES-2 ) { if ( v >= -126 && v <= 129 ) { EmitString( NearJumpStr( op ) ); Emit1( v - 2 ); i->njump = 1; return; } } #endif str = FarJumpStr( op, &jump_size ); if ( jump_size == 0 ) { SV_Error( "VM_CompileX86 error: %s\n", "bad jump size" ); } else { EmitString( str ); Emit4( v - 4 - jump_size ); } } void EmitFloatJump( vm_t *vm, instruction_t *i, int op, int addr ) { switch ( op ) { case OP_EQF: EmitString( "80 E4 40" ); // and ah,0x40 EmitJump( vm, i, OP_NE, addr ); break; case OP_NEF: EmitString( "80 E4 40" ); // and ah,0x40 EmitJump( vm, i, OP_EQ, addr ); break; case OP_LTF: EmitString( "80 E4 01" ); // and ah,0x01 EmitJump( vm, i, OP_NE, addr ); break; case OP_LEF: EmitString( "80 E4 41" ); // and ah,0x41 EmitJump( vm, i, OP_NE, addr ); break; case OP_GTF: EmitString( "80 E4 41" ); // and ah,0x41 EmitJump( vm, i, OP_EQ, addr ); break; case OP_GEF: EmitString( "80 E4 01" ); // and ah,0x01 EmitJump( vm, i, OP_EQ, addr ); break; }; } static void EmitCallAddr( vm_t *vm, int addr ) { int v; v = instructionOffsets[ addr ] - compiledOfs; EmitString( "E8" ); Emit4( v - 5 ); } static void EmitCallOffset( funcx86_t Func ) { int v; v = funcOffset[ Func ] - compiledOfs; EmitString( "E8" ); // call +funcOffset[ Func ] Emit4( v - 5 ); } #ifdef _WIN32 #define SHADOW_BASE 40 #else // linux/*BSD ABI #define SHADOW_BASE 8 #endif #define PUSH_STACK 32 #define PARAM_STACK 128 static void EmitCallFunc( vm_t *vm ) { static int sysCallOffset = 0; int n; EmitString( "85 C0" ); // test eax, eax EmitString( "7C" ); // jl +offset (SystemCall) Emit1( sysCallOffset ); // will be valid after first pass sysCallOffset = compiledOfs; // jump target range check if ( (int)vm_rtChecks.value & 4 ) { EmitString( "3D" ); // cmp eax, vm->instructionCount Emit4( vm->instructionCount ); EmitString( "0F 83" ); // jae +funcOffset[FUNC_ERRJ] n = funcOffset[FUNC_ERRJ] - compiledOfs; Emit4( n - 6 ); } EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 // save proc base and programStack EmitString( "55" ); // push ebp EmitString( "56" ); // push esi // calling another vm function #if idx64 EmitString( "41 FF 14 C0" ); // call dword ptr [r8+rax*8] #else EmitString( "8D 0C 85" ); // lea ecx, [vm->instructionPointers+eax*4] EmitPtr( instructionPointers ); EmitString( "FF 11" ); // call dword ptr [ecx] #endif // restore proc base and programStack so there is // no need to validate programStack anymore EmitString( "5E" ); // pop esi EmitString( "5D" ); // pop ebp EmitString( "C3" ); // ret sysCallOffset = compiledOfs - sysCallOffset; // systemCall: // convert negative num to system call number // and store right before the first arg EmitString( "F7 D0" ); // not eax EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 // we may jump here from ConstOptimize() also funcOffset[FUNC_SYSC] = compiledOfs; #if idx64 // allocate stack for shadow(win32)+parameters EmitString( "48 81 EC" ); // sub rsp, 200 Emit4( SHADOW_BASE + PUSH_STACK + PARAM_STACK ); // save scratch registers EmitString( "48 8D 54 24" ); // lea rdx, [rsp+SHADOW_BASE] Emit1( SHADOW_BASE ); EmitString( "48 89 32" ); // mov [rdx+00], rsi EmitString( "48 89 7A 08" ); // mov [rdx+08], rdi EmitString( "4C 89 42 10" ); // mov [rdx+16], r8 EmitString( "4C 89 4A 18" ); // mov [rdx+24], r9 // ecx = &int64_params[0] EmitString( "48 8D 4C 24" ); // lea rcx, [rsp+SHADOW_BASE+PUSH_STACK] Emit1( SHADOW_BASE + PUSH_STACK ); // save syscallNum EmitString( "48 89 01" ); // mov [rcx], rax // vm->programStack = programStack - 4; EmitString( "48 BA" ); // mov rdx, &vm->programStack EmitPtr( &vm->programStack ); //EmitString( "8D 46 FC" ); // lea eax, [esi-4] EmitString( "8D 46 F8" ); // lea eax, [esi-8] EmitString( "89 02" ); // mov [rdx], eax //EmitString( "89 32" ); // mov dword ptr [rdx], esi // params = (vm->dataBase + programStack + 8); EmitString( "48 8D 74 33 08" ); // lea rsi, [rbx+rsi+8] // rcx = &int64_params[1] EmitString( "48 83 C1 08" ); // add rcx, 8 // dest_params[1-15] = params[1-15]; EmitString( "31 D2" ); // xor edx, edx // loop EmitString( "48 63 04 96" ); // movsxd rax, dword [rsi+rdx*4] EmitString( "48 89 04 D1" ); // mov qword ptr[rcx+rdx*8], rax EmitString( "48 83 C2 01" ); // add rdx, 1 EmitString( "48 83 FA" ); // cmp rdx, 15 Emit1( (PARAM_STACK/8) - 1 ); EmitString( "7C EE" ); // jl -18 #ifdef _WIN32 // rcx = &int64_params[0] EmitString( "48 83 E9 08" ); // sub rcx, 8 #else // linux/*BSD ABI // rdi = &int64_params[0] EmitString( "48 8D 79 F8" ); // lea rdi, [rcx-8] #endif // currentVm->systemCall( param ); EmitString( "41 FF 14 24" ); // call qword [r12] // restore registers EmitString( "48 8D 54 24" ); // lea rdx, [rsp+SHADOW_BASE] Emit1( SHADOW_BASE ); EmitString( "48 8B 32" ); // mov rsi, [rdx+00] EmitString( "48 8B 7A 08" ); // mov rdi, [rdx+08] EmitString( "4C 8B 42 10" ); // mov r8, [rdx+16] EmitString( "4C 8B 4A 18" ); // mov r9, [rdx+24] // we added the return value: *(opstack+1) = eax EmitAddEDI4( vm ); // add edi, 4 EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax // return stack EmitString( "48 81 C4" ); // add rsp, 200 Emit4( SHADOW_BASE + PUSH_STACK + PARAM_STACK ); EmitRexString( "8D 2C 33" ); // lea rbp, [rbx+rsi] EmitString( "C3" ); // ret #else // i386 // params = (int *)((byte *)currentVM->dataBase + programStack + 4); EmitString( "8D 4D 04" ); // lea ecx, [ebp+4] // function prologue EmitString( "55" ); // push ebp EmitRexString( "89 E5" ); // mov ebp, esp EmitRexString( "83 EC 04" ); // sub esp, 4 // align stack before call EmitRexString( "83 E4 F0" ); // and esp, -16 // ABI note: esi/edi must not change during call! // currentVM->programStack = programStack - 4; EmitString( "8D 56 FC" ); // lea edx, [esi-4] EmitString( "89 15" ); // mov [&vm->programStack], edx EmitPtr( &vm->programStack ); // params[0] = syscallNum EmitString( "89 01" ); // mov [ecx], eax // cdecl - set params EmitString( "89 0C 24" ); // mov [esp], ecx // currentVm->systemCall( param ); EmitString( "FF 15" ); // call dword ptr [¤tVM->systemCall] EmitPtr( &vm->systemCall ); // we added the return value: *(opstack+1) = eax #if 0 EmitAddEDI4( vm ); // add edi, 4 EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov [edi], eax #else // break dependency from edi value? EmitString( "89 47 04" ); // mov [edi+4], eax EmitAddEDI4( vm ); // add edi, 4 #endif // function epilogue EmitRexString( "89 EC" ); // mov esp, ebp EmitString( "5D" ); // pop ebp EmitString( "C3" ); // ret #endif } static void EmitFTOLFunc( vm_t *vm ) { EmitRexString( "B8" ); // mov eax, &fp_cw[0] EmitPtr( &fp_cw[0] ); EmitString( "9B D9 38" ); // fnstcw word ptr [eax] EmitString( "D9 68 04" ); // fldcw word ptr [eax+4] EmitString( "DB 1F" ); // fistp dword ptr [edi] EmitString( "D9 28" ); // fldcw word ptr [eax] EmitString( "C3" ); // ret } static void EmitBCPYFunc( vm_t *vm ) { // FIXME: range check EmitString( "56" ); // push esi EmitString( "57" ); // push edi EmitString( "8B 37" ); // mov esi,[edi] EmitString( "8B 7F FC" ); // mov edi,[edi-4] EmitString( "B8" ); // mov eax, datamask Emit4( vm->dataMask ); EmitString( "21 C6" ); // and esi, eax EmitString( "21 C7" ); // and edi, eax #if idx64 EmitString( "48 01 DE" ); // add rsi, rbx EmitString( "48 01 DF" ); // add rdi, rbx #else EmitString( "03 F3" ); // add esi, ebx EmitString( "03 FB" ); // add edi, ebx #endif EmitString( "F3 A5" ); // rep movsd EmitString( "5F" ); // pop edi EmitString( "5E" ); // pop esi EmitCommand( LAST_COMMAND_SUB_DI_8 ); // sub edi, 8 EmitString( "C3" ); // ret } static void EmitPSOFFunc( vm_t *vm ) { EmitRexString( "B8" ); // mov eax, badStackPtr EmitPtr( &badStackPtr ); EmitString( "FF 10" ); // call [eax] EmitString( "C3" ); // ret } static void EmitOSOFFunc( vm_t *vm ) { EmitRexString( "B8" ); // mov eax, badOptackPtr EmitPtr( &badOpStackPtr ); EmitString( "FF 10" ); // call [eax] EmitString( "C3" ); // ret } static void EmitBADJFunc( vm_t *vm ) { EmitRexString( "B8" ); // mov eax, badJumpPtr EmitPtr( &badJumpPtr ); EmitString( "FF 10" ); // call [eax] EmitString( "C3" ); // ret } static void EmitERRJFunc( vm_t *vm ) { EmitRexString( "B8" ); // mov eax, errJumpPtr EmitPtr( &errJumpPtr ); EmitString( "FF 10" ); // call [eax] EmitString( "C3" ); // ret } static void EmitDATAFunc( vm_t *vm ) { EmitRexString( "B8" ); // mov eax, badDataPtr EmitPtr( &badDataPtr ); EmitString( "FF 10" ); // call [eax] EmitString( "C3" ); // ret } static void EmitNCPYFunc( vm_t *vm ) { static int Lend, Lcopy, Lpadz, Lpop0, Lpop1; // jump labels int n; //EmitString( "8B 4D 10" ); // mov ecx, dword ptr [ebp+16] // counter EmitString( "89 C1" ); // mov ecx, eax // get cached value from previous OP_ARG instruction EmitString( "85 C9" ); // test ecx, ecx EmitString( "74" ); // je +Lend Emit1( Lend ); Lend = compiledOfs; EmitString( "57" ); // push edi EmitString( "8B 55 0C" ); // mov edx, dword ptr [ebp+12] // source EmitString( "8B 7D 08" ); // mov edi, dword ptr [ebp+08] // destination EmitRexString( "01 DA" ); // add edx, ebx // + vm->dataBase #if 0 if ( vm->forceDataMask ) { #ifdef idx64 EmitString( "44 21 CF" ); // and edi, r9d #else EmitString( "81 E7" ); // and edi, vm->dataMask Emit4( vm->dataMask ); #endif EmitRexString( "01 DF" ); // add edi, ebx // + vm->dataBase } else #endif if ( (int)vm_rtChecks.value & 8 ) // security checks { EmitString( "89 F8" ); // mov eax, edi EmitString( "09 C8" ); // or eax, ecx EmitString( "3D" ); // cmp eax, vm->dataMask Emit4( vm->dataMask ); EmitString( "0F 87" ); // ja +errorFunction n = funcOffset[FUNC_DATA] - compiledOfs; Emit4( n - 6 ); EmitString( "8D 04 0F" ); // lea eax, dword ptr [edi + ecx] EmitRexString( "01 DF" ); // add edi, ebx // + vm->dataBase EmitString( "3D" ); // cmp eax, vm->dataMask Emit4( vm->dataMask ); EmitString( "0F 87" ); // ja +errorFunction n = funcOffset[FUNC_DATA] - compiledOfs; Emit4( n - 6 ); } else { EmitRexString( "01 DF" ); // add edi, ebx // + vm->dataBase } Lcopy = compiledOfs - Lcopy; EmitString( "8A 02" ); // mov al, dword ptr [edx] EmitString( "88 07" ); // mov dword ptr [edi], al EmitRexString( "83 C2 01" );// add edx, 1 EmitRexString( "83 C7 01" );// add edi, 1 EmitRexString( "84 C0" ); // test al, al EmitString( "74" ); // je +Lpadz Emit1( Lpadz ); Lpadz = compiledOfs; EmitString( "83 E9 01" ); // sub ecx, 1 EmitString( "75" ); // jne +Lcopy Emit1( Lcopy ); Lcopy = compiledOfs; EmitString( "5F" ); // pop edi EmitString( "C3" ); // ret Lpadz = compiledOfs - Lpadz; EmitString( "85 C9" ); // test ecx, ecx EmitString( "74" ); // je +Lpop0 Emit1( Lpop0 ); Lpop0 = compiledOfs; #if 0 // zero only one char EmitString( "31 C0" ); // xor eax, eax EmitString( "88 07" ); // mov dword ptr [edi], al #else // zero all remaining chars EmitString( "83 E9 01" ); // sub ecx, 1 EmitString( "74" ); // je +Lpop1 Emit1( Lpop1 ); Lpop1 = compiledOfs; EmitString( "89 CA" ); // mov edx, ecx EmitString( "C1 E9 02" ); // shr ecx, 2 EmitString( "31 C0" ); // xor eax, eax EmitString( "83 E2 03" ); // and edx, 3 EmitString( "F3 AB" ); // rep stosd EmitString( "89 D1" ); // mov ecx, edx //EmitString( "83 E1 03" ); // and ecx, 3 EmitString( "F3 AA" ); // rep stosb Lpop1 = compiledOfs - Lpop1; #endif Lpop0 = compiledOfs - Lpop0; EmitString( "5F" ); // pop edi Lend = compiledOfs - Lend; EmitString( "C3" ); // ret } /* ================= EmitFCalcEDI ================= */ static void EmitFCalcEDI( int op ) { switch ( op ) { case OP_ADDF: EmitString( "D8 07" ); break; // fadd dword ptr [edi] case OP_SUBF: EmitString( "D8 27" ); break; // fsub dword ptr [edi] case OP_MULF: EmitString( "D8 0F" ); break; // fmul dword ptr [edi] case OP_DIVF: EmitString( "D8 37" ); break; // fdiv dword ptr [edi] default: SV_Error( "bad float op" ); break; }; } /* ================= EmitFCalcPop ================= */ static void EmitFCalcPop( int op ) { switch ( op ) { case OP_ADDF: EmitString( "DE C1" ); break; // faddp case OP_SUBF: EmitString( "DE E9" ); break; // fsubp case OP_MULF: EmitString( "DE C9" ); break; // fmulp case OP_DIVF: EmitString( "DE F9" ); break; // fdivp default: SV_Error( "bad opcode %02x", op ); break; }; } /* ================= CommuteFloatOp ================= */ static int CommuteFloatOp( int op ) { switch ( op ) { case OP_LEF: return OP_GEF; case OP_LTF: return OP_GTF; case OP_GEF: return OP_LEF; case OP_GTF: return OP_LTF; default: return op; } } /* ================= ConstOptimize ================= */ static qbool ConstOptimize( vm_t *vm ) { int v; int op1; qbool sign_extend; op1 = ni->op; switch ( op1 ) { case OP_LOAD4: EmitAddEDI4( vm ); if ( ISS8( ci->value ) ) { EmitString( "8B 43" ); // mov eax, dword ptr [ebx+0x7F] Emit1( ci->value ); } else { EmitString( "8B 83" ); // mov eax, dword ptr [ebx+0x12345678] Emit4( ci->value ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax ip += 1; return true; case OP_LOAD2: EmitAddEDI4( vm ); sign_extend = ( (ci+2)->op == OP_SEX16 ); if ( ISS8( ci->value ) ) { if ( sign_extend ) { EmitString( "0F BF 43" ); // movsx eax, word ptr [ebx+0x7F] ip += 1; } else { EmitString( "0F B7 43" ); // movzx eax, word ptr [ebx+0x7F] } Emit1( ci->value ); } else { if ( sign_extend ) { EmitString( "0F BF 83" ); // movsx eax, word ptr [ebx+0x12345678] ip += 1; } else { EmitString( "0F B7 83" ); // movzx eax, word ptr [ebx+0x12345678] } Emit4( ci->value ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax ip += 1; return true; case OP_LOAD1: EmitAddEDI4( vm ); sign_extend = ( (ci+2)->op == OP_SEX8 ); if ( ISS8( ci->value ) ) { if ( sign_extend ) { EmitString( "0F BE 43" ); // movsx eax, byte ptr [ebx+0x7F] ip += 1; } else { EmitString( "0F B6 43" ); // movzx eax, byte ptr [ebx+0x7F] } Emit1( ci->value ); } else { if ( sign_extend ) { EmitString( "0F BE 83" ); // movsx eax, word ptr [ebx+0x12345678] ip += 1; } else { EmitString( "0F B6 83" ); // movzx eax, word ptr [ebx+0x12345678] } Emit4( ci->value ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax ip += 1; return true; case OP_STORE4: EmitMovEAXEDI( vm ); if ( !ci->value ) { EmitString( "31 C9" ); // xor ecx, ecx } else { EmitString( "B9" ); // mov ecx, 0x12345678 Emit4( ci->value ); } EmitCheckReg( vm, REG_EAX, 4 ); EmitString( "89 0C 03" ); // mov dword ptr [ebx + eax], ecx EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 ip += 1; return true; case OP_STORE2: EmitMovEAXEDI( vm ); if ( !ci->value ) { EmitString( "31 C9" ); // xor ecx, ecx } else { EmitString( "B9" ); // mov ecx, 0x12345678 Emit4( ci->value ); } EmitCheckReg( vm, REG_EAX, 2 ); EmitString( "66 89 0C 03" ); // mov word ptr [ebx + eax], cx EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 ip += 1; return true; case OP_STORE1: EmitMovEAXEDI( vm ); if ( !ci->value ) { EmitString( "31 C9" ); // xor ecx, ecx } else { EmitString( "B9" ); // mov ecx, 0x12345678 Emit4( ci->value ); } EmitCheckReg( vm, REG_EAX, 1 ); EmitString( "88 0C 03" ); // mov byte ptr [ebx + eax], cl EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 ip += 1; return true; case OP_ADD: v = ci->value; EmitMovEAXEDI( vm ); if ( ISS8( v ) ) { EmitString( "83 C0" ); // add eax, 0x7F Emit1( v ); } else { EmitString( "05" ); // add eax, 0x12345678 Emit4( v ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); ip += 1; // OP_ADD return true; case OP_SUB: v = ci->value; EmitMovEAXEDI( vm ); if ( ISS8( v ) ) { EmitString( "83 E8" ); // sub eax, 0x7F Emit1( v ); } else { EmitString( "2D" ); // sub eax, 0x12345678 Emit4( v ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); ip += 1; return true; case OP_MULI: v = ci->value; EmitMovEAXEDI( vm ); if ( ISS8( v ) ) { EmitString( "6B C0" ); // imul eax, 0x7F Emit1( v ); } else { EmitString( "69 C0" ); // imul eax, 0x12345678 Emit4( v ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); ip += 1; return true; case OP_MULF: case OP_DIVF: case OP_ADDF: case OP_SUBF: v = ci->value; EmitLoadFloatEDI( vm ); if ( HasSSEFP() ) { EmitString( "C7 45 00" ); // mov dword ptr [ebp], v Emit4( v ); EmitString( "F3 0F 10 4D 00" ); // movss xmm1, dword ptr [ebp] switch( op1 ) { case OP_ADDF: EmitString( "0F 58 C1" ); break; // addps xmm0, xmm1 case OP_SUBF: EmitString( "0F 5C C1" ); break; // subps xmm0, xmm1 case OP_MULF: EmitString( "0F 59 C1" ); break; // mulps xmm0, xmm1 case OP_DIVF: EmitString( "0F 5E C1" ); break; // divps xmm0, xmm1 } } else { EmitString( "C7 45 00" ); // mov dword ptr [ebp], 0x12345678 Emit4( v ); EmitString( "D9 45 00" ); // fld dword ptr [ebp] EmitFCalcPop( op1 ); // fmulp/fdivp/faddp/fsubp } EmitCommand( LAST_COMMAND_STORE_FLOAT_EDI ); ip +=1; return true; case OP_LSH: v = ci->value; if ( v < 0 || v > 31 ) break; EmitMovEAXEDI( vm ); EmitString( "C1 E0" ); // shl eax, 0x12 Emit1( v ); EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); ip += 1; // OP_LSH return true; case OP_RSHI: v = ci->value; if ( v < 0 || v > 31 ) break; EmitMovEAXEDI( vm ); EmitString( "C1 F8" ); // sar eax, 0x12 Emit1( v ); EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); ip += 1; return true; case OP_RSHU: v = ci->value; if ( v < 0 || v > 31 ) break; EmitMovEAXEDI( vm ); EmitString( "C1 E8" ); // shr eax, 0x12 Emit1( v ); EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); ip += 1; return true; case OP_BAND: v = ci->value; EmitMovEAXEDI( vm ); if ( ISU8( v ) ) { EmitString( "83 E0" ); // and eax, 0x7F Emit1( v ); } else { EmitString( "25" ); // and eax, 0x12345678 Emit4( v ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); ip += 1; return true; case OP_BOR: v = ci->value; EmitMovEAXEDI( vm ); if ( ISU8( v ) ) { EmitString( "83 C8" ); // or eax, 0x7F Emit1( v ); } else { EmitString( "0D" ); // or eax, 0x12345678 Emit4( v ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); ip += 1; return true; case OP_BXOR: v = ci->value; EmitMovEAXEDI( vm ); if ( ISU8( v ) ) { EmitString( "83 F0" ); // xor eax, 0x7F Emit1( v ); } else { EmitString( "35" ); // xor eax, 0x12345678 Emit4( v ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); ip += 1; return true; case OP_JUMP: EmitJump( vm, ni, ni->op, ci->value ); ip += 1; // OP_JUMP return true; case OP_CALL: #ifdef VM_LOG_SYSCALLS // [dataBase + programStack + 0] = ip; EmitString( "C7 45 00" ); // mov dword ptr [ebp], 0x12345678 Emit4( ip ); #endif v = ci->value; // try to inline some syscalls if ( HasSSEFP() && v == ~g_sqrt ) { // inline SSE implementation of sin/cos is too problematic... EmitString( "F3 0F 10 45 08" ); // movss xmm0, dword ptr [ebp + 8] EmitAddEDI4( vm ); EmitString( "F3 0F 51 C0" ); // sqrtss xmm0, xmm0 EmitCommand( LAST_COMMAND_STORE_FLOAT_EDI ); ip += 1; return true; } else if ( v == ~g_sin || v == ~g_cos || v == ~g_sqrt ) { EmitString( "D9 45 08" ); // fld dword ptr [ebp + 8] switch ( v ) { case ~g_sqrt: EmitString( "D9 FA" ); break; // fsqrt case ~g_sin: EmitString( "D9 FE" ); break; // fsin case ~g_cos: EmitString( "D9 FF" ); break; // fcos } EmitAddEDI4( vm ); // add edi, 4 EmitCommand( LAST_COMMAND_STORE_FLOAT_EDI_X87 );// fstp dword ptr[edi] ip += 1; return true; } if ( v < 0 ) // syscall { EmitString( "B8" ); // mov eax, 0x12345678 Emit4( ~v ); EmitCallOffset( FUNC_SYSC ); LastCommand = LAST_COMMAND_MOV_EAX_EDI_CALL; ip += 1; // OP_CALL return true; } EmitString( "55" ); // push ebp EmitString( "56" ); // push rsi EmitString( "53" ); // push rbx EmitCallAddr( vm, v ); // call +addr EmitString( "5B" ); // pop rbx EmitString( "5E" ); // pop rsi EmitString( "5D" ); // pop ebp ip += 1; // OP_CALL return true; case OP_EQF: case OP_NEF: case OP_LTF: case OP_LEF: case OP_GTF: case OP_GEF: if ( !HasFCOM() ) return false; EmitLoadFloatEDI( vm ); EmitCommand( LAST_COMMAND_SUB_DI_4 ); v = ci->value; if ( HasSSEFP() ) { if ( v == 0 ) { EmitString( "0F 57 C9" ); // xorps xmm1, xmm1 } else { EmitString( "C7 45 00" ); // mov dword ptr [ebp], v Emit4( v ); EmitString( "F3 0F 10 4D 00" ); // movss xmm1, dword ptr [ebp] } EmitString( "0F 2F C1" ); // comiss xmm0, xmm1 EmitJump( vm, ni, ni->op, ni->value ); } else { if ( v == 0 ) { EmitString( "D9 EE" ); // fldz } else { EmitString( "C7 45 00" ); // mov [ebp], 0x12345678 Emit4( v ); EmitString( "D9 45 00" ); // fld dword ptr [ebp] } EmitString( "DF E9" ); // fucomip EmitString( "DD D8" ); // fstp st(0) EmitJump( vm, ni, CommuteFloatOp( ni->op ), ni->value ); } ip +=1; return true; case OP_EQ: case OP_NE: case OP_GEI: case OP_GTI: case OP_GTU: case OP_GEU: case OP_LTU: case OP_LEU: case OP_LEI: case OP_LTI: EmitMovEAXEDI( vm ); EmitCommand( LAST_COMMAND_SUB_DI_4 ); v = ci->value; if ( v == 0 && ( op1 == OP_EQ || op1 == OP_NE ) ) { EmitString( "85 C0" ); // test eax, eax } else { if ( ISS8( v ) ) { EmitString( "83 F8" ); // cmp eax, 0x7F Emit1( v ); } else { EmitString( "3D" ); // cmp eax, 0xFFFFFFFF Emit4( v ); } } EmitJump( vm, ni, ni->op, ni->value ); ip += 1; return true; default: break; } return false; } /* ================= VM_FindMOps Search for known macro-op sequences ================= */ static void VM_FindMOps( instruction_t *buf, int instructionCount ) { int n, v, op0; instruction_t *i; i = buf; n = 0; while ( n < instructionCount ) { op0 = i->op; if ( op0 == OP_LOCAL ) { // OP_LOCAL + OP_LOCAL + OP_LOAD4 + OP_CONST + OP_XXX + OP_STORE4 if ( (i+1)->op == OP_LOCAL && i->value == (i+1)->value && (i+2)->op == OP_LOAD4 && (i+3)->op == OP_CONST && (i+4)->op != OP_UNDEF && (i+5)->op == OP_STORE4 ) { v = (i+4)->op; if ( v == OP_ADD ) { i->op = MOP_ADD4; i += 6; n += 6; continue; } if ( v == OP_SUB ) { i->op = MOP_SUB4; i += 6; n += 6; continue; } if ( v == OP_BAND ) { i->op = MOP_BAND4; i += 6; n += 6; continue; } if ( v == OP_BOR ) { i->op = MOP_BOR4; i += 6; n += 6; continue; } } // skip useless sequences if ( (i+1)->op == OP_LOCAL && (i+0)->value == (i+1)->value && (i+2)->op == OP_LOAD4 && (i+3)->op == OP_STORE4 ) { i->op = MOP_IGNORE4; i += 4; n += 4; continue; } } else if ( op0 == OP_CONST && (i+1)->op == OP_CALL && (i+2)->op == OP_POP && i >= buf+6 && (i-1)->op == OP_ARG && !i->jused ) { // some void function( arg1, arg2, arg3 ) if ( i->value == ~g_strlcpy ) { i->op = MOP_NCPY; i += 3; n += 3; continue; } } i++; n++; } } /* ================= EmitMOPs ================= */ static qbool EmitMOPs( vm_t *vm, int op ) { int v, n; switch ( op ) { //[local] += CONST case MOP_ADD4: n = inst[ip+2].value; v = ci->value; // local variable address if ( ISS8( n ) ) { if ( ISS8( v ) ) { EmitString( "83 45" ); // add dword ptr [ebp + 0x7F], 0x12 Emit1( v ); Emit1( n ); } else { EmitString( "83 85" ); // add dword ptr [ebp + 0x12345678], 0x12 Emit4( v ); Emit1( n ); } } else { if ( ISS8( v ) ) { EmitString( "81 45" ); // add dword ptr [ebp + 0x7F], 0x12345678 Emit1( v ); Emit4( n ); } else { EmitString( "81 85" ); // add dword ptr [ebp + 0x12345678], 0x12345678 Emit4( v ); Emit4( n ); } } ip += 5; return true; //[local] -= CONST case MOP_SUB4: n = inst[ip+2].value; v = ci->value; // local variable address if ( ISS8( n ) ) { if ( ISS8( v ) ) { EmitString( "83 6D" ); // sub dword ptr [ebp + 0x7F], 0x12 Emit1( v ); Emit1( n ); } else { EmitString( "83 AD" ); // sub dword ptr [ebp + 0x12345678], 0x12 Emit4( v ); Emit1( n ); } } else { if ( ISS8( v ) ) { EmitString( "81 6D" ); // sub dword ptr [ebp + 0x7F], 0x12345678 Emit1( v ); Emit4( n ); } else { EmitString( "81 AD" ); // sub dword ptr[esi+0x12345678], 0x12345678 Emit4( v ); Emit4( n ); } } ip += 5; return true; //[local] &= CONST case MOP_BAND4: n = inst[ip+2].value; v = ci->value; // local variable address if ( ISS8( n ) ) { if ( ISS8( v ) ) { EmitString( "83 65" ); // and dword ptr [ebp + 0x7F], 0x12 Emit1( v ); Emit1( n ); } else { EmitString( "83 A5" ); // and dword ptr [ebp + 0x12345678], 0x12 Emit4( v ); Emit1( n ); } } else { if ( ISS8( v ) ) { EmitString( "81 65" ); // and dword ptr [ebp + 0x7F], 0x12345678 Emit1( v ); Emit4( n ); } else { EmitString( "81 A5" ); // and dword ptr [ebp + 0x12345678], 0x12345678 Emit4( v ); Emit4( n ); } } ip += 5; return true; //[local] |= CONST case MOP_BOR4: n = inst[ip+2].value; v = ci->value; // local variable address if ( ISS8( n ) ) { if ( ISS8( v ) ) { EmitString( "83 4D" ); // or dword ptr [ebp + 0x7F], 0x12 Emit1( v ); Emit1( n ); } else { EmitString( "83 8D" ); // or dword ptr [ebp + 0x12345678], 0x12 Emit4( v ); Emit1( n ); } } else { if ( ISS8( v ) ) { EmitString( "81 4D" ); // or dword ptr [ebp + 0x7F], 0x12345678 Emit1( v ); Emit4( n ); } else { EmitString( "81 8D" ); // or dword ptr [ebp + 0x12345678], 0x12345678 Emit4( v ); Emit4( n ); } } ip += 5; return true; // [local] = [local] case MOP_IGNORE4: ip += 3; return true; // const + call + pop case MOP_NCPY: EmitCallOffset( FUNC_NCPY ); ip += 2; return true; }; return false; } /* ================= VM_Compile ================= */ qbool VM_Compile( vm_t *vm, vmHeader_t *header ) { const char *errMsg; int instructionCount; int proc_base; int proc_len; int i, n, v; qbool wantres; Sys_GetProcessorId(NULL); inst = (instruction_t*)Q_malloc( (header->instructionCount + 8 ) * sizeof( instruction_t ) ); instructionOffsets = (int*)Q_malloc( header->instructionCount * sizeof( int ) ); errMsg = VM_LoadInstructions( (byte *) header + header->codeOffset, header->codeLength, header->instructionCount, inst ); if ( !errMsg ) { errMsg = VM_CheckInstructions( inst, vm->instructionCount, vm->jumpTableTargets, vm->numJumpTableTargets, vm->exactDataLength ); } if ( errMsg ) { VM_FreeBuffers(); Con_Printf( "VM_CompileX86 error: %s\n", errMsg ); return false; } //VM_ReplaceInstructions( vm, inst ); VM_FindMOps( inst, vm->instructionCount ); code = NULL; // we will allocate memory later, after last defined pass instructionPointers = NULL; memset( funcOffset, 0, sizeof( funcOffset ) ); instructionCount = header->instructionCount; for( pass = 0; pass < NUM_PASSES; pass++ ) { __compile: pop1 = OP_UNDEF; lastConst = 0; // translate all instructions ip = 0; compiledOfs = 0; LastCommand = LAST_COMMAND_NONE; proc_base = -1; proc_len = 0; #if idx64 EmitString( "53" ); // push rbx EmitString( "56" ); // push rsi EmitString( "57" ); // push rdi EmitString( "55" ); // push rbp EmitString( "41 54" ); // push r12 EmitString( "41 55" ); // push r13 EmitString( "41 56" ); // push r14 EmitString( "41 57" ); // push r15 EmitRexString( "BB" ); // mov rbx, vm->dataBase EmitPtr( vm->dataBase ); EmitString( "49 B8" ); // mov r8, vm->instructionPointers EmitPtr( instructionPointers ); EmitString( "49 C7 C1" ); // mov r9, vm->dataMask Emit4( vm->dataMask ); EmitString( "49 BC" ); // mov r12, vm->systemCall EmitPtr( &vm->systemCall ); EmitString( "49 C7 C5" ); // mov r13, vm->stackBottom Emit4( vm->stackBottom ); EmitRexString( "B8" ); // mov rax, &vm->programStack EmitPtr( &vm->programStack ); EmitString( "8B 30" ); // mov esi, [rax] EmitRexString( "B8" ); // mov rax, &vm->opStack EmitPtr( &vm->opStack ); EmitRexString( "8B 38" ); // mov rdi, [rax] EmitRexString( "B8" ); // mov rax, &vm->opStackTop EmitPtr( &vm->opStackTop ); EmitString( "4C 8B 30" ); // mov r14, [rax] #else EmitString( "60" ); // pushad EmitRexString( "BB" ); // mov ebx, vm->dataBase EmitPtr( vm->dataBase ); EmitString( "8B 35" ); // mov esi, [vm->programStack] EmitPtr( &vm->programStack ); EmitString( "8B 3D" ); // mov edi, [vm->opStack] EmitPtr( &vm->opStack ); #endif EmitCallOffset( FUNC_ENTR ); #if idx64 #ifdef DEBUG_VM EmitRexString( "B8" ); // mov rax, &vm->programStack EmitPtr( &vm->programStack ); EmitString( "89 30" ); // mov [rax], esi #endif EmitRexString( "B8" ); // mov rax, &vm->opStack EmitPtr( &vm->opStack ); EmitRexString( "89 38" ); // mov [rax], rdi EmitString( "41 5F" ); // pop r15 EmitString( "41 5E" ); // pop r14 EmitString( "41 5D" ); // pop r13 EmitString( "41 5C" ); // pop r12 EmitString( "5D" ); // pop rbp EmitString( "5F" ); // pop rdi EmitString( "5E" ); // pop rsi EmitString( "5B" ); // pop rbx #else #ifdef DEBUG_VM EmitString( "89 35" ); // [vm->programStack], esi EmitPtr( &vm->programStack ); #endif EmitString( "89 3D" ); // [vm->opStack], edi EmitPtr( &vm->opStack ); EmitString( "61" ); // popad #endif EmitString( "C3" ); // ret EmitAlign( 4 ); // main function entry offset funcOffset[FUNC_ENTR] = compiledOfs; while ( ip < instructionCount ) { instructionOffsets[ ip ] = compiledOfs; ci = &inst[ ip ]; ni = &inst[ ip + 1 ]; ip++; if ( ci->jused ) { LastCommand = LAST_COMMAND_NONE; pop1 = OP_UNDEF; } switch ( ci->op ) { case OP_UNDEF: case OP_IGNORE: break; case OP_BREAK: EmitString( "CC" ); // int 3 break; case OP_ENTER: v = ci->value; if ( ISU8( v ) ) { EmitString( "83 EE" ); // sub esi, 0x12 Emit1( v ); } else { EmitString( "81 EE" ); // sub esi, 0x12345678 Emit4( v ); } // locate endproc for ( n = -1, i = ip + 1; i < instructionCount; i++ ) { if ( inst[ i ].op == OP_PUSH && inst[ i + 1 ].op == OP_LEAVE ) { n = i; break; } } // should never happen because equal check in VM_LoadInstructions() but anyway if ( n == -1 ) { VM_FreeBuffers(); Con_Printf( "VM_CompileX86 error: %s\n", "missing proc end" ); return false; } proc_base = ip + 1; proc_len = n - proc_base + 1 ; // programStack overflow check if ( (int)vm_rtChecks.value & 1 ) { #if idx64 EmitString( "4C 39 EE" ); // cmp rsi, r13 #else EmitString( "81 FE" ); // cmp esi, vm->stackBottom Emit4( vm->stackBottom ); #endif EmitString( "0F 82" ); // jb +funcOffset[FUNC_PSOF] n = funcOffset[FUNC_PSOF] - compiledOfs; Emit4( n - 6 ); } // opStack overflow check if ( (int)vm_rtChecks.value & 2 ) { if ( ISU8( ci->opStack ) ) { EmitRexString( "8D 47" ); // lea eax, [edi+0x7F] Emit1( ci->opStack ); } else { EmitRexString( "8D 87" ); // lea eax, [edi+0x12345678] Emit4( ci->opStack ); } #if idx64 EmitString( "4C 39 F0" ); // cmp rax, r14 #else EmitString( "3B 05" ); // cmp eax, [&vm->opStackTop] EmitPtr( &vm->opStackTop ); #endif EmitString( "0F 87" ); // ja +funcOffset[FUNC_OSOF] n = funcOffset[FUNC_OSOF] - compiledOfs; Emit4( n - 6 ); } EmitRexString( "8D 2C 33" ); // lea ebp, [ebx+esi] break; case OP_CONST: // we can safely perform optimizations only in case if // we are 100% sure that next instruction is not a jump label if ( !ni->jused && ConstOptimize( vm ) ) break; EmitAddEDI4( vm ); // add edi, 4 EmitString( "C7 07" ); // mov dword ptr [edi], 0x12345678 lastConst = ci->value; Emit4( lastConst ); LastCommand = LAST_COMMAND_MOV_EDI_CONST; break; case OP_LOCAL: // optimization: merge OP_LOCAL + OP_LOAD4 if ( ni->op == OP_LOAD4 ) { EmitAddEDI4( vm ); // add edi, 4 v = ci->value; if ( ISU8( v ) ) { EmitString( "8B 45" ); // mov eax, dword ptr [ebp + 0x7F] Emit1( v ); } else { EmitString( "8B 85" ); // mov eax, dword ptr [ebp + 0x12345678] Emit4( v ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax ip++; break; } // optimization: merge OP_LOCAL + OP_LOAD2 if ( ni->op == OP_LOAD2 ) { EmitAddEDI4( vm ); // add edi, 4 v = ci->value; if ( ISU8( v ) ) { EmitString( "0F B7 45" ); // movzx eax, word ptr [ebp + 0x7F] Emit1( v ); } else { EmitString( "0F B7 85" ); // movzx eax, word ptr [ebp + 0x12345678] Emit4( v ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax ip++; break; } // optimization: merge OP_LOCAL + OP_LOAD1 if ( ni->op == OP_LOAD1 ) { EmitAddEDI4( vm ); // add edi, 4 v = ci->value; if ( ISU8( v ) ) { EmitString( "0F B6 45" ); // movzx eax, byte ptr [ebp + 0x7F] Emit1( v ); } else { EmitString( "0F B6 85" ); // movzx eax, byte ptr [ebp + 0x12345678] Emit4( v ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax ip++; break; } // TODO: i = j + k; // TODO: i = j - k; EmitAddEDI4( vm ); // add edi, 4 v = ci->value; if ( ISU8( v ) ) { EmitString( "8D 46" ); // lea eax, [esi + 0x7F] Emit1( v ); } else { EmitString( "8D 86" ); // lea eax, [esi + 0x12345678] Emit4( v ); } EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax break; case OP_ARG: if ( LastCommand == LAST_COMMAND_STORE_FLOAT_EDI_SSE ) { REWIND(4); v = ci->value; if ( ISU8( v ) ) { EmitString( "F3 0F 11 45" ); // movss dword ptr [ebp + 0x7F], xmm0 Emit1( v ); } else { EmitString( "F3 0F 11 85" ); // movss dword ptr [ebp + 0x12345678], xmm0 Emit4( v ); } EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; } EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] if ( (ci+1)->op == MOP_NCPY && !(ci+1)->jused ) { // we will read counter from eax EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; } v = ci->value; if ( ISU8( v ) ) { EmitString( "89 45" ); // mov dword ptr [ebp + 0x7F], eax Emit1( v ); } else { EmitString( "89 85" ); // mov dword ptr [ebp + 0x12345678], eax Emit4( v ); } EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; case OP_CALL: #ifdef VM_LOG_SYSCALLS // [dataBase + programStack + 0] = ip-1; EmitString( "C7 45 00" ); // mov dword ptr [ebp], 0x12345678 Emit4( ip-1 ); #endif EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitCallOffset( FUNC_CALL ); // call +FUNC_CALL break; case OP_PUSH: EmitAddEDI4( vm ); // add edi, 4 break; case OP_POP: EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; case OP_LEAVE: #ifdef DEBUG_VM v = ci->value; if ( ISU8( v ) ) { EmitString( "83 C6" ); // add esi, 0x12 Emit1( v ); } else { EmitString( "81 C6" ); // add esi, 0x12345678 Emit4( v ); } #endif EmitString( "C3" ); // ret break; case OP_LOAD4: if ( LastCommand == LAST_COMMAND_MOV_EDI_EAX ) { REWIND( 2 ); EmitCheckReg( vm, REG_EAX, 4 ); // range check eax EmitString( "8B 04 03" ); // mov eax, dword ptr [ebx + eax] EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax break; } EmitMovECXEDI( vm ); // mov ecx, dword ptr [edi] EmitCheckReg( vm, REG_ECX, 4 ); // range check ecx EmitString( "8B 04 0B" ); // mov eax, dword ptr [ebx + ecx] EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax break; case OP_LOAD2: if ( LastCommand == LAST_COMMAND_MOV_EDI_EAX ) { REWIND( 2 ); EmitCheckReg( vm, REG_EAX, 2 ); // range check eax if ( ni->op == OP_SEX16 ) { EmitString( "0F BF 04 03" ); // movsx eax, word ptr [ebx + eax] ip++; } else { EmitString( "0F B7 04 03" ); // movzx eax, word ptr [ebx + eax] } EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax break; } EmitMovECXEDI( vm ); // mov ecx, dword ptr [edi] EmitCheckReg( vm, REG_ECX, 2 ); // range check ecx if ( ni->op == OP_SEX16 ) { EmitString( "0F BF 04 0B" ); // movsx eax, word ptr [ebx + ecx] ip++; } else { EmitString( "0F B7 04 0B" ); // movzx eax, word ptr [ebx + ecx] } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax break; case OP_LOAD1: if ( LastCommand == LAST_COMMAND_MOV_EDI_EAX ) { REWIND( 2 ); EmitCheckReg( vm, REG_EAX, 1 ); // range check eax if ( ni->op == OP_SEX8 ) { EmitString( "0F BE 04 03" ); // movsx eax, byte ptr [ebx + eax] ip++; } else { EmitString( "0F B6 04 03" ); // movzx eax, byte ptr [ebx + eax] } EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax break; } EmitMovECXEDI( vm ); // mov ecx, dword ptr [edi] EmitCheckReg( vm, REG_ECX, 1 ); // range check ecx if ( ni->op == OP_SEX8 ) { EmitString( "0F BE 04 0B" ); // movsx eax, byte ptr [ebx + ecx] ip++; } else { EmitString( "0F B6 04 0B" ); // movzx eax, byte ptr [ebx + ecx] } EmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax break; case OP_STORE4: EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitString( "8B 4F FC" ); // mov ecx, dword ptr [edi-4] EmitCheckReg( vm, REG_ECX, 4 ); // range check EmitString( "89 04 0B" ); // mov dword ptr [ebx + ecx], eax EmitCommand( LAST_COMMAND_SUB_DI_8 ); // sub edi, 8 break; case OP_STORE2: EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitString( "8B 4F FC" ); // mov ecx, dword ptr [edi-4] EmitCheckReg( vm, REG_ECX, 2 ); // range check EmitString( "66 89 04 0B" ); // mov word ptr [ebx + ecx], ax EmitCommand( LAST_COMMAND_SUB_DI_8 ); // sub edi, 8 break; case OP_STORE1: EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitString( "8B 4F FC" ); // mov ecx, dword ptr [edi-4] EmitCheckReg( vm, REG_ECX, 1 ); // range check EmitString( "88 04 0B" ); // mov byte ptr [ebx + ecx], al EmitCommand( LAST_COMMAND_SUB_DI_8 ); // sub edi, 8 break; case OP_EQ: case OP_NE: case OP_LTI: case OP_LEI: case OP_GTI: case OP_GEI: case OP_LTU: case OP_LEU: case OP_GTU: case OP_GEU: EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitCommand( LAST_COMMAND_SUB_DI_8 ); // sub edi, 8 EmitString( "39 47 04" ); // cmp dword ptr [edi+4], eax EmitJump( vm, ci, ci->op, ci->value ); break; case OP_EQF: case OP_NEF: case OP_LTF: case OP_LEF: case OP_GTF: case OP_GEF: if ( HasFCOM() ) { EmitLoadFloatEDI( vm ); if ( HasSSEFP() ) { EmitString( "F3 0F 10 4F FC" ); // movss xmm1, dword ptr [edi-4] EmitCommand( LAST_COMMAND_SUB_DI_8 ); // sub edi, 8 EmitString( "0F 2F C8" ); // comiss xmm1, xmm0 } else { EmitCommand( LAST_COMMAND_SUB_DI_8 ); // sub edi, 8 //EmitString( "D9 47 08" ); // fld dword ptr [edi+8] EmitString( "D9 47 04" ); // fld dword ptr [edi+4] EmitString( "DF E9" ); // fucomip EmitString( "DD D8" ); // fstp st(0) } EmitJump( vm, ci, ci->op, ci->value ); } else { EmitCommand( LAST_COMMAND_SUB_DI_8 ); // sub edi, 8 EmitString( "D9 47 04" ); // fld dword ptr [edi+4] EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] EmitString( "DF E0" ); // fnstsw ax EmitFloatJump( vm, ci, ci->op, ci->value ); } pop1 = OP_UNDEF; break; case OP_NEGI: EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitString( "F7 D8" ); // neg eax EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax break; case OP_ADD: wantres = ( ops[ ni->op ].stack <= 0 ); EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] if ( wantres ) { EmitString( "03 47 FC" ); // add eax, dword ptr [edi-4] EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax } else { EmitString( "01 47 FC" ); // add dword ptr [edi-4],eax EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 } break; case OP_SUB: wantres = ( ops[ ni->op ].stack <= 0 ); if ( wantres ) { EmitMovECXEDI( vm ); // mov ecx,dword ptr [edi] EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 EmitString( "29 C8" ); // sub eax, ecx EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax } else { EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitString( "29 47 FC" ); // sub dword ptr [edi-4],eax EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 } break; case OP_DIVI: wantres = ( ops[ ni->op ].stack <= 0 ); EmitMovECXEDI( vm ); // mov ecx,dword ptr [edi] EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] if ( wantres ) { EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 EmitString( "99" ); // cdq EmitString( "F7 F9" ); // idiv ecx EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax } else{ EmitString( "99" ); // cdq EmitString( "F7 F9" ); // idiv ecx EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 } break; case OP_DIVU: EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] EmitString( "33 D2" ); // xor edx, edx EmitString( "F7 37" ); // div dword ptr [edi] EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; case OP_MODI: EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] EmitString( "99" ); // cdq EmitString( "F7 3F" ); // idiv dword ptr [edi] EmitString( "89 57 FC" ); // mov dword ptr [edi-4],edx EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; case OP_MODU: EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] EmitString( "33 D2" ); // xor edx, edx EmitString( "F7 37" ); // div dword ptr [edi] EmitString( "89 57 FC" ); // mov dword ptr [edi-4],edx EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; case OP_MULI: wantres = ( ops[ ni->op ].stack <= 0 ); EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitString( "F7 6F FC" ); // imul eax, dword ptr [edi-4] if ( wantres ) { EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax } else { EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 } break; case OP_MULU: EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] EmitString( "F7 27" ); // mul dword ptr [edi] EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; case OP_BAND: wantres = ( ops[ ni->op ].stack <= 0 ); EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] if ( wantres ) { EmitString( "23 47 FC" ); // and eax, dword ptr [edi-4] EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax } else { EmitString( "21 47 FC" ); // and dword ptr [edi-4],eax EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 } break; case OP_BOR: EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitString( "09 47 FC" ); // or dword ptr [edi-4],eax EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; case OP_BXOR: EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitString( "31 47 FC" ); // xor dword ptr [edi-4],eax EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; case OP_BCOM: EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitString( "F7 D0" ); // not eax EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax break; case OP_LSH: EmitMovECXEDI( vm ); // mov ecx, dword ptr [edi] EmitString( "D3 67 FC" ); // shl dword ptr [edi-4], cl EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; case OP_RSHI: EmitMovECXEDI( vm ); // mov ecx, dword ptr [edi] EmitString( "D3 7F FC" ); // sar dword ptr [edi-4], cl EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; case OP_RSHU: EmitMovECXEDI( vm ); // mov ecx, dword ptr [edi] EmitString( "D3 6F FC" ); // shr dword ptr [edi-4], cl EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 break; case OP_NEGF: //if ( !ci->fpu ) EmitLoadFloatEDI( vm ); // fld dword ptr [edi] | movss xmm0, dword ptr [edi] if ( HasSSEFP() ) { EmitString( "0F 57 C9" ); // xorps xmm1, xmm1 EmitString( "0F 5C C8" ); // subps xmm1, xmm0 EmitString( "0F 28 C1" ); // movaps xmm0, xmm1 } else { EmitString( "D9 E0" ); // fchs } //if ( !ci->fpu || ci->store ) EmitCommand( LAST_COMMAND_STORE_FLOAT_EDI ); // fstp dword ptr [edi] break; case OP_ADDF: case OP_SUBF: case OP_DIVF: case OP_MULF: wantres = ( ops[ ni->op ].stack <= 0 ); if ( HasSSEFP() ) { if ( LastCommand == LAST_COMMAND_STORE_FLOAT_EDI_SSE ) { REWIND(4); EmitString( "0F 28 C8" ); // movaps xmm1, xmm0 } else { EmitString( "F3 0F 10 0F" ); // movss xmm1, dword ptr [edi] } EmitString( "F3 0F 10 47 FC" ); // movss xmm0, dword ptr [edi-4] if ( wantres ) { if ( ni->op == OP_STORE4 ) { EmitString( "8B 47 F8" ); // mov eax, dword ptr [edi-8] EmitCheckReg( vm, REG_EAX, 4 ); } else if ( ni->op == OP_ARG ) { // } else { EmitCommand( LAST_COMMAND_SUB_DI_4 ); } } switch( ci->op ) { case OP_ADDF: EmitString( "0F 58 C1" ); break; // addps xmm0, xmm1 case OP_SUBF: EmitString( "0F 5C C1" ); break; // subps xmm0, xmm1 case OP_MULF: EmitString( "0F 59 C1" ); break; // mulps xmm0, xmm1 case OP_DIVF: EmitString( "0F 5E C1" ); break; // divps xmm0, xmm1 } if ( wantres ) { if ( ni->op == OP_STORE4 ) { EmitString( "F3 0F 11 04 03" ); // movss dword ptr [ebx + eax], xmm0 EmitCommand( LAST_COMMAND_SUB_DI_12 ); pop1 = OP_UNDEF; ip++; // OP_STORE4 } else if ( ni->op == OP_ARG ) { v = ni->value; if ( ISU8( v ) ) { EmitString( "F3 0F 11 45" ); // movss dword ptr [ebp + 0x7F], xmm0 Emit1( v ); } else { EmitString( "F3 0F 11 85" ); // movss dword ptr [ebp + 0x12345678], xmm0 Emit4( v ); } EmitCommand( LAST_COMMAND_SUB_DI_8 ); pop1 = OP_UNDEF; ip++; // OP_ARG } else { EmitCommand( LAST_COMMAND_STORE_FLOAT_EDI ); } } else { EmitString( "F3 0F 11 47 FC" ); // movss dword ptr [edi-4], xmm0 EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 } } else { EmitString( "D9 47 FC" ); // fld dword ptr [edi-4] EmitFCalcEDI( ci->op ); // fadd|fsub|fmul|fdiv dword ptr [edi] EmitString( "D9 5F FC" ); // fstp dword ptr [edi-4] EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 } break; case OP_CVIF: if ( HasSSEFP() ) { if ( LastCommand == LAST_COMMAND_MOV_EDI_EAX ) { REWIND(2); EmitString( "F3 0F 2A C0" ); // cvtsi2ss xmm0, eax } else { EmitString( "F3 0F 2A 07" ); // cvtsi2ss xmm0, dword ptr [edi] } } else { EmitString( "DB 07" ); // fild dword ptr [edi] } //if ( !ci->fpu ) EmitCommand( LAST_COMMAND_STORE_FLOAT_EDI ); break; case OP_CVFI: if ( HasSSEFP() ) { // assume that rounding mode in MXCSR is correctly set in 64-bit environment EmitLoadFloatEDI_SSE( vm ); // movss xmm0, dword ptr [edi] EmitString( "F3 0F 2C C0" ); // cvttss2si eax, xmm0 EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax } else { EmitLoadFloatEDI_X87( vm ); // fld dword ptr [edi] // call the library conversion function EmitCallOffset( FUNC_FTOL ); // call +FUNC_FTOL } break; case OP_SEX8: EmitString( "0F BE 07" ); // movsx eax, byte ptr [edi] EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax break; case OP_SEX16: EmitString( "0F BF 07" ); // movsx eax, word ptr [edi] EmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax break; case OP_BLOCK_COPY: EmitString( "B9" ); // mov ecx, 0x12345678 Emit4( ci->value >> 2 ); EmitCallOffset( FUNC_BCPY ); break; case OP_JUMP: EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4 // jump target range check if ( (int)vm_rtChecks.value & 4 ) { if ( proc_base != -1 ) { // allow jump within local function scope only EmitString( "89 C2" ); // mov edx, eax if ( ISU8( proc_base ) ) { EmitString( "83 EA" ); // sub edx, 0x7F Emit1( proc_base ); } else { EmitString( "81 EA" ); // sub edx, 0x12345678 Emit4( proc_base ); } if ( ISU8( proc_len ) ) { EmitString( "83 FA" ); // cmp edx, 0x7F Emit1( proc_len ); } else { EmitString( "81 FA" ); // cmp edx, 0x12345678 Emit4( proc_len ); } } else { EmitString( "3D" ); // cmp eax, 0x12345678 Emit4( vm->instructionCount ); } EmitString( "0F 83" ); // jae +funcOffset[FUNC_BADJ] n = funcOffset[FUNC_BADJ] - compiledOfs; Emit4( n - 6 ); } #if idx64 EmitString( "41 FF 24 C0" ); // jmp dword ptr [r8 + rax*8] #else EmitString( "FF 24 85" ); // jmp dword ptr [instructionPointers + eax * 4] EmitPtr( instructionPointers ); #endif break; case MOP_IGNORE4: case MOP_ADD4: case MOP_SUB4: case MOP_BAND4: case MOP_BOR4: case MOP_NCPY: if ( !EmitMOPs( vm, ci->op ) ) SV_Error( "VM_CompileX86: bad opcode %02X", ci->op ); break; default: SV_Error( "VM_CompileX86: bad opcode %02X", ci->op ); VM_FreeBuffers(); return false; } pop1 = (opcode_t)ci->op; } // while( ip < header->instructionCount ) // **************** // system functions // **************** EmitAlign( 8 ); funcOffset[FUNC_CALL] = compiledOfs; EmitCallFunc( vm ); EmitAlign( 8 ); funcOffset[FUNC_FTOL] = compiledOfs; EmitFTOLFunc( vm ); EmitAlign( 8 ); funcOffset[FUNC_BCPY] = compiledOfs; EmitBCPYFunc( vm ); EmitAlign( 8 ); funcOffset[FUNC_NCPY] = compiledOfs; EmitNCPYFunc( vm ); // *************** // error functions // *************** // bad jump EmitAlign( 8 ); funcOffset[FUNC_BADJ] = compiledOfs; EmitBADJFunc( vm ); // error jump EmitAlign( 8 ); funcOffset[FUNC_ERRJ] = compiledOfs; EmitERRJFunc( vm ); // programStack overflow EmitAlign( 8 ); funcOffset[FUNC_PSOF] = compiledOfs; EmitPSOFFunc( vm ); // opStack overflow EmitAlign( 8 ); funcOffset[FUNC_OSOF] = compiledOfs; EmitOSOFFunc( vm ); // read/write access violation EmitAlign( 8 ); funcOffset[FUNC_DATA] = compiledOfs; EmitDATAFunc( vm ); EmitAlign( sizeof( intptr_t ) ); // for instructionPointers } // for( pass = 0; pass < n; pass++ ) n = header->instructionCount * sizeof( intptr_t ); if ( code == NULL ) { code = (byte*)VM_Alloc_Compiled( vm, PAD(compiledOfs,8), n ); if ( code == NULL ) { return false; } instructionPointers = (intptr_t*)(byte*)(code + PAD(compiledOfs,8)); //vm->instructionPointers = instructionPointers; // for debug purposes? pass = NUM_PASSES-1; // repeat last pass goto __compile; } // offset all the instruction pointers for the new location for ( i = 0 ; i < header->instructionCount ; i++ ) { if ( !inst[i].jused ) { instructionPointers[ i ] = (intptr_t)badJumpPtr; continue; } instructionPointers[ i ] = (intptr_t)vm->codeBase.ptr + instructionOffsets[ i ]; } VM_FreeBuffers(); #ifdef VM_X86_MMAP if ( mprotect( vm->codeBase.ptr, vm->codeSize, PROT_READ|PROT_EXEC ) ) { VM_Destroy_Compiled( vm ); Con_Printf( "VM_CompileX86: mprotect failed\n" ); return false; } #elif _WIN32 { DWORD oldProtect = 0; // remove write permissions. if ( !VirtualProtect( vm->codeBase.ptr, vm->codeSize, PAGE_EXECUTE_READ, &oldProtect ) ) { VM_Destroy_Compiled( vm ); Con_Printf( "VM_CompileX86: VirtualProtect failed\n" ); return false; } } #endif vm->destroy = VM_Destroy_Compiled; Con_Printf( "VM file %s compiled to %i bytes of code\n", vm->name, compiledOfs ); return true; } /* ================= VM_Alloc_Compiled ================= */ static void *VM_Alloc_Compiled( vm_t *vm, int codeLength, int tableLength ) { void *ptr; int length; length = codeLength + tableLength; #ifdef VM_X86_MMAP ptr = mmap( NULL, length, PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0 ); if ( ptr == MAP_FAILED ) { SV_Error( "VM_CompileX86: mmap failed" ); return NULL; } #elif _WIN32 // allocate memory with EXECUTE permissions under windows. ptr = VirtualAlloc( NULL, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); if ( !ptr ) { SV_Error( "VM_CompileX86: VirtualAlloc failed" ); return NULL; } #else ptr = malloc( length ); if ( !ptr ) { SV_Error( "VM_CompileX86: malloc failed" ); return NULL; } #endif vm->codeBase.ptr = (byte*)ptr; vm->codeLength = codeLength; vm->codeSize = length; return vm->codeBase.ptr; } /* ============== VM_Destroy_Compiled ============== */ static void VM_Destroy_Compiled( vm_t* vm ) { #ifdef VM_X86_MMAP munmap( vm->codeBase.ptr, vm->codeSize ); #elif _WIN32 VirtualFree( vm->codeBase.ptr, 0, MEM_RELEASE ); #else free( vm->codeBase.ptr ); #endif vm->codeBase.ptr = NULL; } /* ============== VM_CallCompiled This function is called directly by the generated code ============== */ int VM_CallCompiled( vm_t *vm, int nargs, int *args ) { int opStack[MAX_OPSTACK_SIZE]; unsigned int stackOnEntry; int *image; int *oldOpTop; int i; // we might be called recursively, so this might not be the very top stackOnEntry = vm->programStack; oldOpTop = vm->opStackTop; vm->programStack -= (MAX_VMMAIN_CALL_ARGS+2)*4; // set up the stack frame image = (int*)( vm->dataBase + vm->programStack ); for ( i = 0; i < nargs; i++ ) { image[ i + 2 ] = args[ i ]; } image[1] = 0; // return stack image[0] = -1; // will terminate loop on return opStack[1] = 0; vm->opStack = opStack; vm->opStackTop = opStack + ARRAY_LEN( opStack ) - 1; vm->codeBase.func(); // go into generated code if ( vm->opStack != &opStack[1] ) { SV_Error( "opStack corrupted in compiled code" ); } #ifdef DEBUG_VM if ( vm->programStack != stackOnEntry - CALL_PSTACK ) { SV_Error( "programStack corrupted in compiled code" ); } #endif vm->programStack = stackOnEntry; vm->opStackTop = oldOpTop; return vm->opStack[0]; } #else int VM_CallCompiled( vm_t *vm, int nargs, int *args ) { return 0;} qbool VM_Compile( vm_t *vm, vmHeader_t *header ) { return false; } #endif #endif /* USE_PR2 */ mvdsv-0.35/src/winquake.rc000066400000000000000000000067661427146041000155570ustar00rootroot00000000000000/* 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. $Id: winquake.rc,v 1.6 2007/01/07 18:11:03 disconn3ct Exp $ */ //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winresrc.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_DIALOG2 DIALOGEX 50, 20, 317, 227 STYLE WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Mvdsv - QW server" CLASS "mvdsv" FONT 10, "Fixedsys" BEGIN EDITTEXT IDC_EDIT2,6,187,306,13,ES_AUTOHSCROLL | WS_GROUP DEFPUSHBUTTON "OK",IDC_OK,303,211,9,10,NOT WS_VISIBLE PUSHBUTTON "QUIT",IDC_QUIT,262,207,50,14,WS_GROUP EDITTEXT IDC_EDIT1,6,5,306,175,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | ES_WANTRETURN | NOT WS_BORDER | WS_VSCROLL | NOT WS_TABSTOP,WS_EX_CLIENTEDGE PUSHBUTTON "CLEAR",IDC_CLEAR,6,207,50,14 END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_DIALOG2, DIALOG BEGIN LEFTMARGIN, 6 RIGHTMARGIN, 312 TOPMARGIN, 5 BOTTOMMARGIN, 221 END END #endif // APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE //BEGIN // "#include ""afxres.h""\r\n" // "\0" //END //3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ICON2 ICON DISCARDABLE "qwcl2.ico" #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED mvdsv-0.35/src/world.c000066400000000000000000000436011427146041000146650ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // world.c -- world query functions #ifndef CLIENTONLY #include "qwsvdef.h" /* entities never clip against themselves, or their owner line of sight checks trace->crosscontent, but bullets don't */ typedef struct { vec3_t boxmins, boxmaxs;// enclose the test object along entire move float *mins, *maxs; // size of the moving object vec3_t mins2, maxs2; // size when clipping against mosnters float *start, *end; trace_t trace; int type; edict_t *passedict; } moveclip_t; /* ================ SV_HullForEntity Returns a hull that can be used for testing or clipping an object of mins/maxs size. Offset is filled in to contain the adjustment that must be added to the testing object's origin to get a point to use with the returned hull. ================ */ hull_t *SV_HullForEntity (edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset) { vec3_t size, hullmins, hullmaxs; cmodel_t *model; hull_t *hull; // decide which clipping hull to use, based on the size if (ent->v->solid == SOLID_BSP) { // explicit hulls in the BSP model if (ent->v->movetype != MOVETYPE_PUSH) SV_Error ("SOLID_BSP without MOVETYPE_PUSH"); if ((unsigned)ent->v->modelindex >= MAX_MODELS) SV_Error ("SV_HullForEntity: ent.modelindex >= MAX_MODELS"); model = sv.models[(int)ent->v->modelindex]; if (!model) SV_Error ("SOLID_BSP with a non-bsp model"); VectorSubtract (maxs, mins, size); if (size[0] < 3) hull = &model->hulls[0]; else if (size[0] <= 32) hull = &model->hulls[1]; else hull = &model->hulls[2]; // calculate an offset value to center the origin VectorSubtract (hull->clip_mins, mins, offset); VectorAdd (offset, ent->v->origin, offset); } else { // create a temp hull from bounding box sizes VectorSubtract (ent->v->mins, maxs, hullmins); VectorSubtract (ent->v->maxs, mins, hullmaxs); hull = CM_HullForBox (hullmins, hullmaxs); VectorCopy (ent->v->origin, offset); } return hull; } /* =============================================================================== ENTITY AREA CHECKING =============================================================================== */ // ClearLink is used for new headnodes void ClearLink (link_t *l) { l->prev = l->next = l; } void RemoveLink (link_t *l) { l->next->prev = l->prev; l->prev->next = l->next; } void InsertLinkBefore (link_t *l, link_t *before) { l->next = before; l->prev = before->prev; l->prev->next = l; l->next->prev = l; } void InsertLinkAfter (link_t *l, link_t *after) { l->next = after->next; l->prev = after; l->prev->next = l; l->next->prev = l; } //============================================================================ // well, here should be all things related to world but atm it antilag only typedef struct world_s { // { sv_antilag related float lagentsfrac; laggedentinfo_t *lagents; unsigned int maxlagents; // } } world_t; static world_t w; areanode_t sv_areanodes[AREA_NODES]; int sv_numareanodes; /* =============== SV_CreateAreaNode =============== */ areanode_t *SV_CreateAreaNode (int depth, vec3_t mins, vec3_t maxs) { areanode_t *anode; vec3_t size; vec3_t mins1, maxs1, mins2, maxs2; anode = &sv_areanodes[sv_numareanodes]; sv_numareanodes++; ClearLink (&anode->trigger_edicts); ClearLink (&anode->solid_edicts); if (depth == AREA_DEPTH) { anode->axis = -1; anode->children[0] = anode->children[1] = NULL; return anode; } VectorSubtract (maxs, mins, size); if (size[0] > size[1]) anode->axis = 0; else anode->axis = 1; anode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]); VectorCopy (mins, mins1); VectorCopy (mins, mins2); VectorCopy (maxs, maxs1); VectorCopy (maxs, maxs2); maxs1[anode->axis] = mins2[anode->axis] = anode->dist; anode->children[0] = SV_CreateAreaNode (depth+1, mins2, maxs2); anode->children[1] = SV_CreateAreaNode (depth+1, mins1, maxs1); return anode; } /* =============== SV_ClearWorld =============== */ void SV_ClearWorld (void) { memset (sv_areanodes, 0, sizeof(sv_areanodes)); sv_numareanodes = 0; SV_CreateAreaNode (0, sv.worldmodel->mins, sv.worldmodel->maxs); } /* =============== SV_UnlinkEdict =============== */ void SV_UnlinkEdict (edict_t *ent) { if (!ent->e.area.prev) return; // not linked in anywhere RemoveLink (&ent->e.area); ent->e.area.prev = ent->e.area.next = NULL; } /* ==================== SV_AreaEdicts ==================== */ int SV_AreaEdicts (vec3_t mins, vec3_t maxs, edict_t **edicts, int max_edicts, int area) { link_t *l, *start; edict_t *touch; int stackdepth = 0, count = 0; areanode_t *localstack[AREA_NODES], *node = sv_areanodes; // touch linked edicts while (1) { if (area == AREA_SOLID) start = &node->solid_edicts; else start = &node->trigger_edicts; for (l = start->next ; l != start ; l = l->next) { touch = EDICT_FROM_AREA(l); if (touch->v->solid == SOLID_NOT) continue; if (mins[0] > touch->v->absmax[0] || mins[1] > touch->v->absmax[1] || mins[2] > touch->v->absmax[2] || maxs[0] < touch->v->absmin[0] || maxs[1] < touch->v->absmin[1] || maxs[2] < touch->v->absmin[2]) continue; if (count == max_edicts) return count; edicts[count++] = touch; } if (node->axis == -1) goto checkstack; // terminal node // recurse down both sides if (maxs[node->axis] > node->dist) { if (mins[node->axis] < node->dist) { localstack[stackdepth++] = node->children[0]; node = node->children[1]; continue; } node = node->children[0]; continue; } if (mins[node->axis] < node->dist) { node = node->children[1]; continue; } checkstack: if (!stackdepth) return count; node = localstack[--stackdepth]; } return count; } /* ==================== SV_TouchLinks ==================== */ static void SV_TouchLinks ( edict_t *ent, areanode_t *node ) { int i, numtouch; edict_t *touchlist[MAX_EDICTS], *touch; int old_self, old_other; numtouch = SV_AreaEdicts(ent->v->absmin, ent->v->absmax, touchlist, sv.max_edicts, AREA_TRIGGERS); // touch linked edicts for (i = 0; i < numtouch; i++) { touch = touchlist[i]; if (touch == ent) continue; if (!touch->v->touch || touch->v->solid != SOLID_TRIGGER) continue; old_self = pr_global_struct->self; old_other = pr_global_struct->other; pr_global_struct->self = EDICT_TO_PROG(touch); pr_global_struct->other = EDICT_TO_PROG(ent); pr_global_struct->time = sv.time; PR_EdictTouch (touch->v->touch); pr_global_struct->self = old_self; pr_global_struct->other = old_other; } } /* ==================== SV_LinkToLeafs ==================== */ void SV_LinkToLeafs (edict_t *ent) { int i, leafnums[MAX_ENT_LEAFS]; ent->e.num_leafs = CM_FindTouchedLeafs (ent->v->absmin, ent->v->absmax, leafnums, MAX_ENT_LEAFS, 0, NULL); for (i = 0; i < ent->e.num_leafs; i++) { // ent->e.leafnums are real leafnum minus one (for pvs checks) ent->e.leafnums[i] = leafnums[i] - 1; } } /* =============== SV_LinkEdict =============== */ void SV_LinkEdict (edict_t *ent, qbool touch_triggers) { areanode_t *node; if (ent->e.area.prev) SV_UnlinkEdict (ent); // unlink from old position if (ent == sv.edicts) return; // don't add the world if (ent->e.free) return; // set the abs box VectorAdd (ent->v->origin, ent->v->mins, ent->v->absmin); VectorAdd (ent->v->origin, ent->v->maxs, ent->v->absmax); // // to make items easier to pick up and allow them to be grabbed off // of shelves, the abs sizes are expanded // if ((int)ent->v->flags & FL_ITEM) { ent->v->absmin[0] -= 15; ent->v->absmin[1] -= 15; ent->v->absmax[0] += 15; ent->v->absmax[1] += 15; } else { // because movement is clipped an epsilon away from an actual edge, // we must fully check even when bounding boxes don't quite touch ent->v->absmin[0] -= 1; ent->v->absmin[1] -= 1; ent->v->absmin[2] -= 1; ent->v->absmax[0] += 1; ent->v->absmax[1] += 1; ent->v->absmax[2] += 1; } // link to PVS leafs if (ent->v->modelindex) SV_LinkToLeafs (ent); else ent->e.num_leafs = 0; if (ent->v->solid == SOLID_NOT) return; // find the first node that the ent's box crosses node = sv_areanodes; while (1) { if (node->axis == -1) break; if (ent->v->absmin[node->axis] > node->dist) node = node->children[0]; else if (ent->v->absmax[node->axis] < node->dist) node = node->children[1]; else break; // crosses the node } // link it in if (ent->v->solid == SOLID_TRIGGER) InsertLinkBefore (&ent->e.area, &node->trigger_edicts); else InsertLinkBefore (&ent->e.area, &node->solid_edicts); // if touch_triggers, touch all entities at this node and decend for more if (touch_triggers) SV_TouchLinks ( ent, sv_areanodes ); } /* =============================================================================== POINT TESTING IN HULLS =============================================================================== */ /* ================== SV_PointContents ================== */ int SV_PointContents (vec3_t p) { return CM_HullPointContents (&sv.worldmodel->hulls[0], sv.worldmodel->hulls[0].firstclipnode, p); } //=========================================================================== /* ============ SV_TestEntityPosition A small wrapper around SV_BoxInSolidEntity that never clips against the supplied entity. ============ */ edict_t *SV_TestEntityPosition (edict_t *ent) { trace_t trace; if (ent->v->solid == SOLID_TRIGGER || ent->v->solid == SOLID_NOT) // only clip against bmodels trace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, ent->v->origin, MOVE_NOMONSTERS, ent); else trace = SV_Trace(ent->v->origin, ent->v->mins, ent->v->maxs, ent->v->origin, MOVE_NORMAL, ent); if (trace.startsolid) return sv.edicts; return NULL; } /* ================== SV_ClipMoveToEntity Handles selection or creation of a clipping hull, and offseting (and eventually rotation) of the end points ================== */ trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t *eorg, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) { trace_t trace; vec3_t offset; vec3_t start_l, end_l; hull_t *hull; // get the clipping hull hull = SV_HullForEntity (ent, mins, maxs, offset); // { well, its hack for sv_antilag if (eorg) VectorCopy((*eorg), offset); // } VectorSubtract (start, offset, start_l); VectorSubtract (end, offset, end_l); // trace a line through the apropriate clipping hull trace = CM_HullTrace (hull, start_l, end_l); // fix trace up by the offset VectorAdd (trace.endpos, offset, trace.endpos); // did we clip the move? if (trace.fraction < 1 || trace.startsolid) trace.e.ent = ent; return trace; } //=========================================================================== /* ==================== SV_ClipToLinks Mins and maxs enclose the entire area swept by the move ==================== */ void SV_ClipToLinks ( areanode_t *node, moveclip_t *clip ) { int i, numtouch; edict_t *touchlist[MAX_EDICTS], *touch; trace_t trace; numtouch = SV_AreaEdicts (clip->boxmins, clip->boxmaxs, touchlist, sv.max_edicts, AREA_SOLID); // touch linked edicts for (i = 0; i < numtouch; i++) { // might intersect, so do an exact clip if (clip->trace.allsolid) return; // return!!! touch = touchlist[i]; if (touch == clip->passedict) continue; if (touch->v->solid == SOLID_TRIGGER) SV_Error ("Trigger in clipping list"); if ((clip->type & MOVE_NOMONSTERS) && touch->v->solid != SOLID_BSP) continue; if (clip->passedict && clip->passedict->v->size[0] && !touch->v->size[0]) continue; // points never interact if (clip->type & MOVE_LAGGED) { //can't touch lagged ents - we do an explicit test for them later in SV_AntilagClipCheck. if (touch->e.entnum - 1 < w.maxlagents) if (w.lagents[touch->e.entnum - 1].present) continue; } if (clip->passedict) { if (PROG_TO_EDICT(touch->v->owner) == clip->passedict) continue; // don't clip against own missiles if (PROG_TO_EDICT(clip->passedict->v->owner) == touch) continue; // don't clip against owner } if ((int)touch->v->flags & FL_MONSTER) trace = SV_ClipMoveToEntity (touch, NULL, clip->start, clip->mins2, clip->maxs2, clip->end); else trace = SV_ClipMoveToEntity (touch, NULL, clip->start, clip->mins, clip->maxs, clip->end); // qqshka: I have NO idea why we keep startsolid but let do it. // make sure we keep a startsolid from a previous trace clip->trace.startsolid |= trace.startsolid; if ( trace.allsolid || trace.fraction < clip->trace.fraction ) { // set edict trace.e.ent = touch; // make sure we keep a startsolid from a previous trace trace.startsolid |= clip->trace.startsolid; // bit by bit copy trace struct clip->trace = trace; } } } /* ================== SV_MoveBounds ================== */ void SV_MoveBounds (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, vec3_t boxmins, vec3_t boxmaxs) { #if 0 // debug to test against everything boxmins[0] = boxmins[1] = boxmins[2] = -9999; boxmaxs[0] = boxmaxs[1] = boxmaxs[2] = 9999; #else int i; for (i=0 ; i<3 ; i++) { if (end[i] > start[i]) { boxmins[i] = start[i] + mins[i] - 1; boxmaxs[i] = end[i] + maxs[i] + 1; } else { boxmins[i] = end[i] + mins[i] - 1; boxmaxs[i] = start[i] + maxs[i] + 1; } } #endif } //============================================= void SV_AntilagReset (edict_t *ent) { if (ent->e.entnum == 0 || ent->e.entnum > MAX_CLIENTS) return; svs.clients[ent->e.entnum - 1].antilag_position_next = 0; } void SV_AntilagClipSetUp ( areanode_t *node, moveclip_t *clip ) { edict_t *passedict = clip->passedict; int entnum = passedict->e.entnum; clip->type &= ~MOVE_LAGGED; if (entnum && entnum <= MAX_CLIENTS && !svs.clients[entnum - 1].isBot) { clip->type |= MOVE_LAGGED; w.lagents = svs.clients[entnum - 1].laggedents; w.maxlagents = svs.clients[entnum - 1].laggedents_count; w.lagentsfrac = svs.clients[entnum - 1].laggedents_frac; } else if (passedict->v->owner) { int owner = PROG_TO_EDICT(passedict->v->owner)->e.entnum; if (owner && owner <= MAX_CLIENTS && !svs.clients[owner - 1].isBot) { clip->type |= MOVE_LAGGED; w.lagents = svs.clients[owner - 1].laggedents; w.maxlagents = svs.clients[owner - 1].laggedents_count; w.lagentsfrac = svs.clients[owner - 1].laggedents_frac; } } } void SV_AntilagClipCheck ( areanode_t *node, moveclip_t *clip ) { trace_t trace; edict_t *touch; vec3_t lp; int i; for (i = 0; i < w.maxlagents; i++) { if (clip->trace.allsolid) return; // return!!! if (!w.lagents[i].present) continue; touch = EDICT_NUM(i + 1); if (touch->v->solid == SOLID_NOT) continue; if (touch == clip->passedict) continue; if (touch->v->solid == SOLID_TRIGGER) SV_Error ("Trigger (%s) in clipping list", PR_GetEntityString(touch->v->classname)); if ((clip->type & MOVE_NOMONSTERS) && touch->v->solid != SOLID_BSP) continue; VectorInterpolate(touch->v->origin, w.lagentsfrac, w.lagents[i].laggedpos, lp); if ( clip->boxmins[0] > lp[0]+touch->v->maxs[0] || clip->boxmins[1] > lp[1]+touch->v->maxs[1] || clip->boxmins[2] > lp[2]+touch->v->maxs[2] || clip->boxmaxs[0] < lp[0]+touch->v->mins[0] || clip->boxmaxs[1] < lp[1]+touch->v->mins[1] || clip->boxmaxs[2] < lp[2]+touch->v->mins[2] ) continue; if (clip->passedict && clip->passedict->v->size[0] && !touch->v->size[0]) continue; // points never interact if (clip->passedict) { if (PROG_TO_EDICT(touch->v->owner) == clip->passedict) continue; // don't clip against own missiles if (PROG_TO_EDICT(clip->passedict->v->owner) == touch) continue; // don't clip against owner } if ((int)touch->v->flags & FL_MONSTER) trace = SV_ClipMoveToEntity (touch, &lp, clip->start, clip->mins2, clip->maxs2, clip->end); else trace = SV_ClipMoveToEntity (touch, &lp, clip->start, clip->mins, clip->maxs, clip->end); // qqshka: I have NO idea why we keep startsolid but let do it. // make sure we keep a startsolid from a previous trace clip->trace.startsolid |= trace.startsolid; if ( trace.allsolid || trace.fraction < clip->trace.fraction ) { // set edict trace.e.ent = touch; // make sure we keep a startsolid from a previous trace trace.startsolid |= clip->trace.startsolid; // bit by bit copy trace struct clip->trace = trace; } } } /* ================== SV_Trace ================== */ trace_t SV_Trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int type, edict_t *passedict) { moveclip_t clip; int i; memset ( &clip, 0, sizeof ( moveclip_t ) ); // clip to world clip.trace = SV_ClipMoveToEntity ( sv.edicts, NULL, start, mins, maxs, end ); clip.start = start; clip.end = end; clip.mins = mins; clip.maxs = maxs; clip.type = type; clip.passedict = passedict; if (type & MOVE_MISSILE) { for (i=0 ; i<3 ; i++) { clip.mins2[i] = -15; clip.maxs2[i] = 15; } } else { VectorCopy (mins, clip.mins2); VectorCopy (maxs, clip.maxs2); } // create the bounding box of the entire move SV_MoveBounds ( start, clip.mins2, clip.maxs2, end, clip.boxmins, clip.boxmaxs ); // set up antilag if (clip.type & MOVE_LAGGED) SV_AntilagClipSetUp ( sv_areanodes, &clip ); // clip to entities SV_ClipToLinks ( sv_areanodes, &clip ); // additional antilag clip check if (clip.type & MOVE_LAGGED) SV_AntilagClipCheck ( sv_areanodes, &clip ); return clip.trace; } #endif // !CLIENTONLY mvdsv-0.35/src/world.h000066400000000000000000000051631427146041000146730ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // world.h #ifndef __WORLD_H__ #define __WORLD_H__ #define MOVE_NORMAL 0 #define MOVE_NOMONSTERS 1 #define MOVE_MISSILE 2 // { sv_antilag related #define MOVE_LAGGED 64 //trace touches current last-known-state, instead of actual ents (just affects players for now) // } typedef struct areanode_s { int axis; // -1 = leaf node float dist; struct areanode_s *children[2]; link_t trigger_edicts; link_t solid_edicts; } areanode_t; #define AREA_SOLID 0 #define AREA_TRIGGERS 1 #define AREA_DEPTH 4 #define AREA_NODES 32 extern areanode_t sv_areanodes[AREA_NODES]; void SV_ClearWorld (void); // called after the world model has been loaded, before linking any entities void SV_UnlinkEdict (edict_t *ent); // call before removing an entity, and before trying to move one, // so it doesn't clip against itself // flags ent->v->modified void SV_LinkEdict (edict_t *ent, qbool touch_triggers); // Needs to be called any time an entity changes origin, mins, maxs, or solid // flags ent->v->modified // sets ent->v->absmin and ent->v->absmax // if touchtriggers, calls prog functions for the intersected triggers int SV_PointContents (vec3_t p); // returns the CONTENTS_* value from the world at the given point. // does not check any entities at all edict_t *SV_TestEntityPosition (edict_t *ent); trace_t SV_Trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int type, edict_t *passedict); // mins and maxs are relative // if the entire move stays in a solid volume, trace.allsolid will be set // if the starting point is in a solid, it will be allowed to move out // to an open area // nomonsters is used for line of sight or edge testing, where mosnters // shouldn't be considered solid objects // passedict is explicitly excluded from clipping checks (normally NULL) int SV_AreaEdicts (vec3_t mins, vec3_t maxs, edict_t **edicts, int max_edicts, int area); void SV_AntilagReset (edict_t *ent); #endif /* !__WORLD_H__ */ mvdsv-0.35/src/zone.c000066400000000000000000000337721427146041000145210ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // zone.c - memory management #ifdef SERVERONLY #include "qwsvdef.h" static void Cache_FreeLow(int new_low_hunk); static void Cache_FreeHigh(int new_high_hunk); #else #include "common.h" #include "gl_model.h" #define Cache_FreeLow(...) #define Cache_FreeHigh(...) #define Cache_Init(...) #endif //============================================================================ #define HUNK_SENTINEL 0x1df001ed typedef struct { int sentinel; int size; // including sizeof(hunk_t), -1 = not allocated char name[8]; } hunk_t; byte *hunk_base; int hunk_size; int hunk_low_used; int hunk_high_used; qbool hunk_tempactive; int hunk_tempmark; /* ============== Hunk_Check Run consistency and sentinel trashing checks ============== */ void Hunk_Check(void) { hunk_t *h; for (h = (hunk_t *)hunk_base; (byte *)h != hunk_base + hunk_low_used;) { if (h->sentinel != HUNK_SENTINEL) { Sys_Error("Hunk_Check: trashed sentinel"); } if (h->size < 16 || h->size + (byte *)h - hunk_base > hunk_size) { Sys_Error("Hunk_Check: bad size"); } h = (hunk_t *)((byte *)h + h->size); } } /* ============== Hunk_Print If "all" is specified, every single allocation is printed. Otherwise, allocations with the same name will be totaled up before printing. ============== */ void Hunk_Print(qbool all) { hunk_t *h, *next, *endlow, *starthigh, *endhigh; int count, sum; int totalblocks; char name[9]; name[8] = 0; count = 0; sum = 0; totalblocks = 0; h = (hunk_t *)hunk_base; endlow = (hunk_t *)(hunk_base + hunk_low_used); starthigh = (hunk_t *)(hunk_base + hunk_size - hunk_high_used); endhigh = (hunk_t *)(hunk_base + hunk_size); Con_Printf(" :%8i total hunk size\n", hunk_size); Con_Printf("-------------------------\n"); while (1) { // skip to the high hunk if done with low hunk if (h == endlow) { Con_Printf("-------------------------\n"); Con_Printf(" :%8ikb REMAINING\n", (hunk_size - hunk_low_used - hunk_high_used) / 1024); Con_Printf("-------------------------\n"); h = starthigh; } // if totally done, break if (h == endhigh) { break; } // run consistency checks if (h->sentinel != HUNK_SENTINEL) { Sys_Error("Hunk_Print: trashed sentinel"); } if (h->size < 16 || h->size + (byte *)h - hunk_base > hunk_size) { Sys_Error("Hunk_Print: bad size"); } next = (hunk_t *)((byte *)h + h->size); count++; totalblocks++; sum += h->size; // print the single block memcpy(name, h->name, 8); if (all) { Con_Printf("%8p :%8i %8s\n", h, h->size, name); } // print the total if (next == endlow || next == endhigh || strncmp(h->name, next->name, 8)) { if (!all) { Con_Printf(" :%8ikb %8s (TOTAL)\n", sum / 1024, name); } count = 0; sum = 0; } h = next; } Con_Printf("-------------------------\n"); Con_Printf("%8i total blocks\n", totalblocks); Con_Printf("High used %i, low used %i\n", hunk_high_used, hunk_low_used); } void Hunk_Print_f(void) { qbool all = Cmd_Argc() != 1; Hunk_Print(all); } /* =================== Hunk_AllocName =================== */ void *Hunk_AllocName(int size, const char *name) { hunk_t *h; #ifdef PARANOID Hunk_Check(); #endif if (size < 0) { Sys_Error("Hunk_AllocName: bad size: %i", size); } size = sizeof(hunk_t) + ((size + 15) & ~15); if (hunk_size - hunk_low_used - hunk_high_used < size) { #ifdef SERVERONLY Sys_Error("Hunk_AllocName: Not enough RAM allocated. Try starting using \"-mem 64\" (or more) on the command line."); #else Sys_Error("Hunk_AllocName: Not enough RAM allocated. Try starting using \"-mem 128\" (or more) on the command line."); #endif } h = (hunk_t *)(hunk_base + hunk_low_used); hunk_low_used += size; Cache_FreeLow(hunk_low_used); memset(h, 0, size); h->size = size; h->sentinel = HUNK_SENTINEL; strlcpy(h->name, name, sizeof(h->name)); return (void *)(h + 1); } /* =================== Hunk_Alloc =================== */ void *Hunk_Alloc(int size) { return Hunk_AllocName(size, "unknown"); } int Hunk_LowMark(void) { return hunk_low_used; } void Hunk_FreeToLowMark(int mark) { if (mark < 0 || mark > hunk_low_used) { Sys_Error("Hunk_FreeToLowMark: bad mark %i, hunk_low_used = %i", mark, hunk_low_used); } memset(hunk_base + mark, 0, hunk_low_used - mark); hunk_low_used = mark; } int Hunk_HighMark(void) { if (hunk_tempactive) { hunk_tempactive = false; Hunk_FreeToHighMark(hunk_tempmark); } return hunk_high_used; } void Hunk_FreeToHighMark(int mark) { if (hunk_tempactive) { hunk_tempactive = false; Hunk_FreeToHighMark(hunk_tempmark); } if (mark < 0 || mark > hunk_high_used) { Sys_Error("Hunk_FreeToHighMark: bad mark %i", mark); } memset(hunk_base + hunk_size - hunk_high_used, 0, hunk_high_used - mark); hunk_high_used = mark; } /* =================== Hunk_HighAllocName =================== */ void *Hunk_HighAllocName(int size, char *name) { hunk_t *h; if (size < 0) { Sys_Error("Hunk_HighAllocName: bad size: %i", size); } if (hunk_tempactive) { Hunk_FreeToHighMark(hunk_tempmark); hunk_tempactive = false; } #ifdef PARANOID Hunk_Check(); #endif size = sizeof(hunk_t) + ((size + 15)&~15); if (hunk_size - hunk_low_used - hunk_high_used < size) { #ifdef SERVERONLY Sys_Error("Hunk_HighAllocName: Not enough RAM allocated. Try starting using \"-mem 64\" (or more) on the command line."); #else Sys_Error("Hunk_HighAllocName: Not enough RAM allocated. Try starting using \"-mem 128\" (or more) on the command line."); #endif } hunk_high_used += size; Cache_FreeHigh(hunk_high_used); h = (hunk_t *)(hunk_base + hunk_size - hunk_high_used); memset(h, 0, size); h->size = size; h->sentinel = HUNK_SENTINEL; strlcpy(h->name, name, sizeof(h->name)); return (void *)(h + 1); } /* ================= Hunk_TempAlloc Return space from the top of the hunk ================= */ void *Hunk_TempAlloc(int size) { void *buf; size = (size + 15) & ~15; if (hunk_tempactive) { Hunk_FreeToHighMark(hunk_tempmark); hunk_tempactive = false; } hunk_tempmark = Hunk_HighMark(); buf = Hunk_HighAllocName(size, "temp"); hunk_tempactive = true; return buf; } #ifdef SERVERONLY /* =============================================================================== CACHE MEMORY =============================================================================== */ typedef struct cache_system_s { int size; // including this header cache_user_t* user; char name[16]; struct cache_system_s *prev, *next; struct cache_system_s *lru_prev, *lru_next; // for LRU flushing } cache_system_t; cache_system_t* Cache_TryAlloc(int size, qbool nobottom); cache_system_t cache_head; /* =========== Cache_Move =========== */ void Cache_Move(cache_system_t* c) { cache_system_t* new_block; // we are clearing up space at the bottom, so only allocate it late new_block = Cache_TryAlloc(c->size, true); if (new_block) { memcpy(new_block + 1, c + 1, c->size - sizeof(cache_system_t)); new_block->user = c->user; memcpy(new_block->name, c->name, sizeof(new_block->name)); Cache_Free(c->user); new_block->user->data = (void*)(new_block + 1); } else { Cache_Free(c->user); // tough luck... } } /* ============ Cache_FreeLow Throw things out until the hunk can be expanded to the given point ============ */ void Cache_FreeLow(int new_low_hunk) { cache_system_t* c; while (1) { c = cache_head.next; if (c == &cache_head) { return; // nothing in cache at all } if ((byte*)c >= hunk_base + new_low_hunk) { return; // there is space to grow the hunk } Cache_Move(c); // reclaim the space } } /* ============ Cache_FreeHigh Throw things out until the hunk can be expanded to the given point ============ */ void Cache_FreeHigh(int new_high_hunk) { cache_system_t *c, *prev; prev = NULL; while (1) { c = cache_head.prev; if (c == &cache_head) { return; // nothing in cache at all } if ((byte*)c + c->size <= hunk_base + hunk_size - new_high_hunk) { return; // there is space to grow the hunk } if (c == prev) { Cache_Free(c->user); // didn't move out of the way } else { Cache_Move(c); // try to move it prev = c; } } } void Cache_UnlinkLRU(cache_system_t* cs) { if (!cs->lru_next || !cs->lru_prev) { Sys_Error("Cache_UnlinkLRU: NULL link"); } cs->lru_next->lru_prev = cs->lru_prev; cs->lru_prev->lru_next = cs->lru_next; cs->lru_prev = cs->lru_next = NULL; } void Cache_MakeLRU(cache_system_t* cs) { if (cs->lru_next || cs->lru_prev) { Sys_Error("Cache_MakeLRU: active link"); } cache_head.lru_next->lru_prev = cs; cs->lru_next = cache_head.lru_next; cs->lru_prev = &cache_head; cache_head.lru_next = cs; } /* ============ Cache_TryAlloc Looks for a free block of memory between the high and low hunk marks Size should already include the header and padding ============ */ cache_system_t* Cache_TryAlloc(int size, qbool nobottom) { cache_system_t *cs, *new_block; // is the cache completely empty? if (!nobottom && cache_head.prev == &cache_head) { if (hunk_size - hunk_high_used - hunk_low_used < size) { Sys_Error("Cache_TryAlloc: %i is greater than free hunk", size); } new_block = (cache_system_t*)(hunk_base + hunk_low_used); memset(new_block, 0, sizeof(*new_block)); new_block->size = size; cache_head.prev = cache_head.next = new_block; new_block->prev = new_block->next = &cache_head; Cache_MakeLRU(new_block); return new_block; } // search from the bottom up for space new_block = (cache_system_t*)(hunk_base + hunk_low_used); cs = cache_head.next; do { if (!nobottom || cs != cache_head.next) { if ((byte*)cs - (byte*)new_block >= size) { // found space memset(new_block, 0, sizeof(*new_block)); new_block->size = size; new_block->next = cs; new_block->prev = cs->prev; cs->prev->next = new_block; cs->prev = new_block; Cache_MakeLRU(new_block); return new_block; } } // continue looking new_block = (cache_system_t*)((byte*)cs + cs->size); cs = cs->next; } while (cs != &cache_head); // try to allocate one at the very end if (hunk_base + hunk_size - hunk_high_used - (byte*)new_block >= size) { memset(new_block, 0, sizeof(*new_block)); new_block->size = size; new_block->next = &cache_head; new_block->prev = cache_head.prev; cache_head.prev->next = new_block; cache_head.prev = new_block; Cache_MakeLRU(new_block); return new_block; } return NULL; // couldn't allocate } /* ============ Cache_Flush Throw everything out, so new data will be demand cached ============ */ void Cache_Flush(void) { while (cache_head.next != &cache_head) { Cache_Free(cache_head.next->user); // reclaim the space } #ifndef SERVERONLY Mod_ClearSimpleTextures(); #endif } /* ============ Cache_Print ============ */ void Cache_Print(void) { cache_system_t* cd; for (cd = cache_head.next; cd != &cache_head; cd = cd->next) { Con_Printf("%5.1f kB : %s\n", (cd->size / (float)(1024)), cd->name); } } /* ============ Cache_Report ============ */ void Cache_Report(void) { Con_Printf("%4.1f of %4.1f megabyte data cache free\n", (float)(hunk_size - hunk_high_used - hunk_low_used) / (1024 * 1024), (float)hunk_size / (1024 * 1024)); } /* ============ Cache_Init ============ */ void Cache_Init(void) { cache_head.next = cache_head.prev = &cache_head; cache_head.lru_next = cache_head.lru_prev = &cache_head; #ifndef WITH_DP_MEM // If DP mem is used then we can't add commands untill Cmd_Init() executed. Cache_Init_Commands(); #endif } void Cache_Init_Commands(void) { Cmd_AddCommand("flush", Cache_Flush); Cmd_AddCommand("cache_print", Cache_Print); Cmd_AddCommand("cache_report", Cache_Report); Cmd_AddCommand("hunk_print", Hunk_Print_f); } #ifndef WITH_DP_MEM /* ============== Cache_Free Frees the memory and removes it from the LRU list ============== */ void Cache_Free(cache_user_t* c) { cache_system_t* cs; if (!c->data) { Sys_Error("Cache_Free: not allocated"); } cs = ((cache_system_t*)c->data) - 1; cs->prev->next = cs->next; cs->next->prev = cs->prev; cs->next = cs->prev = NULL; c->data = NULL; Cache_UnlinkLRU(cs); } /* ============== Cache_Check ============== */ void* Cache_Check(cache_user_t* c) { cache_system_t* cs; if (!c->data) { return NULL; } cs = ((cache_system_t*)c->data) - 1; // move to head of LRU Cache_UnlinkLRU(cs); Cache_MakeLRU(cs); return c->data; } /* ============== Cache_Alloc ============== */ void* Cache_Alloc(cache_user_t* c, int size, const char* name) { cache_system_t* cs; if (c->data) { Sys_Error("Cache_Alloc: already allocated"); } if (size <= 0) { Sys_Error("Cache_Alloc: size %i", size); } size = (size + sizeof(cache_system_t) + 15) & ~15; // find memory for it while (1) { cs = Cache_TryAlloc(size, false); if (cs) { strlcpy(cs->name, name, sizeof(cs->name)); c->data = (void*)(cs + 1); cs->user = c; break; } // free the least recently used cahedat if (cache_head.lru_prev == &cache_head) { Sys_Error("Cache_Alloc: out of memory"); } // not enough memory at all Cache_Free(cache_head.lru_prev->user); } return Cache_Check(c); } #endif // !WITH_DP_MEM #endif // SERVERONLY //============================================================================ /* ======================== Memory_Init ======================== */ void Memory_Init(void *buf, int size) { hunk_base = (byte *)buf; hunk_size = size; hunk_low_used = 0; hunk_high_used = 0; Cache_Init(); } mvdsv-0.35/src/zone.h000066400000000000000000000055571427146041000145260ustar00rootroot00000000000000/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #ifndef __ZONE_H__ #define __ZONE_H__ /* memory allocation H_??? The hunk manages the entire memory block given to quake. It must be contiguous. Memory can be allocated from either the low or high end in a stack fashion. The only way memory is released is by resetting one of the pointers. Hunk allocations should be given a name, so the Hunk_Print () function can display usage. Hunk allocations are guaranteed to be 16 byte aligned. The video buffers are allocated high to avoid leaving a hole underneath server allocations when changing to a higher video mode. Cache_??? Cache memory is for objects that can be dynamically loaded and can usefully stay persistent between levels. The size of the cache fluctuates from level to level. To allocate a cachable object Temp_??? Temp memory is used for file loading and surface caching. The size of the cache memory is adjusted so that there is a minimum of 512k remaining for temp memory. ------ Top of Memory ------- high hunk allocations <--- high hunk reset point held by vid video buffer z buffer surface cache <--- high hunk used cachable memory <--- low hunk used client and server low hunk allocations <-- low hunk reset point held by host startup hunk allocations ----- Bottom of Memory ----- */ void Memory_Init (void *buf, int size); void *Hunk_Alloc (int size); // returns 0 filled memory void *Hunk_AllocName (int size, const char *name); void *Hunk_HighAllocName (int size, char *name); int Hunk_LowMark (void); void Hunk_FreeToLowMark (int mark); int Hunk_HighMark (void); void Hunk_FreeToHighMark (int mark); void *Hunk_TempAlloc (int size); void Hunk_Check (void); #ifdef SERVERONLY typedef struct cache_user_s { void *data; } cache_user_t; void Cache_Flush (void); void *Cache_Check (cache_user_t *c); // returns the cached data, and moves to the head of the LRU list // if present, otherwise returns NULL void Cache_Free(cache_user_t *c); void *Cache_Alloc (cache_user_t *c, int size, const char *name); // Returns NULL if all purgeable data was tossed and there still // wasn't enough room. void Cache_Report (void); void Cache_Init_Commands (void); #endif #endif /* !__ZONE_H__ */ mvdsv-0.35/tools/000077500000000000000000000000001427146041000137375ustar00rootroot00000000000000mvdsv-0.35/tools/cross-cmake/000077500000000000000000000000001427146041000161465ustar00rootroot00000000000000mvdsv-0.35/tools/cross-cmake/linux-aarch64.cmake000066400000000000000000000010741427146041000215370ustar00rootroot00000000000000# the name of the target operating system set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm64) # which compilers to use for C and C++ set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) # adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) mvdsv-0.35/tools/cross-cmake/linux-amd64.cmake000066400000000000000000000010161427146041000212160ustar00rootroot00000000000000# the name of the target operating system set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR amd64) # which compilers to use for C and C++ set(CMAKE_C_COMPILER x86_64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER x86_64-linux-gnu-g++) # adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) mvdsv-0.35/tools/cross-cmake/linux-armhf.cmake000066400000000000000000000010771427146041000214070ustar00rootroot00000000000000# the name of the target operating system set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) # which compilers to use for C and C++ set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) #set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++) # adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) mvdsv-0.35/tools/cross-cmake/linux-i686.cmake000066400000000000000000000012261427146041000210020ustar00rootroot00000000000000# the name of the target operating system set(CMAKE_SYSTEM_NAME Linux) # which compilers to use for C and C++ set(CMAKE_C_COMPILER i686-linux-gnu-gcc) # Turn off excess precision with -mfpmath=sse -msse2, otherwise KTX compiled with bots will hang. set(CMAKE_C_FLAGS "-m32 -mfpmath=sse -msse2") #set(CMAKE_CXX_COMPILER x86_64-linux-gnu-g++) #set(CMAKE_CXX_FLAGS -m32) # adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) mvdsv-0.35/tools/cross-cmake/windows-x64.cmake000066400000000000000000000010441427146041000212600ustar00rootroot00000000000000# the name of the target operating system set(CMAKE_SYSTEM_NAME Windows) # which compilers to use for C and C++ set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) # adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) mvdsv-0.35/tools/cross-cmake/windows-x86.cmake000066400000000000000000000012511427146041000212640ustar00rootroot00000000000000# the name of the target operating system set(CMAKE_SYSTEM_NAME Windows) # which compilers to use for C and C++ set(CMAKE_C_COMPILER i686-w64-mingw32-gcc) # Turn off excess precision with -mfpmath=sse -msse2, otherwise KTX compiled with bots will hang. set(CMAKE_C_FLAGS "-mfpmath=sse -msse2") set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) set(CMAKE_RC_COMPILER i686-w64-mingw32-windres) # adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) mvdsv-0.35/tools/old_mvs_files/000077500000000000000000000000001427146041000165645ustar00rootroot00000000000000mvdsv-0.35/tools/old_mvs_files/mvdsv_vc2017.sln000066400000000000000000000033051427146041000214440ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30309.148 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "qwsv", "mvdsv_vc2017.vcxproj", "{617E4627-FBC6-4834-9C6B-B178EACE973E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Debug-NoAsm|Win32 = Debug-NoAsm|Win32 debug-noweb|Win32 = debug-noweb|Win32 Release|Win32 = Release|Win32 Release-NoAsm|Win32 = Release-NoAsm|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {617E4627-FBC6-4834-9C6B-B178EACE973E}.Debug|Win32.ActiveCfg = Debug|Win32 {617E4627-FBC6-4834-9C6B-B178EACE973E}.Debug|Win32.Build.0 = Debug|Win32 {617E4627-FBC6-4834-9C6B-B178EACE973E}.Debug-NoAsm|Win32.ActiveCfg = Debug-NoAsm|Win32 {617E4627-FBC6-4834-9C6B-B178EACE973E}.Debug-NoAsm|Win32.Build.0 = Debug-NoAsm|Win32 {617E4627-FBC6-4834-9C6B-B178EACE973E}.debug-noweb|Win32.ActiveCfg = debug-noweb|Win32 {617E4627-FBC6-4834-9C6B-B178EACE973E}.debug-noweb|Win32.Build.0 = debug-noweb|Win32 {617E4627-FBC6-4834-9C6B-B178EACE973E}.Release|Win32.ActiveCfg = Release|Win32 {617E4627-FBC6-4834-9C6B-B178EACE973E}.Release|Win32.Build.0 = Release|Win32 {617E4627-FBC6-4834-9C6B-B178EACE973E}.Release-NoAsm|Win32.ActiveCfg = Release-NoAsm|Win32 {617E4627-FBC6-4834-9C6B-B178EACE973E}.Release-NoAsm|Win32.Build.0 = Release-NoAsm|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F77C5407-8039-4548-98D2-D3AC9D539E7D} EndGlobalSection EndGlobal mvdsv-0.35/tools/old_mvs_files/mvdsv_vc2017.vcxproj000066400000000000000000003323361427146041000223540ustar00rootroot00000000000000 Debug-NoAsm Win32 debug-noweb Win32 Debug Win32 Release-NoAsm Win32 Release Win32 qwsv {617E4627-FBC6-4834-9C6B-B178EACE973E} qwsv 10.0 Application false MultiByte v142 Application false MultiByte v142 Application false MultiByte v142 Application false MultiByte true v142 Application false MultiByte true v142 <_ProjectFileVersion>10.0.30319.1 Release\ Release\ Release\ Release\ *.asm%3b*.bsc%3b*.cod%3b*.dep%3b*.idb%3b*.ilk%3b*.obj%3b*.pgc%3b*.pgd%3b*.res%3b*.rsp%3b*.sbr%3b*.tlb%3b*.tlh%3b*.tli%3b*.tmp%3b*.manifest*%3b$(TargetPath) *.asm%3b*.bsc%3b*.cod%3b*.dep%3b*.idb%3b*.ilk%3b*.obj%3b*.pgc%3b*.pgd%3b*.res%3b*.rsp%3b*.sbr%3b*.tlb%3b*.tlh%3b*.tli%3b*.tmp%3b*.manifest*%3b$(TargetPath) false false true true true true Debug\ Debug\ Debug\ Debug\ Debug\ Debug\ *.asm%3b*.bsc%3b*.cod%3b*.dep%3b*.idb%3b*.ilk%3b*.obj%3b*.pgc%3b*.pgd%3b*.res%3b*.rsp%3b*.sbr%3b*.tlb%3b*.tlh%3b*.tli%3b*.tmp%3b*.manifest*%3b$(TargetPath) *.asm%3b*.bsc%3b*.cod%3b*.dep%3b*.idb%3b*.ilk%3b*.obj%3b*.pgc%3b*.pgd%3b*.res%3b*.rsp%3b*.sbr%3b*.tlb%3b*.tlh%3b*.tli%3b*.tmp%3b*.manifest*%3b$(TargetPath) *.asm%3b*.bsc%3b*.cod%3b*.dep%3b*.idb%3b*.ilk%3b*.obj%3b*.pgc%3b*.pgd%3b*.res%3b*.rsp%3b*.sbr%3b*.tlb%3b*.tlh%3b*.tli%3b*.tmp%3b*.manifest*%3b$(TargetPath) true true true true true true true true true AllRules.ruleset AllRules.ruleset AllRules.ruleset AllRules.ruleset AllRules.ruleset C:\Projects\quake\mvdsv\dependencies\include;$(IncludePath) C:\Projects\quake\mvdsv\dependencies\lib;$(LibraryPath) C:\Projects\quake\mvdsv\dependencies\include;$(IncludePath) C:\Projects\quake\mvdsv\dependencies\lib;$(LibraryPath) C:\Projects\quake\mvdsv\dependencies\include;$(IncludePath) C:\Projects\quake\mvdsv\dependencies\lib;$(LibraryPath) C:\Projects\quake\mvdsv\dependencies\include;$(IncludePath) C:\Projects\quake\mvdsv\dependencies\lib;$(LibraryPath) C:\Projects\quake\mvdsv\dependencies\include;$(IncludePath) C:\Projects\quake\mvdsv\dependencies\lib;$(LibraryPath) Release\mvdsv.tlb MaxSpeed AnySuitable true Speed true WWW_INTEGRATION;_CONSOLE;CURL_STATICLIB;NDEBUG;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;_CRT_DISABLE_PERFCRIT_LOCKS;id386;USE_PR2;SERVERONLY;WITH_NQPROGS;__LITTLE_ENDIAN__Q__;%(PreprocessorDefinitions) true MultiThreadedDebugDLL false /J $(TargetDir)$(TargetName).pch $(IntDir) $(IntDir) $(TargetDir)$(TargetName).pdb $(IntDir) Level3 true CompileAsC NDEBUG;%(PreprocessorDefinitions) 0x0409 wsock32.lib;winmm.lib;%(AdditionalDependencies) mvdsv.exe true false $(TargetDir)$(TargetName).pdb true $(TargetDir)$(TargetName).map Console true true true true MachineX86 false true Release\mvdsv.tlb MaxSpeed AnySuitable true Speed true WWW_INTEGRATION;_CONSOLE;CURL_STATICLIB;NDEBUG;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;_CRT_DISABLE_PERFCRIT_LOCKS;USE_PR2;SERVERONLY;WITH_NQPROGS;__LITTLE_ENDIAN__Q__;%(PreprocessorDefinitions) true MultiThreadedDebugDLL false /J $(TargetDir)$(TargetName).pch $(IntDir) $(IntDir) $(TargetDir)$(TargetName).pdb $(IntDir) Level3 true CompileAsC NDEBUG;%(PreprocessorDefinitions) 0x0409 wsock32.lib;winmm.lib;%(AdditionalDependencies) mvdsv.exe true false $(TargetDir)$(TargetName).pdb true $(TargetDir)$(TargetName).map Console true true true true MachineX86 false true Debug\mvdsv.tlb Disabled WWW_INTEGRATION;_CONSOLE;CURL_STATICLIB;_DEBUG;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;_CRT_DISABLE_PERFCRIT_LOCKS;id386;USE_PR2;SERVERONLY;WITH_NQPROGS;__LITTLE_ENDIAN__Q__;%(PreprocessorDefinitions) true EnableFastChecks false MultiThreadedDebugDLL true /J $(TargetDir)$(TargetName).pch All $(IntDir) $(IntDir) $(IntDir)$(TargetName).pdb true $(IntDir) Level3 true EditAndContinue CompileAsC _DEBUG;%(PreprocessorDefinitions) 0x0409 wsock32.lib;winmm.lib;%(AdditionalDependencies) mvdsv-debug.exe true true true $(TargetDir)$(TargetName).pdb true $(TargetDir)$(TargetName).map false Console false MachineX86 false false true Debug\mvdsv.tlb Disabled WWW_INTEGRATION;_CONSOLE;CURL_STATICLIB;_DEBUG;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;_CRT_DISABLE_PERFCRIT_LOCKS;USE_PR2;SERVERONLY;WITH_NQPROGS;__LITTLE_ENDIAN__Q__;%(PreprocessorDefinitions) true EnableFastChecks false MultiThreadedDebugDLL true /J $(TargetDir)$(TargetName).pch All $(IntDir) $(IntDir) $(IntDir)$(TargetName).pdb true $(IntDir) Level3 true EditAndContinue CompileAsC _DEBUG;%(PreprocessorDefinitions) 0x0409 wsock32.lib;winmm.lib;%(AdditionalDependencies) mvdsv-debug.exe true true true $(TargetDir)$(TargetName).pdb true $(TargetDir)$(TargetName).map false Console false MachineX86 false false true Debug\mvdsv.tlb Disabled _CONSOLE;CURL_STATICLIB;_DEBUG;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;_CRT_DISABLE_PERFCRIT_LOCKS;USE_PR2;SERVERONLY;WITH_NQPROGS;__LITTLE_ENDIAN__Q__;%(PreprocessorDefinitions) true EnableFastChecks false MultiThreadedDebugDLL true /J $(TargetDir)$(TargetName).pch All $(IntDir) $(IntDir) $(IntDir)$(TargetName).pdb true $(IntDir) Level3 true EditAndContinue CompileAsC _DEBUG;%(PreprocessorDefinitions) 0x0409 wsock32.lib;winmm.lib;%(AdditionalDependencies) mvdsv-debug.exe true true true $(TargetDir)$(TargetName).pdb true $(TargetDir)$(TargetName).map false Console false MachineX86 false false true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true Disabled Disabled Disabled %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true MaxSpeed MaxSpeed %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true cl /nologo /EP > "$(OutDir)%(Filename).spp" "%(FullPath)" gas2masm < "$(OutDir)%(Filename).spp" >"$(OutDir)%(Filename).asm" ml /nologo /c /Cp /coff /Fo"$(OutDir)%(Filename).obj" /Zm /Zi "$(OutDir)%(Filename).asm" del "$(OutDir)%(Filename).spp" cl /nologo /EP > "$(OutDir)%(Filename).spp" "%(FullPath)" gas2masm < "$(OutDir)%(Filename).spp" >"$(OutDir)%(Filename).asm" ml /nologo /c /Cp /coff /Fo"$(OutDir)%(Filename).obj" /Zm /Zi "$(OutDir)%(Filename).asm" del "$(OutDir)%(Filename).spp" cl /nologo /EP > "$(OutDir)%(Filename).spp" "%(FullPath)" gas2masm < "$(OutDir)%(Filename).spp" >"$(OutDir)%(Filename).asm" ml /nologo /c /Cp /coff /Fo"$(OutDir)%(Filename).obj" /Zm /Zi "$(OutDir)%(Filename).asm" del "$(OutDir)%(Filename).spp" $(OutDir)%(Filename).obj;%(Outputs) $(OutDir)%(Filename).obj;%(Outputs) $(OutDir)%(Filename).obj;%(Outputs) cl /nologo /EP > "$(OutDir)%(Filename).spp" "%(FullPath)" gas2masm < "$(OutDir)%(Filename).spp" >"$(OutDir)%(Filename).asm" ml /nologo /c /Cp /coff /Fo"$(OutDir)%(Filename).obj" /Zm /Zi "$(OutDir)%(Filename).asm" del "$(OutDir)%(Filename).spp" cl /nologo /EP > "$(OutDir)%(Filename).spp" "%(FullPath)" gas2masm < "$(OutDir)%(Filename).spp" >"$(OutDir)%(Filename).asm" ml /nologo /c /Cp /coff /Fo"$(OutDir)%(Filename).obj" /Zm /Zi "$(OutDir)%(Filename).asm" del "$(OutDir)%(Filename).spp" $(OutDir)%(Filename).obj;%(Outputs) $(OutDir)%(Filename).obj;%(Outputs) true true true cl /nologo /EP > "$(OutDir)%(Filename).spp" "%(FullPath)" gas2masm < "$(OutDir)%(Filename).spp" >"$(OutDir)%(Filename).asm" ml /nologo /c /Cp /coff /Fo"$(OutDir)%(Filename).obj" /Zm /Zi "$(OutDir)%(Filename).asm" del "$(OutDir)%(Filename).spp" cl /nologo /EP > "$(OutDir)%(Filename).spp" "%(FullPath)" gas2masm < "$(OutDir)%(Filename).spp" >"$(OutDir)%(Filename).asm" ml /nologo /c /Cp /coff /Fo"$(OutDir)%(Filename).obj" /Zm /Zi "$(OutDir)%(Filename).asm" del "$(OutDir)%(Filename).spp" cl /nologo /EP > "$(OutDir)%(Filename).spp" "%(FullPath)" gas2masm < "$(OutDir)%(Filename).spp" >"$(OutDir)%(Filename).asm" ml /nologo /c /Cp /coff /Fo"$(OutDir)%(Filename).obj" /Zm /Zi "$(OutDir)%(Filename).asm" del "$(OutDir)%(Filename).spp" $(OutDir)%(Filename).obj;%(Outputs) $(OutDir)%(Filename).obj;%(Outputs) $(OutDir)%(Filename).obj;%(Outputs) cl /nologo /EP > "$(OutDir)%(Filename).spp" "%(FullPath)" gas2masm < "$(OutDir)%(Filename).spp" >"$(OutDir)%(Filename).asm" ml /nologo /c /Cp /coff /Fo"$(OutDir)%(Filename).obj" /Zm /Zi "$(OutDir)%(Filename).asm" del "$(OutDir)%(Filename).spp" cl /nologo /EP > "$(OutDir)%(Filename).spp" "%(FullPath)" gas2masm < "$(OutDir)%(Filename).spp" >"$(OutDir)%(Filename).asm" ml /nologo /c /Cp /coff /Fo"$(OutDir)%(Filename).obj" /Zm /Zi "$(OutDir)%(Filename).asm" del "$(OutDir)%(Filename).spp" $(OutDir)%(Filename).obj;%(Outputs) $(OutDir)%(Filename).obj;%(Outputs) true true true