From 00210d37de8113ab88a88d659681d6fbd913777a Mon Sep 17 00:00:00 2001 From: iiTzArcur Date: Sat, 16 Mar 2024 17:46:38 +0100 Subject: [PATCH] allow arm builds --- Dockerfile | 57 +- .pdm-python => bf2-worker/.pdm-python | 0 pdm.lock => bf2-worker/pdm.lock | 0 bf2-worker/poetry.lock | 434 ++++ pyproject.toml => bf2-worker/pyproject.toml | 10 +- serverList.py => bf2-worker/serverList.py | 13 +- build.Dockerfile | 11 + ealist/Makefile | 7 + ealist/ealist.c | 4 +- gslist-2 | Bin 203312 -> 0 bytes gslist/.gitignore | 58 + gslist/LICENSE.md | 340 +++ gslist/Makefile | 27 + gslist/README.md | 5 + gslist/gpl.txt | 340 +++ gslist/gslist.txt | 710 ++++++ gslist/src/compa.bat | 25 + gslist/src/countries.h | 307 +++ gslist/src/enctype1_decoder.c | 352 +++ gslist/src/enctype2_decoder.c | 113 + gslist/src/enctype_shared.c | 181 ++ gslist/src/enctypex_decoder.c | 685 +++++ gslist/src/gscfg.h | 398 +++ gslist/src/gshttp.h | 40 + gslist/src/gslegacy.h | 302 +++ gslist/src/gslist.c | 1231 +++++++++ gslist/src/gslist.ico | Bin 0 -> 3262 bytes gslist/src/gslist_icon.rc | 22 + gslist/src/gsmsalg.h | 190 ++ gslist/src/gsmyfunc.h | 933 +++++++ gslist/src/gsnatneg.c | 962 +++++++ gslist/src/gsshow.h | 253 ++ gslist/src/gssql.h | 219 ++ gslist/src/gsweb.h | 2506 +++++++++++++++++++ gslist/src/gswskin.h | 169 ++ gslist/src/gswtray.h | 108 + gslist/src/multi_query.h | 977 ++++++++ gslist/src/mydownlib.c | 1660 ++++++++++++ gslist/src/mydownlib.h | 108 + gslist/src/show_dump.h | 67 + gslist/src/stristr.c | 32 + gslist/src/winerr.h | 74 + poetry.lock | 422 ---- 43 files changed, 13902 insertions(+), 450 deletions(-) rename .pdm-python => bf2-worker/.pdm-python (100%) rename pdm.lock => bf2-worker/pdm.lock (100%) create mode 100644 bf2-worker/poetry.lock rename pyproject.toml => bf2-worker/pyproject.toml (73%) rename serverList.py => bf2-worker/serverList.py (96%) create mode 100644 build.Dockerfile create mode 100644 ealist/Makefile delete mode 100755 gslist-2 create mode 100644 gslist/.gitignore create mode 100644 gslist/LICENSE.md create mode 100644 gslist/Makefile create mode 100644 gslist/README.md create mode 100644 gslist/gpl.txt create mode 100644 gslist/gslist.txt create mode 100644 gslist/src/compa.bat create mode 100644 gslist/src/countries.h create mode 100644 gslist/src/enctype1_decoder.c create mode 100644 gslist/src/enctype2_decoder.c create mode 100644 gslist/src/enctype_shared.c create mode 100644 gslist/src/enctypex_decoder.c create mode 100644 gslist/src/gscfg.h create mode 100644 gslist/src/gshttp.h create mode 100644 gslist/src/gslegacy.h create mode 100644 gslist/src/gslist.c create mode 100644 gslist/src/gslist.ico create mode 100644 gslist/src/gslist_icon.rc create mode 100644 gslist/src/gsmsalg.h create mode 100644 gslist/src/gsmyfunc.h create mode 100644 gslist/src/gsnatneg.c create mode 100644 gslist/src/gsshow.h create mode 100644 gslist/src/gssql.h create mode 100644 gslist/src/gsweb.h create mode 100644 gslist/src/gswskin.h create mode 100644 gslist/src/gswtray.h create mode 100644 gslist/src/multi_query.h create mode 100644 gslist/src/mydownlib.c create mode 100644 gslist/src/mydownlib.h create mode 100644 gslist/src/show_dump.h create mode 100644 gslist/src/stristr.c create mode 100644 gslist/src/winerr.h delete mode 100644 poetry.lock diff --git a/Dockerfile b/Dockerfile index 412c7ac..09676ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,51 @@ +FROM ubuntu:23.04 AS build-stage + +RUN apt-get update && apt-get install -y libgeoip-dev libmysqlclient-dev build-essential && apt-get clean + +COPY gslist /gslist + +WORKDIR /gslist + +RUN make + +FROM python:3.12 AS requirements-stage +WORKDIR /tmp + +RUN pip install poetry poetry-plugin-export +COPY ./bf2-worker/pyproject.toml ./poetry.lock* /tmp/ +RUN poetry export -f requirements.txt --output requirements.txt --without-hashes + FROM ubuntu:23.04 -RUN apt-get update -RUN apt-get install -y wget software-properties-common gnupg2 xvfb -RUN dpkg --add-architecture i386 -RUN mkdir -pm755 /etc/apt/keyrings -RUN wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key -RUN wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/lunar/winehq-lunar.sources -RUN apt-get update -RUN apt-get install --no-install-recommends -y winehq-stable winetricks winbind python3 curl python3-poetry python-is-python3 +# AMD64 build for bfbc2 +# RUN apt-get install -y wget software-properties-common gnupg2 xvfb +# RUN dpkg --add-architecture i386 +# RUN mkdir -pm755 /etc/apt/keyrings +# RUN wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key +# RUN wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/lunar/winehq-lunar.sources +# RUN apt-get update +# RUN apt-get install --no-install-recommends -y winehq-stable winetricks winbind + +# ARM build for bfbc2 +# WORKDIR /temp +# ADD https://github.com/AndreRH/hangover/releases/download/hangover-9.3/hangover_9.3_ubuntu2310_mantic_arm64.tar /temp +# RUN tar -xvf hangover_9.3_ubuntu2310_mantic_arm64.tar +# RUN apt-get update && apt-get install -y ./hangover-wine_9.3~mantic_arm64.deb -ENV WINEDEBUG=fixme-all -ENV DISPLAY :0 +RUN apt-get update && apt-get install -y python3 curl python3-pip python3-venv python-is-python3 && apt-get clean +# ENV WINEDEBUG=fixme-all +# ENV DISPLAY :0 -COPY ./pyproject.toml /pyproject.toml -COPY ./poetry.lock /poetry.lock -RUN poetry config virtualenvs.create false +# Set pip env +RUN python3 -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" -RUN poetry install --only main +COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt -COPY . /bf2-api +COPY ./bf2-worker /bf2-api +COPY ./ealist /bf2-api/ealist +COPY --from=build-stage /gslist/gslist /bf2-api/gslist-2 WORKDIR /bf2-api ENTRYPOINT [ "python3.11", "serverList.py" ] \ No newline at end of file diff --git a/.pdm-python b/bf2-worker/.pdm-python similarity index 100% rename from .pdm-python rename to bf2-worker/.pdm-python diff --git a/pdm.lock b/bf2-worker/pdm.lock similarity index 100% rename from pdm.lock rename to bf2-worker/pdm.lock diff --git a/bf2-worker/poetry.lock b/bf2-worker/poetry.lock new file mode 100644 index 0000000..7fd085e --- /dev/null +++ b/bf2-worker/poetry.lock @@ -0,0 +1,434 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "aiofiles" +version = "23.2.1" +description = "File support for asyncio." +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"}, + {file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"}, +] + +[[package]] +name = "blinker" +version = "1.7.0" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.8" +files = [ + {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, + {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "dnspython" +version = "2.5.0" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dnspython-2.5.0-py3-none-any.whl", hash = "sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6"}, + {file = "dnspython-2.5.0.tar.gz", hash = "sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=5.0.3)", "mypy (>=1.0.1)", "pylint (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "sphinx (>=7.0.0)", "twine (>=4.0.0)", "wheel (>=0.41.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.25.1)"] +doq = ["aioquic (>=0.9.20)"] +idna = ["idna (>=2.1)"] +trio = ["trio (>=0.14)"] +wmi = ["wmi (>=1.5.1)"] + +[[package]] +name = "flask" +version = "3.0.1" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask-3.0.1-py3-none-any.whl", hash = "sha256:ca631a507f6dfe6c278ae20112cea3ff54ff2216390bf8880f6b035a5354af13"}, + {file = "flask-3.0.1.tar.gz", hash = "sha256:6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=3.0.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "h2" +version = "4.1.0" +description = "HTTP/2 State-Machine based protocol implementation" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, + {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, +] + +[package.dependencies] +hpack = ">=4.0,<5" +hyperframe = ">=6.0,<7" + +[[package]] +name = "hpack" +version = "4.0.0" +description = "Pure-Python HPACK header compression" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, + {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, +] + +[[package]] +name = "hypercorn" +version = "0.16.0" +description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn" +optional = false +python-versions = ">=3.8" +files = [ + {file = "hypercorn-0.16.0-py3-none-any.whl", hash = "sha256:929e45c4acde3fbf7c58edf55336d30a009d2b4cb1f1eb96e6a515d61b663f58"}, + {file = "hypercorn-0.16.0.tar.gz", hash = "sha256:3b17d1dcf4992c1f262d9f9dd799c374125d0b9a8e40e1e2d11e2938b0adfe03"}, +] + +[package.dependencies] +h11 = "*" +h2 = ">=3.1.0" +priority = "*" +wsproto = ">=0.14.0" + +[package.extras] +docs = ["pydata_sphinx_theme", "sphinxcontrib_mermaid"] +h3 = ["aioquic (>=0.9.0,<1.0)"] +trio = ["exceptiongroup (>=1.1.0)", "trio (>=0.22.0)"] +uvloop = ["uvloop"] + +[[package]] +name = "hyperframe" +version = "6.0.1" +description = "HTTP/2 framing layer for Python" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, + {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, +] + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.7" +files = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] + +[[package]] +name = "jinja2" +version = "3.1.3" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.4" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, + {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, +] + +[[package]] +name = "priority" +version = "2.0.0" +description = "A pure-Python implementation of the HTTP/2 priority tree" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"}, + {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"}, +] + +[[package]] +name = "pymongo" +version = "4.6.1" +description = "Python driver for MongoDB " +optional = false +python-versions = ">=3.7" +files = [ + {file = "pymongo-4.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4344c30025210b9fa80ec257b0e0aab5aa1d5cca91daa70d82ab97b482cc038e"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux1_i686.whl", hash = "sha256:1c5654bb8bb2bdb10e7a0bc3c193dd8b49a960b9eebc4381ff5a2043f4c3c441"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:eaf2f65190c506def2581219572b9c70b8250615dc918b3b7c218361a51ec42e"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:262356ea5fcb13d35fb2ab6009d3927bafb9504ef02339338634fffd8a9f1ae4"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:2dd2f6960ee3c9360bed7fb3c678be0ca2d00f877068556785ec2eb6b73d2414"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:ff925f1cca42e933376d09ddc254598f8c5fcd36efc5cac0118bb36c36217c41"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:3cadf7f4c8e94d8a77874b54a63c80af01f4d48c4b669c8b6867f86a07ba994f"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55dac73316e7e8c2616ba2e6f62b750918e9e0ae0b2053699d66ca27a7790105"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:154b361dcb358ad377d5d40df41ee35f1cc14c8691b50511547c12404f89b5cb"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2940aa20e9cc328e8ddeacea8b9a6f5ddafe0b087fedad928912e787c65b4909"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:010bc9aa90fd06e5cc52c8fac2c2fd4ef1b5f990d9638548dde178005770a5e8"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e470fa4bace5f50076c32f4b3cc182b31303b4fefb9b87f990144515d572820b"}, + {file = "pymongo-4.6.1-cp310-cp310-win32.whl", hash = "sha256:da08ea09eefa6b960c2dd9a68ec47949235485c623621eb1d6c02b46765322ac"}, + {file = "pymongo-4.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:13d613c866f9f07d51180f9a7da54ef491d130f169e999c27e7633abe8619ec9"}, + {file = "pymongo-4.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a0ae7a48a6ef82ceb98a366948874834b86c84e288dbd55600c1abfc3ac1d88"}, + {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bd94c503271e79917b27c6e77f7c5474da6930b3fb9e70a12e68c2dff386b9a"}, + {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d4ccac3053b84a09251da8f5350bb684cbbf8c8c01eda6b5418417d0a8ab198"}, + {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:349093675a2d3759e4fb42b596afffa2b2518c890492563d7905fac503b20daa"}, + {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88beb444fb438385e53dc9110852910ec2a22f0eab7dd489e827038fdc19ed8d"}, + {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8e62d06e90f60ea2a3d463ae51401475568b995bafaffd81767d208d84d7bb1"}, + {file = "pymongo-4.6.1-cp311-cp311-win32.whl", hash = "sha256:5556e306713e2522e460287615d26c0af0fe5ed9d4f431dad35c6624c5d277e9"}, + {file = "pymongo-4.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:b10d8cda9fc2fcdcfa4a000aa10413a2bf8b575852cd07cb8a595ed09689ca98"}, + {file = "pymongo-4.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b435b13bb8e36be11b75f7384a34eefe487fe87a6267172964628e2b14ecf0a7"}, + {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e438417ce1dc5b758742e12661d800482200b042d03512a8f31f6aaa9137ad40"}, + {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b47ebd89e69fbf33d1c2df79759d7162fc80c7652dacfec136dae1c9b3afac7"}, + {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbed8cccebe1169d45cedf00461b2842652d476d2897fd1c42cf41b635d88746"}, + {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30a9e06041fbd7a7590693ec5e407aa8737ad91912a1e70176aff92e5c99d20"}, + {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8729dbf25eb32ad0dc0b9bd5e6a0d0b7e5c2dc8ec06ad171088e1896b522a74"}, + {file = "pymongo-4.6.1-cp312-cp312-win32.whl", hash = "sha256:3177f783ae7e08aaf7b2802e0df4e4b13903520e8380915e6337cdc7a6ff01d8"}, + {file = "pymongo-4.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:00c199e1c593e2c8b033136d7a08f0c376452bac8a896c923fcd6f419e07bdd2"}, + {file = "pymongo-4.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6dcc95f4bb9ed793714b43f4f23a7b0c57e4ef47414162297d6f650213512c19"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:13552ca505366df74e3e2f0a4f27c363928f3dff0eef9f281eb81af7f29bc3c5"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:77e0df59b1a4994ad30c6d746992ae887f9756a43fc25dec2db515d94cf0222d"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3a7f02a58a0c2912734105e05dedbee4f7507e6f1bd132ebad520be0b11d46fd"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:026a24a36394dc8930cbcb1d19d5eb35205ef3c838a7e619e04bd170713972e7"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:3b287e814a01deddb59b88549c1e0c87cefacd798d4afc0c8bd6042d1c3d48aa"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:9a710c184ba845afb05a6f876edac8f27783ba70e52d5eaf939f121fc13b2f59"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:30b2c9caf3e55c2e323565d1f3b7e7881ab87db16997dc0cbca7c52885ed2347"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff62ba8ff70f01ab4fe0ae36b2cb0b5d1f42e73dfc81ddf0758cd9f77331ad25"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:547dc5d7f834b1deefda51aedb11a7af9c51c45e689e44e14aa85d44147c7657"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1de3c6faf948f3edd4e738abdb4b76572b4f4fdfc1fed4dad02427e70c5a6219"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2831e05ce0a4df10c4ac5399ef50b9a621f90894c2a4d2945dc5658765514ed"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:144a31391a39a390efce0c5ebcaf4bf112114af4384c90163f402cec5ede476b"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33bb16a07d3cc4e0aea37b242097cd5f7a156312012455c2fa8ca396953b11c4"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b7b1a83ce514700276a46af3d9e481ec381f05b64939effc9065afe18456a6b9"}, + {file = "pymongo-4.6.1-cp37-cp37m-win32.whl", hash = "sha256:3071ec998cc3d7b4944377e5f1217c2c44b811fae16f9a495c7a1ce9b42fb038"}, + {file = "pymongo-4.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2346450a075625c4d6166b40a013b605a38b6b6168ce2232b192a37fb200d588"}, + {file = "pymongo-4.6.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:061598cbc6abe2f382ab64c9caa83faa2f4c51256f732cdd890bcc6e63bfb67e"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d483793a384c550c2d12cb794ede294d303b42beff75f3b3081f57196660edaf"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f9756f1d25454ba6a3c2f1ef8b7ddec23e5cdeae3dc3c3377243ae37a383db00"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:1ed23b0e2dac6f84f44c8494fbceefe6eb5c35db5c1099f56ab78fc0d94ab3af"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:3d18a9b9b858ee140c15c5bfcb3e66e47e2a70a03272c2e72adda2482f76a6ad"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:c258dbacfff1224f13576147df16ce3c02024a0d792fd0323ac01bed5d3c545d"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:f7acc03a4f1154ba2643edeb13658d08598fe6e490c3dd96a241b94f09801626"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:76013fef1c9cd1cd00d55efde516c154aa169f2bf059b197c263a255ba8a9ddf"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0e6a6c807fa887a0c51cc24fe7ea51bb9e496fe88f00d7930063372c3664c3"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd1fa413f8b9ba30140de198e4f408ffbba6396864c7554e0867aa7363eb58b2"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d219b4508f71d762368caec1fc180960569766049bbc4d38174f05e8ef2fe5b"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b81ecf18031998ad7db53b960d1347f8f29e8b7cb5ea7b4394726468e4295e"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56816e43c92c2fa8c11dc2a686f0ca248bea7902f4a067fa6cbc77853b0f041e"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef801027629c5b511cf2ba13b9be29bfee36ae834b2d95d9877818479cdc99ea"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d4c2be9760b112b1caf649b4977b81b69893d75aa86caf4f0f398447be871f3c"}, + {file = "pymongo-4.6.1-cp38-cp38-win32.whl", hash = "sha256:39d77d8bbb392fa443831e6d4ae534237b1f4eee6aa186f0cdb4e334ba89536e"}, + {file = "pymongo-4.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:4497d49d785482cc1a44a0ddf8830b036a468c088e72a05217f5b60a9e025012"}, + {file = "pymongo-4.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:69247f7a2835fc0984bbf0892e6022e9a36aec70e187fcfe6cae6a373eb8c4de"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7bb0e9049e81def6829d09558ad12d16d0454c26cabe6efc3658e544460688d9"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6a1810c2cbde714decf40f811d1edc0dae45506eb37298fd9d4247b8801509fe"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:e2aced6fb2f5261b47d267cb40060b73b6527e64afe54f6497844c9affed5fd0"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d0355cff58a4ed6d5e5f6b9c3693f52de0784aa0c17119394e2a8e376ce489d4"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:3c74f4725485f0a7a3862cfd374cc1b740cebe4c133e0c1425984bcdcce0f4bb"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:9c79d597fb3a7c93d7c26924db7497eba06d58f88f58e586aa69b2ad89fee0f8"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8ec75f35f62571a43e31e7bd11749d974c1b5cd5ea4a8388725d579263c0fdf6"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e641f931c5cd95b376fd3c59db52770e17bec2bf86ef16cc83b3906c054845"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9aafd036f6f2e5ad109aec92f8dbfcbe76cff16bad683eb6dd18013739c0b3ae"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f2b856518bfcfa316c8dae3d7b412aecacf2e8ba30b149f5eb3b63128d703b9"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec31adc2e988fd7db3ab509954791bbc5a452a03c85e45b804b4bfc31fa221d"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9167e735379ec43d8eafa3fd675bfbb12e2c0464f98960586e9447d2cf2c7a83"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1461199b07903fc1424709efafe379205bf5f738144b1a50a08b0396357b5abf"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3094c7d2f820eecabadae76bfec02669567bbdd1730eabce10a5764778564f7b"}, + {file = "pymongo-4.6.1-cp39-cp39-win32.whl", hash = "sha256:c91ea3915425bd4111cb1b74511cdc56d1d16a683a48bf2a5a96b6a6c0f297f7"}, + {file = "pymongo-4.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:ef102a67ede70e1721fe27f75073b5314911dbb9bc27cde0a1c402a11531e7bd"}, + {file = "pymongo-4.6.1.tar.gz", hash = "sha256:31dab1f3e1d0cdd57e8df01b645f52d43cc1b653ed3afd535d2891f4fc4f9712"}, +] + +[package.dependencies] +dnspython = ">=1.16.0,<3.0.0" + +[package.extras] +aws = ["pymongo-auth-aws (<2.0.0)"] +encryption = ["certifi", "pymongo[aws]", "pymongocrypt (>=1.6.0,<2.0.0)"] +gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] +ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +snappy = ["python-snappy"] +test = ["pytest (>=7)"] +zstd = ["zstandard"] + +[[package]] +name = "quart" +version = "0.19.4" +description = "A Python ASGI web microframework with the same API as Flask" +optional = false +python-versions = ">=3.8" +files = [ + {file = "quart-0.19.4-py3-none-any.whl", hash = "sha256:959da9371b44b6f48d952661863f8f64e68a893481ef3f2ef45b177629dc0928"}, + {file = "quart-0.19.4.tar.gz", hash = "sha256:22ff186cf164955a7bf7483ff42a739a9fad3b119041846b15dc9597ec74c85c"}, +] + +[package.dependencies] +aiofiles = "*" +blinker = ">=1.6" +click = ">=8.0.0" +flask = ">=3.0.0" +hypercorn = ">=0.11.2" +itsdangerous = "*" +jinja2 = "*" +markupsafe = "*" +werkzeug = ">=3.0.0" + +[package.extras] +docs = ["pydata_sphinx_theme"] +dotenv = ["python-dotenv"] + +[[package]] +name = "werkzeug" +version = "3.0.1" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, + {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "39d88248a5c33d62fd721e24dd7c31d27577b98239a0c00754dc8dbfdcdbc1fb" diff --git a/pyproject.toml b/bf2-worker/pyproject.toml similarity index 73% rename from pyproject.toml rename to bf2-worker/pyproject.toml index 5289253..e72e82a 100644 --- a/pyproject.toml +++ b/bf2-worker/pyproject.toml @@ -7,11 +7,11 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.11" -pymongo = "^4.5.0" -Flask = "^3.0.0" -Quart = "^0.19.3" -hypercorn = "^0.15.0" -jinja2 = "^3.1.2" +pymongo = "^4.6.2" +Flask = "^3.0.2" +Quart = "^0.19.4" +hypercorn = "^0.16.0" +jinja2 = "^3.1.3" [build-system] requires = ["poetry-core"] diff --git a/serverList.py b/bf2-worker/serverList.py similarity index 96% rename from serverList.py rename to bf2-worker/serverList.py index 462bedd..1943f04 100644 --- a/serverList.py +++ b/bf2-worker/serverList.py @@ -1,4 +1,5 @@ """Get server lists from games and insert them into the database.""" + import asyncio from threading import Thread from subprocess import run, TimeoutExpired @@ -116,12 +117,12 @@ async def main_loop(main_db: MongoClient): "-n bfvietnam -x master2.qtracker.com:28900 -Y bfvietnam h2P9dJ -t 2 -Q 0 -q", "bfvietnam-qtracker", ) - ealist_gather( - main_db, - "mohair-pc -n bfbc2-pc-server -X none -o 1", - "bfbc2-pc-server.gsl", - "bfbc2", - ) + # ealist_gather( + # main_db, + # "mohair-pc -n bfbc2-pc-server -X none -o 1", + # "bfbc2-pc-server.gsl", + # "bfbc2", + # ) await asyncio.sleep(10) # 10 seconds diff --git a/build.Dockerfile b/build.Dockerfile new file mode 100644 index 0000000..2757260 --- /dev/null +++ b/build.Dockerfile @@ -0,0 +1,11 @@ +FROM ubuntu:23.04 + +RUN apt-get update && apt-get install -y libssl-dev build-essential && apt-get clean + +COPY ealist /ealist + +WORKDIR /ealist + +RUN make + +RUN ./ealist \ No newline at end of file diff --git a/ealist/Makefile b/ealist/Makefile new file mode 100644 index 0000000..8bbcdb5 --- /dev/null +++ b/ealist/Makefile @@ -0,0 +1,7 @@ +SRC = ealist.c +LIBS = -lssl -lcrypto + +all: ealist + +ealist: + $(CC) $(SRC) $(CFLAGS) -o ealist $(LIBS) \ No newline at end of file diff --git a/ealist/ealist.c b/ealist/ealist.c index f1e9439..70cf0bf 100644 --- a/ealist/ealist.c +++ b/ealist/ealist.c @@ -1614,7 +1614,7 @@ int recv_tcp(SSL *ssl_sd, int sd, u8 *data, int datalen) { -static const u8 SSL_CERT_X509[] = // x509 –in input.crt –inform PEM –out output.crt –outform DER +static const u8 SSL_CERT_X509[] = // x509 �in input.crt �inform PEM �out output.crt �outform DER "\x30\x82\x03\x07\x30\x82\x02\x70\xa0\x03\x02\x01\x02\x02\x09\x00" "\x85\x3a\x6e\x0a\xa4\x3c\x6b\xec\x30\x0d\x06\x09\x2a\x86\x48\x86" "\xf7\x0d\x01\x01\x05\x05\x00\x30\x61\x31\x0b\x30\x09\x06\x03\x55" @@ -1665,7 +1665,7 @@ int recv_tcp(SSL *ssl_sd, int sd, u8 *data, int datalen) { "\x15\x69\xce\x4a\x73\x3b\xee\x12\x4d\x1c\x63\x11\x9b\xdf\x4d\xa1" "\x38\x0d\xb6\x1d\xfb\xd6\xb8\x5b\xc2\x10\xd9"; -static const u8 SSL_CERT_RSA[] = // rsa –in input.key –inform PEM –out output.key –outform DER +static const u8 SSL_CERT_RSA[] = // rsa �in input.key �inform PEM �out output.key �outform DER "\x30\x82\x02\x5b\x02\x01\x00\x02\x81\x81\x00\xc5\xe3\x3f\x2d\x8f" "\x98\xc2\x2a\xef\x71\xea\x40\x21\x54\x3f\x08\x62\x9c\x7b\x39\x22" "\xfd\xda\x80\x1f\x21\x3e\x8d\x68\xcf\x8e\x6b\x70\x98\x95\x2c\x1e" diff --git a/gslist-2 b/gslist-2 deleted file mode 100755 index 263deed237b3da002111dd6992774214481a471f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 203312 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 + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/gslist/Makefile b/gslist/Makefile new file mode 100644 index 0000000..312ba23 --- /dev/null +++ b/gslist/Makefile @@ -0,0 +1,27 @@ +CFLAGS += -O2 -s -fstack-protector-all +PREFIX = /usr/local +BINDIR = $(PREFIX)/bin +SRC = src/gslist.c src/enctype1_decoder.c src/enctype2_decoder.c src/enctype_shared.c src/mydownlib.c +#LIBS = -lpthread /usr/lib/libGeoIP.a /usr/lib/i386-linux-gnu/libz.a +#SQLIBS = /usr/lib/i386-linux-gnu/libmysqlclient.a /usr/lib/i386-linux-gnu/libm.a -ldl +LIBS = -lpthread -lGeoIP -lz +SQLIBS = -lmysqlclient +O = $(SRC:.c=.o) + +all: gslist gslistsql + +gslist: + $(CC) $(SRC) $(CFLAGS) -o gslist $(LIBS) -DGSWEB + $(CC) $(SRC) $(CFLAGS) -o gslistsql $(SQLIBS) $(LIBS) -DGSWEB -DSQL + +clean: + rm -f gslist gslistsql src/gslist.o src/enctype1_decoder.o src/enctype2_decoder.o src/enctype_shared.o src/enctypex_decoder.o src/mydownlib.o + +install: + install -m 755 -d $(BINDIR) + install -m 755 gslist $(BINDIR)/gslist + install -m 755 gslistsql $(BINDIR)/gslistsql + +.PHONY: + clean install + diff --git a/gslist/README.md b/gslist/README.md new file mode 100644 index 0000000..077fdf2 --- /dev/null +++ b/gslist/README.md @@ -0,0 +1,5 @@ +# gslist +This is a "fork" of [Luigi Auriemmas brilliant gslist tool](http://aluigi.altervista.org/papers.htm#gslist). + +## Changes (compared to the original version of gslist 0.8.11a) +- added command line parameter for enabling/disabling GeoIP lookups (disabled by default, because current database update implementation does not work due to GeoIP databases no longer being publically available) diff --git a/gslist/gpl.txt b/gslist/gpl.txt new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/gslist/gpl.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, 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 Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/gslist/gslist.txt b/gslist/gslist.txt new file mode 100644 index 0000000..7b626b7 --- /dev/null +++ b/gslist/gslist.txt @@ -0,0 +1,710 @@ +######################################################################## + +Gslist +Author: Luigi Auriemma +e-mail: aluigi@autistici.org +web: aluigi.org + +######################################################################## + + +1) Introduction +2) Features +3) Installation +4) Web GUI +5) Options +6) Notes, tips and troubleshooting +7) Conclusion + + +######################################################################## + +=============== +1) Introduction +=============== + + +Gslist is a command-line game servers browser with multiple advanced +options and functions. +It supports an incredible amount of games (over 1000 ever updated +officially and moreover automatically) for different platforms because +it is based on the Gamespy master server master.gamespy.com which is +ever fully updated with the latest games in the world... this is one or +probably the only advantage of the Gamespy's monopoly for multiplayer +games. + +Naturally this tool is open source (released under GPL) and can be +compiled on a lot of systems. +Its usage is suggested moreover for advanced people that play a lot with +the console (aka terminal or command-line), testers, admins, webmasters +and anyone who needs something for watching the servers list of a +specific game and only that, without automatic pings or other extra +things. +This tool gives you the list of online servers, that is its main job... +but it does also other things if needed. + +Note that the ports you see when retrieve the servers list are usually +the query ports, not the game ports (although if often they are the +same). +The query port is an UDP port used to send server's informations and the +game port is usually a parameter contained in these replies. + +If you have ideas about new options or functions contact me! + + +######################################################################## + +=========== +2) Features +=========== + + +Some features of Gslist: + +- tons of games supported, it is probably the only application in the + world that supports a so long list of games (thanx to the Gamespy's + monopoly naturally) +- an useful server-side filter lets users to receive the list of servers + that match specific requirements like no empty servers or servers + located in a specific country and more +- an output option permits to create front-ends for Gslist or to + implement Gslist in any enviroment where other applications must read + the data contained in the files created by the program or from its + direct output (stdout) (these applications must be released under a + GPL license naturally) +- can be used also to query the online servers using the old and new + Gamespy query plus a lot of other queries available with the -d option +- web GUI like any "normal" server browser, read the section below +- you can launch a specific application for each game server found +- heartbeat sender, your IP can be inserted in the online servers list + and other players can find your server +- optimized for speed and resources +- portable and opensource +- Gslist is ever supported and open to new ideas and features +- Gslist is also supported by XQF (http://linuxgames.com/xqf/) +- others... + + +######################################################################## + +=============== +3) Installation +=============== + + +*nix: +- compilation requirements: make, pthread, geoip and optionally + mysqlclient (apt-get install libgeoip-dev libmysqlclient15-dev) +- make +- make install + as root, the file gslist will be put in /usr/local/bin + note that installing is not required, you need only the gslist + executable and nothing more +- when launched for the first time the tool automatically creates its + working directory .gslist in the home directory of the current user + (~/.gslist). In this location will be stored the configuration and + database files. + Anyway exists an option at runtime (-p) for using a different + directory + +Windows: +- copy gslist.exe in a directory (c:\gslist for example or c:\windows so + you can launch the tool from any location of the disk) + From version 0.8.2 you need to have also libmysql.dll (included in the + zip package) +- the tool uses the Application Data folder for storing its + configuration and database files + +Database update: +When you install Gslist you need to update the database of the supported +games (no longer required from version 0.8 for querying the master +server), all you need is the file gslist.cfg and you can build or +download it through the -u option. +A backup copy of this file is available in the Gslist package. + + +######################################################################## + +========== +4) Web GUI +========== + + +The web GUI activable with the -w option or through the double-click on +gslistweb.exe transforms Gslist is a basic web server which provides a +full working server browser accessible through any web browser. +All its html code is valid 4.0 and is totally script free which means it +works really on any browser and on any system. + +Its main target is to be simple, really simple so although you have zero +experience about ping, servers, configurations, and other things or you +don't want to read this text you should be able to use it almost +"immediately". + +Now a quick list of things to do when you start Gslist for the first +time: + +1) Update: the first and most necessary step if your database has not + been created yet. + It downloads all the latest informations about all the supported + games. Use it when a new game is not included in your current list + From version 0.8 a backup copy of the downloadable database is + available in the Gslist zipped package + +2) Go on the Scan link in the top menu and click on the button. + This function scans some keys of the registry for finding all the + supported games you have installed on your system and automatically + configures the path of their executables and what type of query they + accepts. + When this stage is finished you will be automatically redirected in + the Configuration menu where you can verify if everything is correct. + +3) The Configuration menu is the place where you can set and save your + player name, your filter (the filter is EVER temporary and this is + the only place where you can save it permanently for each game), + options for limiting the amount of visualized games, font size and + more. + This is also the place where you can set the options for your games: + + - path: where is located the game executable + - find: a confortable option for selecting the right game + executable without writing the full path, if needed. + When you have found it copy the text and paste it in the + above "path" field or it will not work! + - query: the type of query you want to use, this is automatically + set for the specific game when you use Scan or you need to + manually choose the exact type of query. + The "DirectPlay 8" and "All-Seeing Eye" queries don't work + in the interface because don't exist games that use them. + The tool gets the default query from the database file but + in some rare games this type of query could be wrong so + you need to set it manually + - filter: sets the custom filter for each game, you can add it + manually, add the custom filter stored in memory (created + in the Filter section) or reset it + - remove: removes the game from the list of configured games + - save: saves the path and the query selected in the configuration + +4) Main: the heart of the program, here you have the configured games + selectable in the bottom place and all the available servers and + their informations in the center. + When you select a game Gslist gets the full servers list and starts + to ping all the servers found retrieving all their informations. + The list is then sorted for ping time. + Remember that you are using a browser so CTRL-F does all the searches + you can imagine in any page, and same thing is for the Refresh, Back + and Forward buttons that are very very useful! + The REPING button is able to reping the servers list in memory + +5) Search: the place where you can manually add, list, ping or find + informations about all the supported game. + It is also the only place (with the Main menu naturally) accessible + by the external users if you launch Gslist on a non-local interface. + Here you can search a specifc text for finding a game or you can + simply click on Search with a black field and you will see all the + available games. + Then is possible to ADD a game to the installed games so you can + configure it, or you can LIST the available servers without pinging + them, or you can PING them as usual or you can find informations + about each specific game + +6) Filter: the filter is important for limiting the amount of servers + using some rules. + Here you can decide if you want to see the empty or full servers or + if you want to receive only the list of servers that contain the + word "rox" in their name, or the servers located in a specific + country and so on. + There are 2 fields here, one that shows the full filter and another + that simplifies the building of the filter without the need of + knowing all the available commands. + In fact if you want to see all the non-empty servers is enough to + place a zero in the minplayers field and click on Set. That's all. + Go in Configuration when and if you want to permanently save your + current filter + +7) IP: options for pinging an IP, directly joining it or adding it to + the favorites menu. + The third field for the join and favorites button is used for + specifying a password. + Remember that game servers sometimes have a game and a query port. + The query port is used to get informations from a server while the + game port is used only for direct join or direct favorites. + Take that in mind because it's important! + +8) Favorites: nothing to say, when you go on this menu it automatically + gets the informations from the favorites servers and you can join + them. + The X at the left of the IP is used for removing the favorite. + Note that the scanning is an IP at time so if you have 10 favorite + servers and none of them is online you must wait the end of the + timeout + +9) About: other than the links to the program homepage and the viewing + of the program version, here you find also a small list of tips for a + better usage of the Gslist web interface + +X) Quit: closes Gslist + + +Notes: +- if you double-click on gslistweb.exe when it's running it will start + the browser, so don't worry about launching the program many times + since it automatically stops the new instances +- when there are many servers to ping you need to wait some seconds, + my BIG suggestion is to set a small timeout (default is 2 seconds) and + to not show the unpingable servers in the Configuration menu so you + will see only the fastest servers that have replied to your queries +- this web interface is also good for multi-users so if you set it on + your local network interface (like 192.168.0.1) or on all the + interfaces (0 or 0.0.0.0) it can be used also by your friends. + This network GUI has been created for a single-user environment or in + a LAN between friends but NOT to be accessed from Internet on a + production server for security reasons (I have avoided any type of bug + but never say never) and because could exist some parts of code which + are not thread-safe and this could result in mixed results in an + intensive multi-user usage. + Naturally if you use the default interface 127.0.0.1 nobody except you + can access Gslist, so don't worry. +- in the servers list, 'd' is used for dedicated servers, 'p' for + servers protected by password and 'pb' for those which use the + Punkbuster anti-cheat system +- from version 0.8.8 the webGUI uses enctypeX by default which means + that it no longer pings the servers because all the informations are + collected directly from the master server, the results are practically + immediate. + the only downside of this solution is that no longer exist the ping. + anyway is possible to choose the old enctype 1 or 2 specifying -t 1 + at command-line (or through a shortcut on Windows). +- the webgui interface is no longer actively supported + + +######################################################################## + +========== +5) Options +========== + + +-n GAMENAME + or +-N GAMENAME + or +-y GAMENAME GAMEKEY + you can retrieve the servers list of a specific game simply specifying + its gamename. + Gamename is a text string identifying a game, for example aoe for Age + of Empires, ut2004 for Unreal Tournament 2004, doom3 for Doom 3, mohaa + for Medal of Honor Allied Assault, battlefield2 for the homonym game + and so on. + All the available gamenames can be seen through the -s and -l options. + From version 0.8 is also possible to specify more gamenames separated + by commas (without spaces), Gslist will get the servers list of each + one of them sequentially and there is no longer a verification of the + existence of the gamename in the database, so any gamename is accepted. + Example: -N doom3 + -n halor,ut,battlefield2 + + +-l + lists the entire database of supported games. + The database is contained in a text file called gslist.cfg and is just + the same data showed by this option so you can read the file with a + normal text editor too. + From version 0.8 the database and the gamekeys are no longer required + + +-s PATTERN + the most useful function to use Gslist. In fact it must be used to + search a specific game, gamename, gamekey or any other thing contained + in the file gslist.cfg. + The function is case insensitive which means is not important if the + pattern you search is in lower, higher of both cases because the + function finds it ever. + Example: -s unreal, -s UNREAL, -s uNrEaL + the result is ever the same for all the 3 examples + -s HpWx9z or -s hal and so on + + +-u + the function for updating the game database. + The gamenames are enough easy to calculate but this database is useful + to know what games are supported and for what platform. + The list is downloaded from my website but it can be also created + manually using the -m or -M options + From version 0.8 a backup copy of the gslist.cfg file is available in + the Gslist package + + +-U + rebuild gslist.cfg, used mainly for debugging. + + +-i HOST PORT + sends the \status\ query (known as the old Gamespy query protocol) to + a specific server and port and then waits a reply. + Naturally the game server must support this type of query (alsmost any + game listed supports this or the -I query). + Example: -i localhost 7787, -i 192.168.0.1 12203 + -i 192.168.0.1:12203 + + +-I HOST PORT + just as above but instead of \status\ it sends the new Gamespy query + composed by the bytes "fe fd 00 ?? ?? ?? ?? ff 00 00 01" where + ???????? are replaced with a partially random value or with 00. + Example: -I localhost 7787, -I 192.168.0.1 12203 + -I 192.168.0.1:12203 + + +-d T HOST PORT + this is an option that can be used to query a server using different + protocols, like that used by Quake 3, Half-Life, DirectPlay8, Doom3, + Source, Tribes 2 and so on. + The parameter T is referred to the type of query to use, you can see + the list of available queries simply without specifying any parameter + or using ? as T parameter. + Example: -d 1 127.0.0.1 27960 + -d 5 localhost 6073 + -d 1 127.0.0.1:7778 + -d or -d ? for details + + +-f FILTERS + specify a filter to apply to the servers list before returning (so a + filter applied by the same master server and not by Gslist). + This option is really very useful in some cases, in fact the Gamespy + master server supports SQL queries to filter the servers list for + example to avoid empty or full servers, or to receive only italian + servers and many other things. + It's all server-side so that saves also the network bandwidth. + + The valid operators you can use are: + <>, !=, >=, !<, <=, !>, =, <, >, (, ), +, -, *, /, %, + AND, NOT, OR, LIKE, NOT LIKE, IS NULL, IS NOT NULL + + While the main items are: + hostaddr, hostport, gamever, country, hostname, + mapname, gametype, gamemode, numplayers, maxplayers + + But is possible to specify any item used in a specific game like + password or dedicated for example, they are the same parameters + that you see when query the servers of a game + + Is also possible to use the wildcard character % + The delimiter for the text strings is the character ' + + Some usage examples: + - for non-empty servers: -f "(numplayers > 0)" + - for empty servers only: -f "(numplayers = 0)" + - for finding the IP of the server with the name Jackass: + -f "(hostname LIKE 'Jackass')" + - for all the servers with a name containing the text ass: + -f "(hostname LIKE '%ass%')" + - for italian servers: -f "(country = 'IT')" + - for servers on port 10 -f "(hostport = 10)" + - for dedicated servers -f "(dedicated = 1)" + -f "(dedicated LIKE 'True')" + - for passworded servers -f "(password = 1)" + -f "(password LIKE 'True')" + - for servers using the version 1.1 of the game: + -f "(gamever = '1.1')" + or + -f "(gamever LIKE '1.1')" + + ...all your fantasy, the filters are so useful and huges that you can + do everything you want with them. However remember that the filters + refers to the informations obtained by the Gamespy master server from + the last query made by it to the game servers (usually a query for each + heartbeat) so is possible that you are filtering old informations + because heartbeats have a timeout of 5 minutes. + Remember ever to delimit your filter with the char ", like in the above + examples. + Some reference links: + http://www.gamespyarcade.com/support/filter.shtml + http://www.gamespyarcade.com/support/help/filter.shtml + http://www.gamespyarcade.com/helpers/workshop/filters/ + + +-r "prog..." + a cool and original option. It lets you to execute a specific program + for each server and port found. + With "prog..." I mean the command to execute with all its arguments. + There are 2 patterns that are substituited with the current IP and + port of the server found in the servers list, they are #IP for the IP + address of the server and #PORT for its query port. + If for example I use: + -r "echo the server #IP listens on port #PORT, nice" + the program will execute the command: + echo the server 127.0.0.1 listens on port 100, nice + (if the IP and port in the list are 127.0.0.1 and 100, naturally). + Other examples are: + -r "c:\tools\ping.exe #IP" + -r "./myprog -p #PORT #IP" + + +-o [OUT] + used to dump the received servers list to files instead of screen or in + a different format. + You can choose between 5 types of outputs (OUT): + + 1 the servers list is dumped in text format to a file that has the + name of GAMENAME plus the extension gsl. + The text output is composed by the IP:port and a line-feed (0x0a). + Example of quake3.gsl if we have chosen the game Quake 3: + 1.2.3.4:1234 + 11.22.33.44:4321 + + 2 exactly as above with the only difference that the output file is + EVER gslist-out.gsl for any game + + 3 the servers list is dumped in binary format to a file that has the + name of GAMENAME plus the extension gsl. + With binary output I mean the network-byte format used for IPs and + ports (just as received from the master server) so the data + contained in this output is already ready to be used with sockets. + An example of IP and port contained in hex format in the file: + 7F0000011E62 = 127.0.0.1 7778 + + 4 exactly as above with the only difference that the output file is + ever gslist-out.gsl for any game + + 5 screen output (stdout) using the classical format IP:port, like + 127.0.0.1:12345 + + 6 hex dump of the raw data received from the server. If you have + used the -t 1 or 2 option you will see the encrypted raw data. + Needed only for debugging. + + X if OUT is not 1, 2, 3, 4, 5 or 6 it is considered a filename to + which sending all the servers list output (just as what you see + normally on the screen) + + +-w IP PORT + the web interface. Check section 4 for all the details you need. + IP is your local IP interface like 127.0.0.1 for localhost or 0 for ANY + interface. + PORT is the port you want to use with Gslist, 0 is automatically + substituited with 28903. + + +-q + quiet output, practically only the list of servers and nothing more. + + +-x S[:P] + you can choose a master server and a port different than + master.gamespy.com:28900. + This option is useful for tests or if you know a master server that + uses the Gamespy master server protocol. + The port is optional and the default is 28900. + Example: -x localhost, -x localhost:12345 + + +-b PORT + the heartbeats sender. Lets your IP address to be included in the + online servers list located on the master server. PORT is the port used + by your server (usually the query port). + PORT is bound for some milliseconds to be able to reply to the master + server's queries or to the possible validation requests that arrives + when the heartbeat is sent, so during this short time your server will + be not accessible by other users. + The -b and -B options must be used with any of the options -n, -N or + -y. + Note: I consider this option a bit experimental because binding the + port could cause some problems or packets loss to your server, + so is preferred the option -B. + Use it only for tests or for games that don't support the old + Gamespy query (\status\). + It's also useful for statistical purposes, for example you can + see how many clients are online + Example for UT2004: -b 7787 -N ut2004 + + +-B PORT + exactly as above but the PORT will not be bound. + Use this option ONLY if your server supports the old Gamespy protocol + (\status\) and so it can reply to the master server query. + Remember that if you don't reply to the query you will not be added in + the servers list. + + +-L SEC + puts the tool in a continuous loop, where SEC is the amount of seconds + to wait between each cycle. + So if you use -L 60 Gslist will retrieve the servers list of a specific + game and will redo the same after one minute and so on infinitely until + you will break it. + + +-t NUM + Gslist supports enctype 0, 1, 2 and -1 + Enctype 1 or 2 are now default since at the beginning of September 2005 + Gamespy has removed enctype 0 for the games released from about the year + 2002/2003 (like America's Army, UT2003, UT2004 and so on). + From version 0.8.6 there is a new enctype available which in reality is + just a new protocol at all, it's enough to use -t -1 for selecting it. + it allows gslist to act EXACTLY as do all the games which use the + Gamespy master server without differences. + + +-c + an useful function that shows all the available country codes for the + "country" filter (like IT for Italy, US for USA, UK for England and so + on). + + +-Y NAME KEY + lets you to choose a specific GAMENAME and KEY. + From version 0.8 this option is used only for gaining access to the + master server, through this option you can specify other games or + programs and is useful for possible future compatibility reasons. + + +-p PATH + the directory where you want to read or store the Gslist configuration + files. + From version 0.8 the configuration files are no longer required but this + option could be useful in rare cases. + For example, if you want to use the gslist.cfg file in the local + directory you must use -p . + + +-m + or +-M + this is the option I use for creating the gslist.cfg file available for + download. You can avoid to use the -u option simply using -m or -M + which update the database using the data available on Gamespy and my + gshkeys.txt file. + The difference between -m and -M is that the second one rebuilds the + database from scratch after having deleted the previous local database + files. + + +-Q T + will query any server available in the servers list retrieved with the + -n option. T is just the same parameter used with -d. + Source engine, DirectPlay and ASE queries are not supported + The data will be showed on the screen using the format: + + IP:PORT \parameter\value\parameter\value\...\parameter\value + + +-X INFO + similar to the above option except that all the info are received + directly from the same master server which means that there are problems + of time and delay, there are no problems of NATted servers or packets + lost and so on. + it works only with the so called enctypeX (-t -1) + the only limitation is that you can use this type of fast query only + with games which use the Gamespy master server natively, so for example + you can't use -X with Call of Duty 4 or Quake 3 and so on + the format of INFO is very simple and the following example should + explain anything: + + -t -1 -X \hostname\gamever\numplayers\maxplayers\mapname\gametype + + on linux you could have problems to use the backslash so put the query + between marks: + + -t -1 -X "\hostname\gamever\numplayers\maxplayers\mapname\gametype" + or + -t -1 -X \\hostname\\gamever\\numplayers\\maxplayers\\mapname\\gametype + + note that now gslist uses the enctypeX mode by default so it's no longer + needed to specify -t -1 + + +-S + show the SQL options + this function is experimental and is the implementation of the old + gsscansql program I wrote many years ago. + Currently there is no documentation about it except the runtime help. + requires the -Q option for working + + +-E + ignore the SQL errors and continues after some seconds + + +-D MS + amount of milliseconds to wait between each query used with the -Q + command or when using the webgui + this option is VERY important in some conditions, for example if you + are behind a restrictive NAT or router and so you can't send too much + UDP packets all together + + +-z FILE + instead of downloading the servers list from the master server Gslist + can get it from a text file containing the list of IP and ports like + in the following example: + 1.2.3.4 1234 + 1.2.3.4:1234 + myserver.org 1234 + + +-F + additional option for -Q which forces the usage of the Gamespy NAT + negotiation for getting informations from the servers which don't reply + or are behind NAT/router + + +-C + do not filter the colors and the chars >= 0x7f from the replies + received by the servers queried with -Q/-X + + +-e + this option shows some quick examples to use Gslist and some of its + features. + Useful if you don't remember the syntax of a command or don't know what + you are doing wrong. + + +-v + shows the version of Gslist + + +######################################################################## + +================================== +6) Notes, tips and troubleshooting +================================== + + +- if you want to join a server protected by password through the web GUI + you need to add &pass=YOURPASS at the end of the /play link, like: + + http://127.0.0.1/play?game=halor&ip=1.2.3.4&port=2302&pass=mypass + +- on Unix systems Gslist uses the Pthread library, so if you are on + Linux and Gslist closes itself often without errors, try to use the + latest Pthread library available + +- if in the web GUI you don't see the servers informations but only the + list of IP addresses you need probably to specify a different type of + query + +- compilation options: + -DSQL enables the SQL option + -DGSWEB enables the web interface option + -DWINTRAY enables the Windows interface + +- contact me for any other problem + + +######################################################################## + +============= +7) Conclusion +============= + + +As already said the program is open to new ideas and functions so let me +know your ideas, suggestions and feedback (moreover bug reports) or if +you have not understood something. + + +######################################################################## diff --git a/gslist/src/compa.bat b/gslist/src/compa.bat new file mode 100644 index 0000000..3b4f613 --- /dev/null +++ b/gslist/src/compa.bat @@ -0,0 +1,25 @@ +@echo off + +set GSLIST_MINGW_PATH=c:\mingw +set GSLIST_MINGW_OPT=-s -O2 -mtune=generic -fstack-protector-all -Wall -Wextra -Wunused -Wshadow -Wno-pointer-sign -Wno-sign-compare -Wno-unused-parameter +set GSLIST_MINGW_LIBS=gslist_icon.o stristr.c -DGSWEB "%GSLIST_MINGW_PATH%\lib\libz.a" "%GSLIST_MINGW_PATH%\lib\libws2_32.a" +set GSLIST_FILES=gslist.c enctype1_decoder.c enctype2_decoder.c enctype_shared.c mydownlib.c + +"%GSLIST_MINGW_PATH%\bin\windres" gslist_icon.rc gslist_icon.o + +echo ---------- +echo - gslist - +echo ---------- +"%GSLIST_MINGW_PATH%\bin\gcc" %GSLIST_MINGW_OPT% -o ..\gslist.exe %GSLIST_FILES% %GSLIST_MINGW_LIBS% + +echo ------------- +echo - gslistweb - +echo ------------- +#"%GSLIST_MINGW_PATH%\bin\gcc" %GSLIST_MINGW_OPT% -o ..\gslistweb.exe %GSLIST_FILES% %GSLIST_MINGW_LIBS% -mwindows -DWINTRAY + +echo ------------- +echo - gslistsql - +echo ------------- +#"%GSLIST_MINGW_PATH%\bin\gcc" %GSLIST_MINGW_OPT% -o ..\gslistsql.exe %GSLIST_FILES% %GSLIST_MINGW_LIBS% -DSQL "%GSLIST_MINGW_PATH%\lib\libmysql.lib" + +del gslist_icon.o diff --git a/gslist/src/countries.h b/gslist/src/countries.h new file mode 100644 index 0000000..93e7095 --- /dev/null +++ b/gslist/src/countries.h @@ -0,0 +1,307 @@ +static const char *countries[][2] = { + { "[Codes]", "" }, + { "US", "United States" }, + { "ZZ", "Other" }, + { "AF", "Afghanistan" }, + { "AL", "Albania" }, + { "DZ", "Algeria" }, + { "AS", "American Samoa" }, + { "AD", "Andorra" }, + { "AO", "Angola" }, + { "AI", "Anguilla" }, + { "AQ", "Antarctica" }, + { "AG", "Antigua and Barbuda" }, + { "AR", "Argentina" }, + { "AM", "Armenia" }, + { "AW", "Aruba" }, + { "AU", "Australia" }, + { "AT", "Austria" }, + { "AZ", "Azerbaijan" }, + { "BS", "Bahamas" }, + { "BH", "Bahrain" }, + { "BD", "Bangladesh" }, + { "BB", "Barbados" }, + { "BY", "Belarus" }, + { "BE", "Belgium" }, + { "BZ", "Belize" }, + { "BJ", "Benin" }, + { "BM", "Bermuda" }, + { "BT", "Bhutan" }, + { "BO", "Bolivia" }, + { "BA", "Bosnia and Herzegovina" }, + { "BW", "Botswana" }, + { "BV", "Bouvet Island" }, + { "BR", "Brazil" }, + { "IO", "British Indian Ocean Territory" }, + { "BN", "Brunei Darussalam" }, + { "BG", "Bulgaria" }, + { "BF", "Burkina Faso" }, + { "BI", "Burundi" }, + { "KH", "Cambodia" }, + { "CM", "Cameroon" }, + { "CA", "Canada" }, + { "CV", "Cape Verde" }, + { "CT", "Catalonia" }, + { "KY", "Cayman Islands" }, + { "CF", "Central African Republic" }, + { "TD", "Chad" }, + { "CL", "Chile" }, + { "CN", "China" }, + { "CX", "Christmas Island" }, + { "CC", "Cocos (Keeling Islands)" }, + { "CO", "Colombia" }, + { "KM", "Comoros" }, + { "CG", "Congo" }, + { "CK", "Cook Islands" }, + { "CR", "Costa Rica" }, + { "CI", "Cote D'Ivoire (Ivory Coast)" }, + { "HR", "Croatia (Hrvatska)" }, + { "CU", "Cuba" }, + { "CY", "Cyprus" }, + { "CZ", "Czech Republic" }, + { "DK", "Denmark" }, + { "DJ", "Djibouti" }, + { "DM", "Dominica" }, + { "DO", "Dominican Republic" }, + { "TP", "East Timor" }, + { "EC", "Ecuador" }, + { "EG", "Egypt" }, + { "SV", "El Salvador" }, + { "GQ", "Equatorial Guinea" }, + { "ER", "Eritrea" }, + { "EE", "Estonia" }, + { "ET", "Ethiopia" }, + { "FK", "Falkland Islands (Malvinas)" }, + { "FO", "Faroe Islands" }, + { "FJ", "Fiji" }, + { "FI", "Finland" }, + { "FR", "France" }, + { "FX", "France Metropolitan" }, + { "GF", "French Guiana" }, + { "PF", "French Polynesia" }, + { "TF", "French Southern Territories" }, + { "GA", "Gabon" }, + { "GM", "Gambia" }, + { "GE", "Georgia" }, + { "DE", "Germany" }, + { "GH", "Ghana" }, + { "GI", "Gibraltar" }, + { "GR", "Greece" }, + { "GL", "Greenland" }, + { "GD", "Grenada" }, + { "GP", "Guadeloupe" }, + { "GU", "Guam" }, + { "GT", "Guatemala" }, + { "GN", "Guinea" }, + { "GW", "Guinea-Bissau" }, + { "GY", "Guyana" }, + { "HT", "Haiti" }, + { "HM", "Heard and McDonald Islands" }, + { "HN", "Honduras" }, + { "HK", "Hong Kong" }, + { "HU", "Hungary" }, + { "IS", "Iceland" }, + { "IN", "India" }, + { "ID", "Indonesia" }, + { "IR", "Iran" }, + { "IQ", "Iraq" }, + { "IE", "Ireland" }, + { "IL", "Israel" }, + { "IT", "Italy" }, + { "JM", "Jamaica" }, + { "JP", "Japan" }, + { "JO", "Jordan" }, + { "KZ", "Kazakhstan" }, + { "KE", "Kenya" }, + { "KI", "Kiribati" }, + { "KP", "North Korea" }, + { "KR", "South Korea" }, + { "KW", "Kuwait" }, + { "KG", "Kyrgyzstan" }, + { "LA", "Laos" }, + { "LV", "Latvia" }, + { "LB", "Lebanon" }, + { "LS", "Lesotho" }, + { "LR", "Liberia" }, + { "LY", "Libya" }, + { "LI", "Liechtenstein" }, + { "LT", "Lithuania" }, + { "LU", "Luxembourg" }, + { "MO", "Macau" }, + { "MK", "Macedonia" }, + { "MG", "Madagascar" }, + { "MW", "Malawi" }, + { "MY", "Malaysia" }, + { "MV", "Maldives" }, + { "ML", "Mali" }, + { "MT", "Malta" }, + { "MH", "Marshall Islands" }, + { "MQ", "Martinique" }, + { "MR", "Mauritania" }, + { "MU", "Mauritius" }, + { "YT", "Mayotte" }, + { "MX", "Mexico" }, + { "FM", "Micronesia" }, + { "MD", "Moldova" }, + { "MC", "Monaco" }, + { "MN", "Mongolia" }, + { "MS", "Montserrat" }, + { "MA", "Morocco" }, + { "MZ", "Mozambique" }, + { "MM", "Myanmar (Burma)" }, + { "NA", "Namibia" }, + { "NR", "Nauru" }, + { "NP", "Nepal" }, + { "NL", "Netherlands" }, + { "AN", "Netherlands Antilles" }, + { "NC", "New Caledonia" }, + { "NZ", "New Zealand" }, + { "NI", "Nicaragua" }, + { "NE", "Niger" }, + { "NG", "Nigeria" }, + { "NU", "Niue" }, + { "NF", "Norfolk Island" }, + { "MP", "Northern Mariana Islands" }, + { "NO", "Norway" }, + { "OM", "Oman" }, + { "PK", "Pakistan" }, + { "PW", "Palau" }, + { "PA", "Panama" }, + { "PG", "Papua New Guinea" }, + { "PY", "Paraguay" }, + { "PE", "Peru" }, + { "PH", "Philippines" }, + { "PN", "Pitcairn" }, + { "PL", "Poland" }, + { "PT", "Portugal" }, + { "PR", "Puerto Rico" }, + { "QA", "Qatar" }, + { "RE", "Reunion" }, + { "RO", "Romania" }, + { "RU", "Russian Federation" }, + { "RW", "Rwanda" }, + { "KN", "Saint Kitts and Nevis" }, + { "LC", "Saint Lucia" }, + { "VC", "Saint Vincent and The Grenadines" }, + { "WS", "Samoa" }, + { "SM", "San Marino" }, + { "ST", "Sao Tome and Principe" }, + { "SA", "Saudi Arabia" }, + { "SN", "Senegal" }, + { "SC", "Seychelles" }, + { "SL", "Sierra Leone" }, + { "SG", "Singapore" }, + { "SK", "Slovak Republic" }, + { "SI", "Slovenia" }, + { "SB", "Solomon Islands" }, + { "SO", "Somalia" }, + { "ZA", "South Africa" }, + { "GS", "S. Georgia and S. Sandwich Isls." }, + { "ES", "Spain" }, + { "LK", "Sri Lanka" }, + { "SH", "St. Helena" }, + { "PM", "St. Pierre and Miquelon" }, + { "SD", "Sudan" }, + { "SR", "Suriname" }, + { "SJ", "Svalbard and Jan Mayen Islands" }, + { "SZ", "Swaziland" }, + { "SE", "Sweden" }, + { "CH", "Switzerland" }, + { "SY", "Syria" }, + { "TW", "Taiwan" }, + { "TJ", "Tajikistan" }, + { "TZ", "Tanzania" }, + { "TH", "Thailand" }, + { "TG", "Togo" }, + { "TK", "Tokelau" }, + { "TO", "Tonga" }, + { "TT", "Trinidad and Tobago" }, + { "TN", "Tunisia" }, + { "TR", "Turkey" }, + { "TM", "Turkmenistan" }, + { "TC", "Turks and Caicos Islands" }, + { "TV", "Tuvalu" }, + { "UG", "Uganda" }, + { "UA", "Ukraine" }, + { "AE", "United Arab Emirates" }, + { "UK", "United Kingdom" }, + { "UM", "US Minor Outlying Islands" }, + { "UY", "Uruguay" }, + { "UZ", "Uzbekistan" }, + { "VU", "Vanuatu" }, + { "VA", "Vatican City" }, + { "VE", "Venezuela" }, + { "VN", "VietNam" }, + { "VG", "Virgin Islands (British)" }, + { "VI", "Virgin Islands (US)" }, + { "WF", "Wallis and Futuna Islands" }, + { "EH", "Western Sahara" }, + { "YE", "Yemen" }, + { "YU", "Yugoslavia" }, + { "ZR", "Zaire" }, + { "ZM", "Zambia" }, + { "ZW", "Zimbabwe" }, + { "", "" }, + { "[USStates]", "" }, + { "01", "Alabama " }, + { "02", "Alaska " }, + { "04", "Arizona " }, + { "05", "Arkansas " }, + { "06", "California " }, + { "08", "Colorado " }, + { "09", "Connecticut " }, + { "10", "Delaware " }, + { "11", "Dist. of Columbia " }, + { "12", "Florida " }, + { "13", "Georgia " }, + { "15", "Hawaii " }, + { "16", "Idaho " }, + { "17", "Illinois " }, + { "18", "Indiana " }, + { "19", "Iowa " }, + { "20", "Kansas " }, + { "21", "Kentucky " }, + { "22", "Louisiana " }, + { "23", "Maine " }, + { "24", "Maryland " }, + { "25", "Massachusetts " }, + { "26", "Michigan " }, + { "27", "Minnesota " }, + { "28", "Mississippi " }, + { "29", "Missouri " }, + { "30", "Montana " }, + { "31", "Nebraska " }, + { "32", "Nevada " }, + { "33", "New Hampshire " }, + { "34", "New Jersey " }, + { "35", "New Mexico " }, + { "36", "New York " }, + { "37", "North Carolina " }, + { "38", "North Dakota " }, + { "39", "Ohio " }, + { "40", "Oklahoma " }, + { "41", "Oregon " }, + { "42", "Pennsylvania " }, + { "44", "Rhode Island " }, + { "45", "South Carolina " }, + { "46", "South Dakota " }, + { "47", "Tennessee " }, + { "48", "Texas " }, + { "49", "Utah " }, + { "50", "Vermont " }, + { "51", "Virginia " }, + { "53", "Washington " }, + { "54", "West Virginia " }, + { "55", "Wisconsin " }, + { "56", "Wyoming " }, + { "60", "American Samoa " }, + { "64", "Micronesia " }, + { "66", "Guam " }, + { "68", "Marshall Islands " }, + { "69", "No. Mariana Islands " }, + { "70", "Palau " }, + { "72", "Puerto Rico " }, + { "74", "U.S. Islands" }, + { "78", "Virgin Islands" }, + { NULL, NULL } +}; diff --git a/gslist/src/enctype1_decoder.c b/gslist/src/enctype1_decoder.c new file mode 100644 index 0000000..082a8df --- /dev/null +++ b/gslist/src/enctype1_decoder.c @@ -0,0 +1,352 @@ +/* + +GS enctype1 servers list decoder 0.1a +by Luigi Auriemma +e-mail: aluigi@autistici.org +web: aluigi.org + + +INTRODUCTION +============ +This is the algorithm used to decrypt the data sent by the Gamespy +master server (or any other compatible server) using the enctype 1 +method. + + +USAGE +===== +Add the following prototype at the beginning of your code: + + unsigned char *enctype1_decoder(unsigned char *, unsigned char *, int *); + +save the \secure\ value in a small buffer (because gsmsalg modifies this +value or for any other reason) and then use: + + pointer = enctype1_decoder( + secure, // the \secure\ value + buffer, // all the data received from the master server + &buffer_len); // the size of the master server + +The return value is a pointer to the decrypted zone of buffer and +buffer_len is modified with the size of the decrypted data. + +A simpler way to use the function is just using: + + len = enctype1_wrapper(key, data, data_len); + + +THANX TO +======== +REC (http://www.backerstreet.com/rec/rec.htm) which has helped me in many +parts of the code. + + +LICENSE +======= + Copyright 2005,2006,2007,2008 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl.txt + +*/ + +#include + + + +void encshare1(unsigned int *tbuff, unsigned char *datap, int len); +void encshare4(unsigned char *src, int size, unsigned int *dest); + + + +unsigned char enc1key[261]; + + + +unsigned char *enctype1_decoder(unsigned char *id, unsigned char *, int *); +void func1(unsigned char *, int); +void func2(unsigned char *, int, unsigned char *); +void func3(unsigned char *, int, unsigned char *); +void func6(unsigned char *, int); +int func7(int); +void func4(unsigned char *, int); +int func5(int, unsigned char *, int, int *, int *); +void func8(unsigned char *, int, const unsigned char *); + + + + // L00429D10 +unsigned char *enctype1_decoder(unsigned char *id, unsigned char *data, int *datalen) { + unsigned int tbuff[326]; + int i, + len, + tmplen; + unsigned char tbuff2[258]; + unsigned char *datap; + static const unsigned char enctype1_data[256] = /* pre-built */ + "\x01\xba\xfa\xb2\x51\x00\x54\x80\x75\x16\x8e\x8e\x02\x08\x36\xa5" + "\x2d\x05\x0d\x16\x52\x07\xb4\x22\x8c\xe9\x09\xd6\xb9\x26\x00\x04" + "\x06\x05\x00\x13\x18\xc4\x1e\x5b\x1d\x76\x74\xfc\x50\x51\x06\x16" + "\x00\x51\x28\x00\x04\x0a\x29\x78\x51\x00\x01\x11\x52\x16\x06\x4a" + "\x20\x84\x01\xa2\x1e\x16\x47\x16\x32\x51\x9a\xc4\x03\x2a\x73\xe1" + "\x2d\x4f\x18\x4b\x93\x4c\x0f\x39\x0a\x00\x04\xc0\x12\x0c\x9a\x5e" + "\x02\xb3\x18\xb8\x07\x0c\xcd\x21\x05\xc0\xa9\x41\x43\x04\x3c\x52" + "\x75\xec\x98\x80\x1d\x08\x02\x1d\x58\x84\x01\x4e\x3b\x6a\x53\x7a" + "\x55\x56\x57\x1e\x7f\xec\xb8\xad\x00\x70\x1f\x82\xd8\xfc\x97\x8b" + "\xf0\x83\xfe\x0e\x76\x03\xbe\x39\x29\x77\x30\xe0\x2b\xff\xb7\x9e" + "\x01\x04\xf8\x01\x0e\xe8\x53\xff\x94\x0c\xb2\x45\x9e\x0a\xc7\x06" + "\x18\x01\x64\xb0\x03\x98\x01\xeb\x02\xb0\x01\xb4\x12\x49\x07\x1f" + "\x5f\x5e\x5d\xa0\x4f\x5b\xa0\x5a\x59\x58\xcf\x52\x54\xd0\xb8\x34" + "\x02\xfc\x0e\x42\x29\xb8\xda\x00\xba\xb1\xf0\x12\xfd\x23\xae\xb6" + "\x45\xa9\xbb\x06\xb8\x88\x14\x24\xa9\x00\x14\xcb\x24\x12\xae\xcc" + "\x57\x56\xee\xfd\x08\x30\xd9\xfd\x8b\x3e\x0a\x84\x46\xfa\x77\xb8"; + + len = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]); + if((len < 0) || (len > *datalen)) { + *datalen = 0; + return(data); + } + + data[4] = (data[4] ^ 62) - 20; + data[5] = (data[5] ^ 205) - 5; + func8(data + 19, 16, enctype1_data); + + len -= data[4] + data[5] + 40; + datap = data + data[5] + 40; + + tmplen = (len >> 2) - 5; + if(tmplen >= 0) { + func1(NULL, 0); + func4(id, strlen(id)); + func6(datap, tmplen); + memset(enc1key, 0, sizeof(enc1key)); + } + + /* added by me */ + for(i = 256; i < 326; i++) tbuff[i] = 0; + + tmplen = (len >> 1) - 17; + if(tmplen >= 0) { + encshare4(data + 36, 4, tbuff); + encshare1(tbuff, datap, tmplen); + } + + memset(tbuff2, 0, sizeof(tbuff2)); + func3(data + 19, 16, tbuff2); + func2(datap, len, tbuff2); + + *datalen = len; + return(datap); +} + + + + // 0048DAA0 +void func1(unsigned char *id, int idlen) { + if(id && idlen) func4(id, idlen); +} + + + + // 00406720 +void func2(unsigned char *data, int size, unsigned char *crypt) { + unsigned char n1, + n2, + t; + + n1 = crypt[256]; + n2 = crypt[257]; + while(size--) { + t = crypt[++n1]; + n2 += t; + crypt[n1] = crypt[n2]; + crypt[n2] = t; + t += crypt[n1]; + *data++ ^= crypt[t]; + } + crypt[256] = n1; + crypt[257] = n2; +} + + + + // 00406680 +void func3(unsigned char *data, int len, unsigned char *buff) { + int i; + unsigned char pos = 0, + tmp, + rev = 0xff; + + for(i = 0; i < 256; i++) { + buff[i] = rev--; + } + + buff[256] = 0; + buff[257] = 0; + for(i = 0; i < 256; i++) { + tmp = buff[i]; + pos += data[i % len] + tmp; + buff[i] = buff[pos]; + buff[pos] = tmp; + } +} + + + + // 0048D9B0 +void func4(unsigned char *id, int idlen) { + int i, + n1 = 0, + n2 = 0; + unsigned char t1, + t2; + + if(idlen < 1) return; + + for(i = 0; i < 256; i++) enc1key[i] = i; + + for(i = 255; i >= 0; i--) { + t1 = func5(i, id, idlen, &n1, &n2); + t2 = enc1key[i]; + enc1key[i] = enc1key[t1]; + enc1key[t1] = t2; + } + + enc1key[256] = enc1key[1]; + enc1key[257] = enc1key[3]; + enc1key[258] = enc1key[5]; + enc1key[259] = enc1key[7]; + enc1key[260] = enc1key[n1 & 0xff]; +} + + + + // 0048D910 +int func5(int cnt, unsigned char *id, int idlen, int *n1, int *n2) { + int i, + tmp, + mask = 1; + + if(!cnt) return(0); + if(cnt > 1) { + do { + mask = (mask << 1) + 1; + } while(mask < cnt); + } + + i = 0; + do { + *n1 = enc1key[*n1 & 0xff] + id[*n2]; + (*n2)++; + if(*n2 >= idlen) { + *n2 = 0; + *n1 += idlen; + } + tmp = *n1 & mask; + if(++i > 11) tmp %= cnt; + } while(tmp > cnt); + + return(tmp); +} + + + + // 0048DC20 +void func6(unsigned char *data, int len) { + while(len--) { + *data = func7(*data); + data++; + } +} + + + + // 0048DB10 +int func7(int len) { + unsigned char a, + b, + c; + + a = enc1key[256]; + b = enc1key[257]; + c = enc1key[a]; + enc1key[256] = a + 1; + enc1key[257] = b + c; + a = enc1key[260]; + b = enc1key[257]; + b = enc1key[b]; + c = enc1key[a]; + enc1key[a] = b; + a = enc1key[259]; + b = enc1key[257]; + a = enc1key[a]; + enc1key[b] = a; + a = enc1key[256]; + b = enc1key[259]; + a = enc1key[a]; + enc1key[b] = a; + a = enc1key[256]; + enc1key[a] = c; + b = enc1key[258]; + a = enc1key[c]; + c = enc1key[259]; + b = b + a; + enc1key[258] = b; + a = b; + c = enc1key[c]; + b = enc1key[257]; + b = enc1key[b]; + a = enc1key[a]; + c += b; + b = enc1key[260]; + b = enc1key[b]; + c += b; + b = enc1key[c]; + c = enc1key[256]; + c = enc1key[c]; + a += c; + c = enc1key[b]; + b = enc1key[a]; + a = len; + c ^= b; + enc1key[260] = a; + c ^= a; + enc1key[259] = c; + return(c); +} + + + + // 004294B0 +void func8(unsigned char *data, int len, const unsigned char *enctype1_data) { + while(len--) { + *data = enctype1_data[*data]; + data++; + } +} + + + + +int enctype1_wrapper(unsigned char *key, unsigned char *data, int size) { + unsigned char *p; + + p = enctype1_decoder(key, data, &size); + memmove(data, p, size); + return(size); +} + + diff --git a/gslist/src/enctype2_decoder.c b/gslist/src/enctype2_decoder.c new file mode 100644 index 0000000..5d8796e --- /dev/null +++ b/gslist/src/enctype2_decoder.c @@ -0,0 +1,113 @@ +/* + +GS enctype2 servers list decoder 0.1.1a +by Luigi Auriemma +e-mail: aluigi@autistici.org +web: aluigi.org + + +INTRODUCTION +============ +This is the algorithm used to decrypt the data sent by the Gamespy +master server (or any other compatible server) using the enctype 2 +method. + + +USAGE +===== +Add the following prototype at the beginning of your code: + + unsigned char *enctype2_decoder(unsigned char *, unsigned char *, int *); + +then use: + + pointer = enctype2_decoder( + gamekey, // the gamekey + buffer, // all the data received from the master server + &buffer_len); // the size of the master server + +The return value is a pointer to the decrypted zone of buffer and +buffer_len is modified with the size of the decrypted data. + +A simpler way to use the function is just using: + + len = enctype2_wrapper(key, data, data_len); + + +THANX TO +======== +REC (http://www.backerstreet.com/rec/rec.htm) which has helped me in many +parts of the code. + + +LICENSE +======= + Copyright 2004,2005,2006,2007,2008 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl.txt + +*/ + +#include + + + +void encshare1(unsigned int *tbuff, unsigned char *datap, int len); +void encshare4(unsigned char *src, int size, unsigned int *dest); + + + +unsigned char *enctype2_decoder(unsigned char *key, unsigned char *data, int *size) { + unsigned int dest[326]; + int i; + unsigned char *datap; + + *data ^= 0xec; + datap = data + 1; + + for(i = 0; key[i]; i++) datap[i] ^= key[i]; + + /* added by me */ + for(i = 256; i < 326; i++) dest[i] = 0; + + encshare4(datap, *data, dest); + + datap += *data; + *size -= (*data + 1); + if(*size < 6) { + *size = 0; + return(data); + } + + encshare1(dest, datap, *size); + + *size -= 6; + return(datap); +} + + + + +int enctype2_wrapper(unsigned char *key, unsigned char *data, int size) { + unsigned char *p; + + p = enctype2_decoder(key, data, &size); + memmove(data, p, size); + return(size); +} + + diff --git a/gslist/src/enctype_shared.c b/gslist/src/enctype_shared.c new file mode 100644 index 0000000..82a40cc --- /dev/null +++ b/gslist/src/enctype_shared.c @@ -0,0 +1,181 @@ +/* + Copyright 2005,2006,2007,2008 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl.txt + +*/ + +void encshare2(unsigned int *tbuff, unsigned int *tbuffp, int len) { + unsigned int t1, + t2, + t3, + t4, + t5, + *limit, + *p; + + t2 = tbuff[304]; + t1 = tbuff[305]; + t3 = tbuff[306]; + t5 = tbuff[307]; + limit = tbuffp + len; + while(tbuffp < limit) { + p = tbuff + t2 + 272; + while(t5 < 65536) { + t1 += t5; + p++; + t3 += t1; + t1 += t3; + p[-17] = t1; + p[-1] = t3; + t4 = (t3 << 24) | (t3 >> 8); + p[15] = t5; + t5 <<= 1; + t2++; + t1 ^= tbuff[t1 & 0xff]; + t4 ^= tbuff[t4 & 0xff]; + t3 = (t4 << 24) | (t4 >> 8); + t4 = (t1 >> 24) | (t1 << 8); + t4 ^= tbuff[t4 & 0xff]; + t3 ^= tbuff[t3 & 0xff]; + t1 = (t4 >> 24) | (t4 << 8); + } + t3 ^= t1; + *tbuffp++ = t3; + t2--; + t1 = tbuff[t2 + 256]; + t5 = tbuff[t2 + 272]; + t1 = ~t1; + t3 = (t1 << 24) | (t1 >> 8); + t3 ^= tbuff[t3 & 0xff]; + t5 ^= tbuff[t5 & 0xff]; + t1 = (t3 << 24) | (t3 >> 8); + t4 = (t5 >> 24) | (t5 << 8); + t1 ^= tbuff[t1 & 0xff]; + t4 ^= tbuff[t4 & 0xff]; + t3 = (t4 >> 24) | (t4 << 8); + t5 = (tbuff[t2 + 288] << 1) + 1; + } + tbuff[304] = t2; + tbuff[305] = t1; + tbuff[306] = t3; + tbuff[307] = t5; +} + + + +void encshare1(unsigned int *tbuff, unsigned char *datap, int len) { + unsigned char *p, + *s; + + p = s = (unsigned char *)(tbuff + 309); + encshare2(tbuff, (unsigned int *)p, 16); + + while(len--) { + if((p - s) == 63) { + p = s; + encshare2(tbuff, (unsigned int *)p, 16); + } + *datap ^= *p; + datap++; + p++; + } +} + + + +void encshare3(unsigned int *data, int n1, int n2) { + unsigned int t1, + t2, + t3, + t4; + int i; + + t2 = n1; + t1 = 0; + t4 = 1; + data[304] = 0; + for(i = 32768; i; i >>= 1) { + t2 += t4; + t1 += t2; + t2 += t1; + if(n2 & i) { + t2 = ~t2; + t4 = (t4 << 1) + 1; + t3 = (t2 << 24) | (t2 >> 8); + t3 ^= data[t3 & 0xff]; + t1 ^= data[t1 & 0xff]; + t2 = (t3 << 24) | (t3 >> 8); + t3 = (t1 >> 24) | (t1 << 8); + t2 ^= data[t2 & 0xff]; + t3 ^= data[t3 & 0xff]; + t1 = (t3 >> 24) | (t3 << 8); + } else { + data[data[304] + 256] = t2; + data[data[304] + 272] = t1; + data[data[304] + 288] = t4; + data[304]++; + t3 = (t1 << 24) | (t1 >> 8); + t2 ^= data[t2 & 0xff]; + t3 ^= data[t3 & 0xff]; + t1 = (t3 << 24) | (t3 >> 8); + t3 = (t2 >> 24) | (t2 << 8); + t3 ^= data[t3 & 0xff]; + t1 ^= data[t1 & 0xff]; + t2 = (t3 >> 24) | (t3 << 8); + t4 <<= 1; + } + } + data[305] = t2; + data[306] = t1; + data[307] = t4; + data[308] = n1; + // t1 ^= t2; +} + + + +void encshare4(unsigned char *src, int size, unsigned int *dest) { + unsigned int tmp; + int i; + unsigned char pos, + x, + y; + + for(i = 0; i < 256; i++) dest[i] = 0; + + for(y = 0; y < 4; y++) { + for(i = 0; i < 256; i++) { + dest[i] = (dest[i] << 8) + i; + } + + for(pos = y, x = 0; x < 2; x++) { + for(i = 0; i < 256; i++) { + tmp = dest[i]; + pos += tmp + src[i % size]; + dest[i] = dest[pos]; + dest[pos] = tmp; + } + } + } + + for(i = 0; i < 256; i++) dest[i] ^= i; + + encshare3(dest, 0, 0); +} + + diff --git a/gslist/src/enctypex_decoder.c b/gslist/src/enctypex_decoder.c new file mode 100644 index 0000000..b979b3a --- /dev/null +++ b/gslist/src/enctypex_decoder.c @@ -0,0 +1,685 @@ +/* +GS enctypeX servers list decoder/encoder 0.1.3b +by Luigi Auriemma +e-mail: aluigi@autistici.org +web: aluigi.org + +This is the algorithm used by ANY new and old game which contacts the Gamespy master server. +It has been written for being used in gslist so there are no explanations or comments here, +if you want to understand something take a look to gslist.c + + Copyright 2008-2012 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl-2.0.txt +*/ + +#include +#include +#include +#include +#include + + + +typedef struct { + unsigned char encxkey[261]; // static key + int offset; // everything decrypted till now (total) + int start; // where starts the buffer (so how much big is the header), this is the only one you need to zero +} enctypex_data_t; + + + +int enctypex_func5(unsigned char *encxkey, int cnt, unsigned char *id, int idlen, int *n1, int *n2) { + int i, + tmp, + mask = 1; + + if(!cnt) return(0); + if(cnt > 1) { + do { + mask = (mask << 1) + 1; + } while(mask < cnt); + } + + i = 0; + do { + *n1 = encxkey[*n1 & 0xff] + id[*n2]; + (*n2)++; + if(*n2 >= idlen) { + *n2 = 0; + *n1 += idlen; + } + tmp = *n1 & mask; + if(++i > 11) tmp %= cnt; + } while(tmp > cnt); + + return(tmp); +} + + + +void enctypex_func4(unsigned char *encxkey, unsigned char *id, int idlen) { + int i, + n1 = 0, + n2 = 0; + unsigned char t1, + t2; + + if(idlen < 1) return; + + for(i = 0; i < 256; i++) encxkey[i] = i; + + for(i = 255; i >= 0; i--) { + t1 = enctypex_func5(encxkey, i, id, idlen, &n1, &n2); + t2 = encxkey[i]; + encxkey[i] = encxkey[t1]; + encxkey[t1] = t2; + } + + encxkey[256] = encxkey[1]; + encxkey[257] = encxkey[3]; + encxkey[258] = encxkey[5]; + encxkey[259] = encxkey[7]; + encxkey[260] = encxkey[n1 & 0xff]; +} + + + +int enctypex_func7(unsigned char *encxkey, unsigned char d) { + unsigned char a, + b, + c; + + a = encxkey[256]; + b = encxkey[257]; + c = encxkey[a]; + encxkey[256] = a + 1; + encxkey[257] = b + c; + a = encxkey[260]; + b = encxkey[257]; + b = encxkey[b]; + c = encxkey[a]; + encxkey[a] = b; + a = encxkey[259]; + b = encxkey[257]; + a = encxkey[a]; + encxkey[b] = a; + a = encxkey[256]; + b = encxkey[259]; + a = encxkey[a]; + encxkey[b] = a; + a = encxkey[256]; + encxkey[a] = c; + b = encxkey[258]; + a = encxkey[c]; + c = encxkey[259]; + b += a; + encxkey[258] = b; + a = b; + c = encxkey[c]; + b = encxkey[257]; + b = encxkey[b]; + a = encxkey[a]; + c += b; + b = encxkey[260]; + b = encxkey[b]; + c += b; + b = encxkey[c]; + c = encxkey[256]; + c = encxkey[c]; + a += c; + c = encxkey[b]; + b = encxkey[a]; + encxkey[260] = d; + c ^= b ^ d; + encxkey[259] = c; + return(c); +} + + + +int enctypex_func7e(unsigned char *encxkey, unsigned char d) { + unsigned char a, + b, + c; + + a = encxkey[256]; + b = encxkey[257]; + c = encxkey[a]; + encxkey[256] = a + 1; + encxkey[257] = b + c; + a = encxkey[260]; + b = encxkey[257]; + b = encxkey[b]; + c = encxkey[a]; + encxkey[a] = b; + a = encxkey[259]; + b = encxkey[257]; + a = encxkey[a]; + encxkey[b] = a; + a = encxkey[256]; + b = encxkey[259]; + a = encxkey[a]; + encxkey[b] = a; + a = encxkey[256]; + encxkey[a] = c; + b = encxkey[258]; + a = encxkey[c]; + c = encxkey[259]; + b += a; + encxkey[258] = b; + a = b; + c = encxkey[c]; + b = encxkey[257]; + b = encxkey[b]; + a = encxkey[a]; + c += b; + b = encxkey[260]; + b = encxkey[b]; + c += b; + b = encxkey[c]; + c = encxkey[256]; + c = encxkey[c]; + a += c; + c = encxkey[b]; + b = encxkey[a]; + c ^= b ^ d; + encxkey[260] = c; // encrypt + encxkey[259] = d; // encrypt + return(c); +} + + + +int enctypex_func6(unsigned char *encxkey, unsigned char *data, int len) { + int i; + + for(i = 0; i < len; i++) { + data[i] = enctypex_func7(encxkey, data[i]); + } + return(len); +} + + + +int enctypex_func6e(unsigned char *encxkey, unsigned char *data, int len) { + int i; + + for(i = 0; i < len; i++) { + data[i] = enctypex_func7e(encxkey, data[i]); + } + return(len); +} + + + +void enctypex_funcx(unsigned char *encxkey, unsigned char *key, unsigned char *encxvalidate, unsigned char *data, int datalen) { + int i, + keylen; + + keylen = strlen(key); + for(i = 0; i < datalen; i++) { + encxvalidate[(key[i % keylen] * i) & 7] ^= encxvalidate[i & 7] ^ data[i]; + } + enctypex_func4(encxkey, encxvalidate, 8); +} + + + +static int enctypex_data_cleaner_level = 2; // 0 = do nothing + // 1 = colors + // 2 = colors + strange chars + // 3 = colors + strange chars + sql + + + +int enctypex_data_cleaner(unsigned char *dst, unsigned char *src, int max) { + static const unsigned char strange_chars[] = { + ' ','E',' ',',','f',',','.','t',' ','^','%','S','<','E',' ','Z', + ' ',' ','`','`','"','"','.','-','-','~','`','S','>','e',' ','Z', + 'Y','Y','i','c','e','o','Y','I','S','`','c','a','<','-','-','E', + '-','`','+','2','3','`','u','P','-',',','1','`','>','%','%','%', + '?','A','A','A','A','A','A','A','C','E','E','E','E','I','I','I', + 'I','D','N','O','O','O','O','O','x','0','U','U','U','U','Y','D', + 'B','a','a','a','a','a','a','e','c','e','e','e','e','i','i','i', + 'i','o','n','o','o','o','o','o','+','o','u','u','u','u','y','b', + 'y' }; + unsigned char c, + *p; + + if(!dst) return(0); + if(dst != src) dst[0] = 0; // the only change in 0.1.3a + if(!src) return(0); + + if(max < 0) max = strlen(src); + + for(p = dst; (c = *src) && (max > 0); src++, max--) { + if(c == '\\') { // avoids the backslash delimiter + *p++ = '/'; + continue; + } + + if(enctypex_data_cleaner_level >= 1) { + if(c == '^') { // Quake 3 colors + //if(src[1] == 'x') { // ^x112233 (I don't remember the game which used this format) + //src += 7; + //max -= 7; + //} else + if(isdigit(src[1]) || islower(src[1])) { // ^0-^9, ^a-^z... a good compromise + src++; + max--; + } else { + *p++ = c; + } + continue; + } + if(c == 0x1b) { // Unreal colors + src += 3; + max -= 3; + continue; + } + if(c < ' ') { // other colors + continue; + } + } + + if(enctypex_data_cleaner_level >= 2) { + if(c >= 0x7f) c = strange_chars[c - 0x7f]; + } + + if(enctypex_data_cleaner_level >= 3) { + switch(c) { // html/SQL injection (paranoid mode) + case '\'': + case '\"': + case '&': + case '^': + case '?': + case '{': + case '}': + case '(': + case ')': + case '[': + case ']': + case '-': + case ';': + case '~': + case '|': + case '$': + case '!': + case '<': + case '>': + case '*': + case '%': + case ',': c = '.'; break; + default: break; + } + } + + if((c == '\r') || (c == '\n')) { // no new line + continue; + } + *p++ = c; + } + *p = 0; + return(p - dst); +} + + + + // function not related to the algorithm, I have created it only for a quick handling of the received data + // very quick explanation: + // - if you use out it will be considered as an output buffer where placing all the IP and ports of the servers in the classical format: 4 bytes for IP and 2 for the port + // - if you don't use out the function will return a non-zero value if you have received all the data from the master server + // - if you use infobuff it will be considered as an output buffer where placing all the informations of one server at time in the format "IP:port \parameter1\value1\...\parameterN\valueN" + // - infobuff_size is used to avoid to write more data than how much supported by infobuff + // - infobuff_offset instead is used for quickly handling the next servers for infobuff because, as just said, the function handles only one server at time + // infobuff_offset is just the offset of the server to handle in our enctypex buffer, the function returns the offset to the next one or a value zero or -1 if there are no other hosts + // data and out can't be the same buffer because in some games like AA the gamespy master server returns + // 5 bytes for each IP/port and so there is the risk of overwriting the data to handle, that's why I use + // an output buffer which is at least "(datalen / 5) * 6" bytes long +int enctypex_decoder_convert_to_ipport(unsigned char *data, int datalen, unsigned char *out, unsigned char *infobuff, int infobuff_size, int infobuff_offset) { +#define enctypex_infobuff_check(X) \ + if(infobuff) { \ + if((int)(infobuff_size - infobuff_len) <= (int)(X)) { \ + infobuff_size = 0; \ + } else + + typedef struct { + unsigned char type; + unsigned char *name; + } par_t; + + int i, + len, + pars = 0, // pars and vals are used for making the function + vals = 0, // thread-safe when infobuff is not used + infobuff_len = 0; + unsigned char tmpip[6], + port[2], + t, + *p, + *o, + *l; + static const int use_parval = 1; // par and val are required, so this bool is useless + static unsigned char // this function is not thread-safe if you use it for retrieving the extra data (infobuff) + parz = 0, + valz = 0, + **val = NULL; + static par_t *par = NULL; // par[255] and *val[255] was good too + + if(!data) return(0); + if(datalen < 6) return(0); // covers the 6 bytes of IP:port + o = out; + p = data; + l = data + datalen; + + p += 4; // your IP + port[0] = *p++; // the most used port + port[1] = *p++; + if((port[0] == 0xff) && (port[1] == 0xff)) { + return(-1); // error message from the server + } + + if(infobuff && infobuff_offset) { // restore the data + p = data + infobuff_offset; + } else { + if(p < l) { + pars = *p++; + if(use_parval) { // save the static data + parz = pars; + par = realloc(par, sizeof(par_t) * parz); + } + for(i = 0; (i < pars) && (p < l); i++) { + t = *p++; + if(use_parval) { + par[i].type = t; + par[i].name = p; + } + p += strlen(p) + 1; + } + } + if(p < l) { + vals = *p++; + if(use_parval) { // save the static data + valz = vals; + val = realloc(val, sizeof(unsigned char *) * valz); + } + for(i = 0; (i < vals) && (p < l); i++) { + if(use_parval) val[i] = p; + p += strlen(p) + 1; + } + } + } + + if(use_parval) { + pars = parz; + vals = valz; + } + if(infobuff && (infobuff_size > 0)) { + infobuff[0] = 0; + } + + while(p < l) { + t = *p++; + if(!t && !memcmp(p, "\xff\xff\xff\xff", 4)) { + if(!out) o = out - 1; // so the return is not 0 and means that we have reached the end + break; + } + len = 5; + if(t & 0x02) len = 9; + if(t & 0x08) len += 4; + if(t & 0x10) len += 2; + if(t & 0x20) len += 2; + + tmpip[0] = p[0]; + tmpip[1] = p[1]; + tmpip[2] = p[2]; + tmpip[3] = p[3]; + if((len < 6) || !(t & 0x10)) { + tmpip[4] = port[0]; + tmpip[5] = port[1]; + } else { + tmpip[4] = p[4]; + tmpip[5] = p[5]; + } + + if(out) { + memcpy(o, tmpip, 6); + o += 6; + } + enctypex_infobuff_check(22) { + infobuff_len = sprintf(infobuff, + "%u.%u.%u.%u:%hu ", + tmpip[0], tmpip[1], tmpip[2], tmpip[3], + (unsigned short)((tmpip[4] << 8) | tmpip[5])); + }} + + p += len - 1; // the value in len is no longer used from this point + if(t & 0x40) { + for(i = 0; (i < pars) && (p < l); i++) { + enctypex_infobuff_check(1 + strlen(par[i].name) + 1) { + infobuff[infobuff_len++] = '\\'; + infobuff_len += enctypex_data_cleaner(infobuff + infobuff_len, par[i].name, -1); + infobuff[infobuff_len++] = '\\'; + infobuff[infobuff_len] = 0; + }} + t = *p++; + + if(use_parval) { + if(!par[i].type) { // string + if(t == 0xff) { // inline string + enctypex_infobuff_check(strlen(p)) { + infobuff_len += enctypex_data_cleaner(infobuff + infobuff_len, p, -1); + }} + p += strlen(p) + 1; + } else { // fixed string + if(t < vals) { + enctypex_infobuff_check(strlen(val[t])) { + infobuff_len += enctypex_data_cleaner(infobuff + infobuff_len, val[t], -1); + }} + } + } + } else { // number (-128 to 127) + enctypex_infobuff_check(5) { + infobuff_len += sprintf(infobuff + infobuff_len, "%d", (signed char)t); + }} + } + } + } + } + if(infobuff) { // do NOT touch par/val, I use realloc + return(p - data); + } + } + + if((out == data) && ((o - out) > (p - data))) { // I need to remember this + fprintf(stderr, "\nError: input and output buffer are the same and there is not enough space\n"); + exit(1); + } + if(infobuff) { // do NOT touch par/val, I use realloc + parz = 0; + valz = 0; + return(-1); + } + return(o - out); +} + + + +int enctypex_decoder_rand_validate(unsigned char *validate) { + int i, + rnd; + + rnd = ~time(NULL); + for(i = 0; i < 8; i++) { + do { + rnd = ((rnd * 0x343FD) + 0x269EC3) & 0x7f; + } while((rnd < 0x21) || (rnd >= 0x7f)); + validate[i] = rnd; + } + validate[i] = 0; + return(i); +} + + + +unsigned char *enctypex_init(unsigned char *encxkey, unsigned char *key, unsigned char *validate, unsigned char *data, int *datalen, enctypex_data_t *enctypex_data) { + int a, + b; + unsigned char encxvalidate[8]; + + if(*datalen < 1) return(NULL); + a = (data[0] ^ 0xec) + 2; + if(*datalen < a) return(NULL); + b = data[a - 1] ^ 0xea; + if(*datalen < (a + b)) return(NULL); + memcpy(encxvalidate, validate, 8); + enctypex_funcx(encxkey, key, encxvalidate, data + a, b); + a += b; + if(!enctypex_data) { + data += a; + *datalen -= a; // datalen is untouched in stream mode!!! + } else { + enctypex_data->offset = a; + enctypex_data->start = a; + } + return(data); +} + + + +unsigned char *enctypex_decoder(unsigned char *key, unsigned char *validate, unsigned char *data, int *datalen, enctypex_data_t *enctypex_data) { + unsigned char encxkeyb[261], + *encxkey; + + encxkey = enctypex_data ? enctypex_data->encxkey : encxkeyb; + + if(!enctypex_data || (enctypex_data && !enctypex_data->start)) { + data = enctypex_init(encxkey, key, validate, data, datalen, enctypex_data); + if(!data) return(NULL); + } + if(!enctypex_data) { + enctypex_func6(encxkey, data, *datalen); + return(data); + } else if(enctypex_data && enctypex_data->start) { + enctypex_data->offset += enctypex_func6(encxkey, data + enctypex_data->offset, *datalen - enctypex_data->offset); + return(data + enctypex_data->start); + } + return(NULL); +} + + + +// exactly as above but with enctypex_func6e instead of enctypex_func6 +unsigned char *enctypex_encoder(unsigned char *key, unsigned char *validate, unsigned char *data, int *datalen, enctypex_data_t *enctypex_data) { + unsigned char encxkeyb[261], + *encxkey; + + encxkey = enctypex_data ? enctypex_data->encxkey : encxkeyb; + + if(!enctypex_data || (enctypex_data && !enctypex_data->start)) { + data = enctypex_init(encxkey, key, validate, data, datalen, enctypex_data); + if(!data) return(NULL); + } + if(!enctypex_data) { + enctypex_func6e(encxkey, data, *datalen); + return(data); + } else if(enctypex_data && enctypex_data->start) { + enctypex_data->offset += enctypex_func6e(encxkey, data + enctypex_data->offset, *datalen - enctypex_data->offset); + return(data + enctypex_data->start); + } + return(NULL); +} + + + +unsigned char *enctypex_msname(unsigned char *gamename, unsigned char *retname) { + static unsigned char msname[256]; + unsigned i, + c, + server_num; + + if(!gamename) return(NULL); + + server_num = 0; + for(i = 0; gamename[i]; i++) { + c = tolower(gamename[i]); + server_num = c - (server_num * 0x63306ce7); + } + server_num %= 20; + + if(retname) { + snprintf(retname, 256, "%s.ms%d.gamespy.com", gamename, server_num); + return(retname); + } + snprintf(msname, sizeof(msname), "%s.ms%d.gamespy.com", gamename, server_num); + return(msname); +} + + + +int enctypex_wrapper(unsigned char *key, unsigned char *validate, unsigned char *data, int size) { + int i; + unsigned char *p; + + if(!key || !validate || !data || (size < 0)) return(0); + + p = enctypex_decoder(key, validate, data, &size, NULL); + if(!p) return(-1); + for(i = 0; i < size; i++) { + data[i] = p[i]; + } + return(size); +} + + + +// data must be enough big to include the 23 bytes header, remember it: data = realloc(data, size + 23); +int enctypex_quick_encrypt(unsigned char *key, unsigned char *validate, unsigned char *data, int size) { + int i, + rnd, + tmpsize, + keylen, + vallen; + unsigned char tmp[23]; + + if(!key || !validate || !data || (size < 0)) return(0); + + keylen = strlen(key); // only for giving a certain randomness, so useless + vallen = strlen(validate); + rnd = ~time(NULL); + for(i = 0; i < sizeof(tmp); i++) { + rnd = (rnd * 0x343FD) + 0x269EC3; + tmp[i] = rnd ^ key[i % keylen] ^ validate[i % vallen]; + } + tmp[0] = 0xeb; // 7 + tmp[1] = 0x00; + tmp[2] = 0x00; + tmp[8] = 0xe4; // 14 + + for(i = size - 1; i >= 0; i--) { + data[sizeof(tmp) + i] = data[i]; + } + memcpy(data, tmp, sizeof(tmp)); + size += sizeof(tmp); + + tmpsize = size; + enctypex_encoder(key, validate, data, &tmpsize, NULL); + return(size); +} + + diff --git a/gslist/src/gscfg.h b/gslist/src/gscfg.h new file mode 100644 index 0000000..67a1344 --- /dev/null +++ b/gslist/src/gscfg.h @@ -0,0 +1,398 @@ +/* + Copyright 2005-2011 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl-2.0.txt +*/ + +void handoff2gamekey(u8 *out, u8 *in) { + out[0] = in[2]; + out[1] = in[4]; + out[2] = in[6]; + out[3] = in[8]; + out[4] = in[10]; + out[5] = in[12]; +} + + + + /* scans full.cfg to know if game already exists */ +int check_updates(u8 *game, FILE *fdfull) { + int type; + u8 buff[FULLSZ + 1], + *par, + *val; + + rewind(fdfull); + while((type = myreadini(fdfull, buff, sizeof(buff), &par, &val)) >= 0) { + if(type) continue; + if(!strcmp(game, par)) return(0); + } + return(1); +} + + + +void verify_gamespy(void) { + FILE *fdsvc, + *fdfull; + int i, + part, + firstlen; + u8 getfile[GETSZ + 1], + tot[KNOWNSZ + 1], + *s, + *p; + + //fprintf(stderr, "- open %s\n", KNOWNCFG); + fdsvc = gslfopen(KNOWNCFG, "rb"); + if(!fdsvc) std_err(); + + //fprintf(stderr, "- update %s\n", FULLCFG); + fdfull = gslfopen(FULLCFG, "r+b"); + if(!fdfull) { + //fprintf(stderr, "- create %s\n", FULLCFG); + fdfull = gslfopen(FULLCFG, "wb"); + if(!fdfull) std_err(); + } + + firstlen = mystrcpy(getfile, "software/services/index.aspx?mode=full&services=", sizeof(getfile)); + part = 0; + while(!feof(fdsvc)) { + p = getfile + firstlen; + i = 0; + + while(fgets(tot, sizeof(tot), fdsvc)) { + if(tot[0] == '-') continue; + + s = strchr(tot, ' '); + if(!s) continue; + *s = 0; + + if(!check_updates(tot, fdfull)) continue; + + p += mystrcpy(p, tot, sizeof(getfile) - (p - getfile)); + if(++i == MAXNAMESREQ) break; + *p++ = '\\'; + } + + if(!i) break; + if(*(p - 1) == '\\') p--; + *p = 0; + + fseek(fdfull, 0, SEEK_END); // append +// if(http2file(HOST, 28900, getfile, fdfull, 0) <= 0) { + fprintf(stderr, "- download part: %d\n", ++part); + if(mydown_http2file( + NULL, // int *sock + 0, // int timeout + HOST, // u8 *host + 28900, // u16 port + NULL, // u8 *user + NULL, // u8 *pass + NULL, // u8 *referer + GSUSERAGENT, // u8 *useragent + NULL, // u8 *cookie + NULL, // u8 *more_http + 0, // int verbose + getfile, // u8 *getstr + fdfull, // FILE *fd + NULL, // u8 *filename + 0, // int showhead + 0, // int onlyifdiff + 0, // int resume + 0, // u32 from + 0, // u32 tot + NULL, // u32 *filesize + NULL, // u8 *filedata + NULL, // int *ret_code + 0, // int onflyunzip + NULL, // u8 *content + 0, // int contentsize + NULL, // u8 *get + NULL, + 0, + NULL + ) <= 0) { + fprintf(stderr, "- update interrupted for errors on the server, retry later or tomorrow\n\n"); + break; + } + fprintf(stderr, "\n"); + fflush(fdfull); // flush + } + + fclose(fdsvc); + fclose(fdfull); +} + + + +int strlen_spaces(u8 *str, int limit) { + u8 *p; + + if(limit < 0) limit = strlen(str); + for(p = str + limit - 1; p >= str; p--) { + if(*p > ' ') return((p + 1) - str); + } + return(0); +} + + + +int gslistcfgsort(void) { + FILE *fd; + int i, + j, + res, + tot, + sort_list; + u8 **buff, + *p, + tmp[GSLISTSZ + 1 + 32]; // 32 is not needed + + //fprintf(stderr, "- open %s\n", GSLISTTMP); + fd = gslfopen(GSLISTTMP, "rb"); + if(!fd) std_err(); + + for(tot = 0; fgets(tmp, sizeof(tmp), fd); tot++); // count the lines + rewind(fd); + + buff = calloc(tot, sizeof(u8 *)); // allocate space for pointers + if(!buff) std_err(); + + for(i = 0; i < tot; i++) { + fgets(tmp, sizeof(tmp), fd); + for(p = tmp; *p && (*p != '\r') && (*p != '\n'); p++); + *p = 0; + buff[i] = strdup(tmp); + if(!buff[i]) std_err(); + } + fclose(fd); + + fprintf(stderr, "- build, clean & sort %s\n", GSLISTCFG); + fd = gslfopen(GSLISTCFG, "wb"); + if(!fd) std_err(); + + // sort_list: + // 0 = removes the same gamenames, removes those that don't have a gamekey, sort by gamename + // 1 = sort by game description + + for(sort_list = 0; sort_list < 2; sort_list++) { + for(i = 0; i < (tot - 1); i++) { + if(!buff[i] || !buff[i][0]) continue; + for(j = i + 1; j < tot; j++) { + if(!buff[j] || !buff[j][0]) continue; + + if(!sort_list) { + res = strnicmp(buff[j] + CNAMEOFF, buff[i] + CNAMEOFF, CKEYOFF - CNAMEOFF); // gamename only + //res = stricmp(buff[j] + CNAMEOFF, buff[i] + CNAMEOFF); // check both gamename and gamekey + } else if(sort_list == 1) { + res = stricmp(buff[j], buff[i]); + } + if(!res) { // removes duplicates + if(!sort_list) { + if((strlen(buff[i] + CNAMEOFF) <= (CKEYOFF - CNAMEOFF)) || (buff[i][CKEYOFF] <= ' ')) { + buff[i][0] = 0; // the current game doesn't have a gamekey + } else if((strlen(buff[j] + CNAMEOFF) <= (CKEYOFF - CNAMEOFF)) || (buff[j][CKEYOFF] <= ' ')) { + buff[j][0] = 0; // the compared game doesn't have a gamekey + } else if(stricmp(buff[j] + CKEYOFF, buff[i] + CKEYOFF)) { + continue; // different gamekey + } else if(strlen_spaces(buff[j], CNAMEOFF) <= strlen_spaces(buff[i], CNAMEOFF)) { + buff[j][0] = 0; // the current description is longer (longer is better?) + } else { + buff[i][0] = 0; // the compared one is better so delete the current one + } + } else if(sort_list == 1) { + buff[i][0] = 0; + } + } else if(res < 0) { // sort + p = buff[j]; + buff[j] = buff[i]; + buff[i] = p; + } + } + } + } + + for(i = 0; i < tot; i++) { + if(!buff[i]) continue; + if(buff[i][0]) fprintf(fd, "%s\n", buff[i]); // \n for Unix notation (the one I have ever used) + FREEX(buff[i]); + } + FREEX(buff); + + fprintf(fd, "%s%s\n", GSLISTVER, VER); + fclose(fd); + return(tot); +} + + + +void build_cfg(void) { + FILE *fd, + *fdcfg, + *fdtmp; + int type; + u8 buff[FULLSZ + 1], + tot[GSLISTSZ + 1], + *p, + *gamename = NULL, + *gamekey = NULL, + *par, + *val; + + //fprintf(stderr, "- open %s\n", FULLCFG); + fd = gslfopen(FULLCFG, "rb"); + if(!fd) std_err(); + + //fprintf(stderr, "- build %s\n", GSLISTTMP); + fdtmp = gslfopen(GSLISTTMP, "wb"); + if(!fdtmp) std_err(); + + //fprintf(stderr, "- copy old database from %s\n", GSLISTCFG); + fdcfg = gslfopen(GSLISTCFG, "rb"); + if(fdcfg) { + while(fgets(tot, sizeof(tot), fdcfg)) { + if(!strncmp(tot, GSLISTVER, sizeof(GSLISTVER) - 1)) continue; + fputs(tot, fdtmp); + } + fclose(fdcfg); + } + +#define GSLISTCFGRESET memset(tot, ' ', GSLISTSZ - 1); \ + tot[GSLISTSZ - 1] = '\n'; \ + tot[GSLISTSZ] = 0; +#define GSLISTCFGPUT if(tot[0] > ' ') { \ + fputs(tot, fdtmp); \ + GSLISTCFGRESET; \ + } + + GSLISTCFGRESET + + while((type = myreadini(fd, buff, sizeof(buff), &par, &val)) >= 0) { + if(!type) { + GSLISTCFGPUT + memcpy(tot + CNAMEOFF, par, strlen(par)); + memcpy(tot + CFNAMEOFF, par, strlen(par)); // safe + tot[0] = toupper(tot[0]); + + } else if(!stricmp(par, "fullname")) { + if(strlen(val) > CFNAMELEN) val[CFNAMELEN] = 0; + memcpy(tot + CFNAMEOFF, val, strlen(val)); + + } else if(!stricmp(par, "querygame")) { // some games like "Leadfoot Demo" have [leadfootd] and "querygame=leadfoot" + if(strlen(val) > CNAMELEN) val[CNAMELEN] = 0; + memset(tot + CNAMEOFF, ' ', CNAMELEN); + memcpy(tot + CNAMEOFF, val, strlen(val)); + + } else if(!stricmp(par, "handoff")) { + handoff2gamekey(tot + CKEYOFF, val); + } + } + GSLISTCFGPUT // remaining, this is the only compatible way + + fclose(fd); + + //fprintf(stderr, "- copy database from %s\n", GSHKEYSCFG); + fd = gslfopen(GSHKEYSCFG, "rb"); + if(!fd) std_err(); + + while(fgets(buff, sizeof(buff), fd)) { // autoconfig + if(strncmp(buff, "Description", 11)) continue; + gamename = strstr(buff, "gamename"); + if(!gamename) continue; + gamekey = strstr(buff, "gamekey"); + if(!gamekey) continue; + break; + } + + while(fgets(buff, sizeof(buff), fd)) { + if(*buff == '=') continue; + if(*buff <= ' ') break; + + p = delimit(buff); + if((p - buff) > GSLISTSZ) continue; + GSLISTCFGRESET + + memcpy(tot + CFNAMEOFF, buff, gamename - buff); + if(p >= (gamekey + 6)) { + memcpy(tot + CNAMEOFF, gamename, gamekey - gamename); + memcpy(tot + CKEYOFF, gamekey, 6); + } else { + memcpy(tot + CNAMEOFF, gamename, strlen(gamename)); + } + + fputs(tot, fdtmp); + } + + fclose(fd); + fclose(fdtmp); + + fprintf(stderr, "- the database contains %d total entries\n", gslistcfgsort()); + +#undef GSLISTCFGRESET +#undef GSLISTCFGPUT +} + + + +int make_gslistcfg(int clean) { + FILE *fd; + int size1, + size2; + struct stat xstat; + + fprintf(stderr, "- start data files downloading:\n"); + + if(clean) { // unlink all the existent files! + fd = gslfopen(KNOWNCFG, "wb"); + if(!fd) std_err(); + fclose(fd); + + fd = gslfopen(FULLCFG, "wb"); + if(!fd) std_err(); + fclose(fd); + + fd = gslfopen(GSLISTCFG, "wb"); + if(!fd) std_err(); + fclose(fd); + } + + size1 = cool_download(GSHKEYSCFG, ALUIGIHOST, 80, "papers/" GSHKEYSCFG); + fprintf(stderr, "---\n"); + + size2 = cool_download(KNOWNCFG, HOST, 28900, "software/services/index.aspx"); + fprintf(stderr, "---\n"); + + fd = gslfopen(GSLISTCFG, "rb"); + if(fd) fclose(fd); + + if(!fd || (size1 > 0) || (size2 > 0)) { + verify_gamespy(); + build_cfg(); + } else { + fprintf(stderr, "- no updates available\n"); + } + + cool_download(DETECTCFG, HOST, 28900, "software/services/index.aspx?mode=detect"); + fprintf(stderr, "---\n"); + + // check it each 15 days + + return(0); +} + + diff --git a/gslist/src/gshttp.h b/gslist/src/gshttp.h new file mode 100644 index 0000000..9e4bf29 --- /dev/null +++ b/gslist/src/gshttp.h @@ -0,0 +1,40 @@ +/* + Copyright 2005-2011 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl-2.0.txt +*/ + +int cool_download(u8 *fname, u8 *host, u16 port, u8 *uri) { + mydown_options mop; + int size; + u8 *url; + + fname = gslfopen(fname, NULL); + spr(&url, "http://%s:%hu/%s", host, port, uri); + + memset(&mop, 0, sizeof(mop)); + mop.onlyifdiff = 1; // doesn't download the file if size is the same + mop.useragent = GSUSERAGENT; + mop.onflyunzip = 1; + + size = mydown(url, fname, &mop); + FREEX(url); + FREEX(fname); + return(size); +} + + diff --git a/gslist/src/gslegacy.h b/gslist/src/gslegacy.h new file mode 100644 index 0000000..9eba432 --- /dev/null +++ b/gslist/src/gslegacy.h @@ -0,0 +1,302 @@ +/* + Copyright 2005-2011 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl-2.0.txt +*/ + + + + /***************************************************************\ + |* this file contains the old functions mainly used by gsweb.h *| + |* it's almost no longer supported *| + \***************************************************************/ + + + +u8 *read_config_entry(FILE *fd, u32 startoff, u8 *key, u8 **out) { + u32 endoff = startoff; + int type; + u8 buff[DETECTSZ + 1], + *par, + *val; + + *out = NULL; + fseek(fd, startoff, SEEK_SET); + + while((type = myreadini(fd, buff, sizeof(buff), &par, &val)) >= 0) { + if(!type) break; + if(!strcmp(par, key)) { + *out = strdup(val); + } + endoff = ftell(fd); + } + + if(!feof(fd)) fseek(fd, endoff, SEEK_SET); + return(*out); +} + + + +u8 *myitoa(int num) { + static u8 mini[12]; + + sprintf(mini, "%d", num); + return(mini); +} + + + +void zero_equal(u8 *end) { + while(*end <= ' ') end--; + *(end + 1) = 0; +} + + + +void get_key(u8 *gamestr, u8 *gamekey, u8 *gamefull) { + FILE *fd; + u8 buff[GSLISTSZ + 1], + *p; + + fd = gslfopen(GSLISTCFG, "rb"); + if(!fd) { + fputs("- you must use the -u or -U option the first time!", stderr); + std_err(); + } + + *gamekey = 0; + + while(fgets(buff, sizeof(buff), fd)) { + p = strchr(buff + CNAMEOFF, ' '); + if(p) *p = 0; + if(!strcmp(gamestr, buff + CNAMEOFF)) { + memcpy(gamekey, buff + CKEYOFF, 6); + gamekey[6] = 0; + if(gamefull) { + zero_equal(buff + CFNAMEEND); + strcpy(gamefull, buff + CFNAMEOFF); + } + if(*gamekey <= ' ') *gamekey = 0; + break; + } + } + fclose(fd); +} + + + +u32 find_config_entry(FILE *fd, u8 **out) { + u32 startoff = 0; + int type; + u8 buff[DETECTSZ + 1], + *par, + *val; + + *out = NULL; + + while((type = myreadini(fd, buff, sizeof(buff), &par, &val)) >= 0) { + if(type) continue; + *out = strdup(par); + startoff = ftell(fd); + break; + } + + return(startoff); +} + + + +#define FREEGSWEB(A) if(A) { \ + FREEX(A); \ + A = NULL; \ + } + +void free_ipdata(ipdata_t *d, int elements) { + int i; + + for(i = 0; i < elements; i++) { + FREEGSWEB(d[i].name); + FREEGSWEB(d[i].map); + FREEGSWEB(d[i].type); + FREEGSWEB(d[i].ver); + FREEGSWEB(d[i].mod); + FREEGSWEB(d[i].mode); + } + FREEGSWEB(d); +} + + + +void free_gsw_scan_data(gsw_scan_data_t *d, int elements) { + int i; + + for(i = 0; i < elements; i++) { + FREEGSWEB(d[i].full); + FREEGSWEB(d[i].game); + FREEGSWEB(d[i].exe); + } + FREEGSWEB(d); +} + + + +void free_gsw_fav_data(gsw_fav_data_t *d, int elements) { + int i; + + for(i = 0; i < elements; i++) { + FREEGSWEB(d[i].game); + FREEGSWEB(d[i].pass); + } + FREEGSWEB(d); +} + + + +void free_gsw_data(gsw_data_t *d, int elements) { + int i; + + for(i = 0; i < elements; i++) { + FREEGSWEB(d[i].game); + FREEGSWEB(d[i].key); + FREEGSWEB(d[i].full); + FREEGSWEB(d[i].path); + FREEGSWEB(d[i].filter); + } + FREEGSWEB(d); +} + + + +#define MYDUP(A,B) A = mydup(A, B); +u8 *mydup(u8 *key, u8 *val) { + FREEX(key); + return(strdup(val)); +} + + + +int gsw_stricmp(const char *s1, const char *s2) { + u8 a, + b; + + if(!s1 && !s2) return(0); + if(!s1) return(-1); + if(!s2) return(1); + + while(*s1 && (*s1 <= ' ')) s1++; // I don't want to sort the spaces + while(*s2 && (*s2 <= ' ')) s2++; + + for(;;) { + a = tolower(*s1); + b = tolower(*s2); + if(!a && !b) break; + if(!a) return(-1); + if(!b) return(1); + if(a < b) return(-1); + if(a > b) return(1); + s1++; + s2++; + } + return(0); +} + + + +void gsw_sort_IP(ipdata_t *gip, int servers, int sort_type) { + ipdata_t xchg; + int i, + j; + +#define GSW_SORT_FUNCTION(COMPARE) \ + for(i = 0; i < (servers - 1); i++) { \ + if(gip[i].sort == IPDATA_SORT_CLEAR) gip[i].ping = -1; \ + for(j = i + 1; j < servers; j++) { \ + if(COMPARE) { \ + xchg = gip[j]; \ + gip[j] = gip[i]; \ + gip[i] = xchg; \ + } \ + } \ + } + + if(sort_type == GSW_SORT_PING) { + GSW_SORT_FUNCTION(gip[i].ping > gip[j].ping) + + } else if(sort_type == GSW_SORT_NAME) { + GSW_SORT_FUNCTION(gsw_stricmp(gip[i].name, gip[j].name) > 0) + + } else if(sort_type == GSW_SORT_MAP) { + GSW_SORT_FUNCTION(gsw_stricmp(gip[i].map, gip[j].map) > 0) + + } else if(sort_type == GSW_SORT_TYPE) { + GSW_SORT_FUNCTION(gsw_stricmp(gip[i].type, gip[j].type) > 0) + + } else if(sort_type == GSW_SORT_PLAYER) { + GSW_SORT_FUNCTION(gip[i].players > gip[j].players) + + } else if(sort_type == GSW_SORT_VER) { + GSW_SORT_FUNCTION(gsw_stricmp(gip[i].ver, gip[j].ver) > 0) + + } else if(sort_type == GSW_SORT_MOD) { + GSW_SORT_FUNCTION(gsw_stricmp(gip[i].mod, gip[j].mod) > 0) + + } else if(sort_type == GSW_SORT_DED) { + GSW_SORT_FUNCTION(gip[i].ded < gip[j].ded) + + } else if(sort_type == GSW_SORT_PWD) { + GSW_SORT_FUNCTION(gip[i].pwd < gip[j].pwd) + + } else if(sort_type == GSW_SORT_PB) { + GSW_SORT_FUNCTION(gip[i].pb < gip[j].pb) + + } else if(sort_type == GSW_SORT_RANK) { + GSW_SORT_FUNCTION(gip[i].rank < gip[j].rank) + + } else if(sort_type == GSW_SORT_MODE) { + GSW_SORT_FUNCTION(gsw_stricmp(gip[i].mode, gip[j].mode) > 0) + + } else if(sort_type == GSW_SORT_COUNTRY) { + GSW_SORT_FUNCTION(gsw_stricmp(gip[i].country, gip[j].country) > 0) + } + +#undef GSW_SORT_FUNCTION + +/* OLD lame method + for(curr = servers; curr; curr--) { + ping = -1; + pingptr = 0; + for(rest = curr, i = 0; i < servers; i++) { + // not yet sorted && minor ping + if((gip[i].sort != IPDATA_SORT_SORTED) && (gip[i].ping < ping)) { + pingptr = i; + ping = gip[i].ping; + if(!--rest) break; + } + } + + i = pingptr; + if(gip[i].sort == IPDATA_SORT_CLEAR) { + if(gsw_noping) break; + gip[i].ping = 0; + } + gip[i].sort = IPDATA_SORT_SORTED; + + gsweb_show_ipport(sock, game, &gip[i], "", 0); + } +*/ +} + diff --git a/gslist/src/gslist.c b/gslist/src/gslist.c new file mode 100644 index 0000000..c53ce84 --- /dev/null +++ b/gslist/src/gslist.c @@ -0,0 +1,1231 @@ +/* + +====== +Gslist +====== + +INTRODUCTION +------------ +Gslist is a game servers browser and heartbeats sender, it supports an +incredible amount of games and moreover a lot of options making it +simple and complete at the same time. +Then it is opensource and can be compiled on any system with small +changes (if needed). + +All this software is based on the FREE and OFFICIAL PUBLIC data +available online just by the same Gamespy on their PUBLIC servers. + + +CONTRIBUTORS +------------ +- Steve Hartland: filters (cool) and FREEBSD compatibility +- Cimuca Cristian: bug reports and ideas +- Ludwig Nussel: gslist user's directory (very useful in Unix), quiet + option and other tips +- Ben "LordNikon": web interface optimizations +- Kris Sum: suggestions for the web interface +- KaZaMa: many suggestions for the web interface +- ouioui: many feedback about errors in the experimental SQL implementation +- CHC: peerchat rooms and other suggestions +- all the others that I don't remember due to my limited memory 8-) + + +LICENSE +------- + Copyright 2004-2014 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl-2.0.txt + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 + #include + #include + #include + #include + #include "winerr.h" + + #define close closesocket + #define sleep Sleep + #define usleep sleep + #define ftruncate chsize + #define in_addr_t u32 + #define ONESEC 1000 + #define TEMPOZ1 + #define TEMPOZ2 GetTickCount() + #define APPDATA "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders" + #define PATH_SLASH '\\' +#else + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #define ONESEC 1 + #define stristr strcasestr + #define stricmp strcasecmp + #define strnicmp strncasecmp + #define TEMPOZ1 ftime(&timex) + #define TEMPOZ2 ((timex.time * 1000) + timex.millitm) + #define PATH_SLASH '/' +#endif + +typedef int8_t i8; +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; + + + +#define VER "0.8.11a" +static const u8 MS[] = "master.gamespy.com"; +static const u8 MSGAMENAME[] = "gamespy2\\gamever\\20603020"; +static const u8 MSGAMEKEY[] = "d4kZca"; +static const u8 MSGAMENAMEX[] = "gslive"; +static const u8 MSGAMEKEYX[] = "Xn221z"; +#define MSXPORT 28910 +#define MSPORT 28900 +#define HBPORT 27900 +#define GSWPORT 28903 +#define BUFFSZ 8192 +#define QUERYSZ BUFFSZ +#define SECURESZ 66 +#define VALIDATESZ ((SECURESZ / 6) * 8) +#define GSHB1 "\\heartbeat\\%hu" \ + "\\gamename\\%s" +#define GSHB2a "\\validate\\%s" \ + "\\final\\" +#define GSHB2b "\\gamename\\%s" \ + "\\final\\" +#define PCK "\\gamename\\%s" \ + "\\enctype\\%d" \ + "\\validate\\%s" \ + "\\final\\" \ + "\\list\\cmp" \ + "\\gamename\\%s" \ + "%s%s" +#define HOST "motd.gamespy.com" +#define ALUIGIHOST "aluigi.org" +#define GETSZ 2048 +#define GSUSERAGENT "GameSpyHTTP/1.0" +#define TIMEOUT 3 +#define HBMINUTES 300 // 300 seconds = 5 minutes +#define FILEOUT "gslist-out.gsl" +#define GSLISTCFG "gslist.cfg" +#define GSLISTTMP "gslist.tmp" +#define FULLCFG "full.cfg" +#define KNOWNCFG "knownsvc.cfg" +#define GSHKEYSCFG "gshkeys.txt" +#define DETECTCFG "detection.cfg" +#define OLDGSINFO "\\status\\" +#define ENCTYPEX_QUERY_GSLIST "\\hostname\\mapname\\gametype\\gamemode\\numplayers\\maxplayers\\gamever\\password\\sv_punkbuster\\pb\\punkbuster\\hostport\\ranked\\dedicated\\timelimit\\fraglimit\\gamevariant" +#define MAXNAMESREQ 60 +#define GSLISTSZ 80 +#define FULLSZ 2048 +#define DETECTSZ 300 +#define KNOWNSZ 64 +#define CFNAMEOFF 0 +#define CNAMEOFF 54 +#define CKEYOFF 73 +#define CFNAMELEN ((CNAMEOFF - CFNAMEOFF) - 1) +#define CNAMELEN ((CKEYOFF - CNAMEOFF) - 1) +#define CFNAMEEND (CFNAMEOFF + CFNAMELEN) +#define CNAMEEND (CNAMEOFF + CNAMELEN) +#define GSTITLE "\n" \ + "Gslist "VER"\n" \ + "by Luigi Auriemma\n" \ + "e-mail: aluigi@autistici.org\n" \ + "web: aluigi.org\n" \ + "\n" +#define GSLISTVER "GSLISTVER: " +#define GSLISTHOME "http://aluigi.org/papers.htm#gslist" +#define GSMAXPATH 1024 //256, not too big (for the stack) or too small + +#define DOITLATER_LIST 1 +#define DOITLATER_UPD 2 +#define DOITLATER_CFG 3 +#define DOITLATER_CFG0 4 +#define DOITLATER_REBCFG 5 + +#define GSW_SORT_PING 0 +#define GSW_SORT_NAME 1 +#define GSW_SORT_MAP 2 +#define GSW_SORT_TYPE 3 +#define GSW_SORT_PLAYER 4 +#define GSW_SORT_VER 5 +#define GSW_SORT_MOD 6 +#define GSW_SORT_DED 7 +#define GSW_SORT_PWD 8 +#define GSW_SORT_PB 9 +#define GSW_SORT_RANK 10 +#define GSW_SORT_MODE 11 +#define GSW_SORT_COUNTRY 12 + +#define ARGHELP !argv[i] || !strcmp(argv[i], "?") || !stricmp(argv[i], "help") +#define ARGHELP1 !argv[i] || !argv[i + 1] || !strcmp(argv[i + 1], "?") || !stricmp(argv[i + 1], "help") +#define CHECKARG if(!argv[i]) { \ + if (!quiet) fprintf(stderr, "\n" \ + "Error: you missed to pass the parameter to the option\n" \ + "\n"); \ + exit(1); \ + } +#define set_custom_multi_query(X) \ + if((argv[i][0] >= '0') && (argv[i][0] <= '9')) { \ + X = atoi(argv[i]); \ + } else { \ + X = -2; /* for differing it than megaquery */ \ + multi_query_custom_binary = argv[i]; \ + multi_query_custom_binary_size = cstring(multi_query_custom_binary, multi_query_custom_binary, -1, NULL); \ + if (!quiet) fprintf(stderr, "- hex dump of the packet to sent:\n\n"); \ + show_dump(multi_query_custom_binary, multi_query_custom_binary_size, stderr); \ + if (!quiet) fprintf(stderr, "\n"); \ + } + +#ifdef WIN32 + #define quick_thread(NAME, ARG) DWORD WINAPI NAME(ARG) + #define thread_id HANDLE +#else + #define quick_thread(NAME, ARG) void *NAME(ARG) + #define thread_id pthread_t +#endif + +thread_id quick_threadx(void *func, void *data) { + thread_id tid; +#ifdef WIN32 + DWORD tmp; + + tid = CreateThread(NULL, 0, func, data, 0, &tmp); + if(!tid) return(0); +#else + if(pthread_create(&tid, NULL, func, data)) return(0); +#endif + return(tid); +} + +void quick_threadz(thread_id tid) { +#ifdef WIN32 + DWORD ret; + + for(;;) { + GetExitCodeThread(tid, &ret); + if(!ret) break; + sleep(100); + } +#else + pthread_join(tid, NULL); +#endif +} + + + +void lame_room_visualization(u8 *buff); +void prepare_query(u8 *gamestr, int query, u8 *ip, u8 *port); +int gsfullup_aluigi(void); +u8 *gslist_input_list(u8 *fdlist, int *len, u8 *buff, int *dynsz); + + + +#pragma pack(1) // save tons of memory + +typedef struct { + in_addr_t ip; + u16 port; +} ipport_t; + +typedef struct { + in_addr_t ip; // 0.0.0.0 + u16 port; + u16 qport; + u8 *name; // string + u8 *map; // string + u8 *type; // string + u8 players; // 0-255 + u8 max; // 0-255 + u8 *ver; // string + u8 *mod; // string + u8 *mode; // string + u8 ded; // 0-255 + u8 pwd; // 0-255 + u8 pb; // 0-255 + u8 rank; // 0-255 + u8 *country; + u8 sort; // 0 = clear, 1 = reply, 2 = sorted + #define IPDATA_SORT_CLEAR 0 + #define IPDATA_SORT_REPLY 1 + #define IPDATA_SORT_SORTED 2 + u32 ping; +} ipdata_t; + +typedef struct { + u8 nt; // 0 or 1 + u8 chr; // the delimiter + u8 front; // where the data begins? + u8 rear; // where the data ends? + u16 maxping; + int sock; // scan only + int pos; // scan only + int (*func)(u8 *, int, void *gqd); // GSLIST_QUERY_T(*func); // scan only + u8 scantype; // scan only, 1 for gsweb and single queries + #define QUERY_SCANTYPE_SINGLE 0 + #define QUERY_SCANTYPE_GSWEB 1 + u8 *data; // scan only, if 0 + ipdata_t *ipdata; // scan only, if 1 + u8 type; // query number + struct sockaddr_in *peer; + ipport_t *ipport; // work-around for ping + int iplen; // work-around for ping + u32 *ping; + u8 *done; +} generic_query_data_t; + +typedef struct { + u8 *game; /* gamename */ + u8 *key; /* gamekey */ + u8 *full; /* full game description */ + u8 *path; /* executable path */ + u8 *filter; /* filter */ + u8 query; /* type of query */ +} gsw_data_t; + +typedef struct { + u8 *game; /* gamename */ + u8 *pass; + in_addr_t ip; + u16 port; +} gsw_fav_data_t; + +typedef struct { + u8 *full; + u8 *game; + u8 *exe; +} gsw_scan_data_t; + +struct { + u8 *game; + u8 *buff; + int len; +} gsw_refresh; + +#pragma pack() + + + +FILE *fdout = NULL; // for the -o option +in_addr_t msip = INADDR_NONE; +int megaquery = -1, + scandelay = 5, + no_reping = 0, + ignore_errors = 0, + sql = 0, + quiet = 0, + myenctype = -1, // 0, 1 and 2 or -1 for the X type + force_natneg = 0, + enctypex_type = 1, + multi_query_custom_binary_size = 0; +u16 msport = MSPORT; // do N OT touch +u8 *mshost = (u8 *)MS, // do N OT touch + *msgamename = (u8 *)MSGAMENAME, // do N OT touch + *msgamekey = (u8 *)MSGAMEKEY, // do N OT touch + *mymshost = NULL, + gslist_path[GSMAXPATH + 1] = "", + *sql_host = NULL, + *sql_database = NULL, + *sql_username = NULL, + *sql_password = NULL, + *sql_query = NULL, + *sql_queryb = NULL, + *sql_queryl = NULL, + *enctypex_query = "", + *multi_query_custom_binary = NULL; + + + +#include "gsnatneg.c" +//#include "gsmsalg.h" // the old magician +#include "enctypex_decoder.c" // the magic enctypeX +extern u8 *enctype1_decoder(u8 *, u8 *, int *); // for enctype1 decoding +extern u8 *enctype2_decoder(u8 *, u8 *, int *); // for enctype2 decoding + +#include "countries.h" // list of countries +#include "gsmyfunc.h" // mystrcpy, mywebcpy and so on +#include "gslegacy.h" // mostly gsweb related +#include "gsshow.h" // help, output, format and so on +#include "mydownlib.h" // real file downloading +#include "gshttp.h" // easy to use file downloading function +#include "gscfg.h" // manual configuration builder (used essentially for me) +#include "show_dump.h" // hex dump of data +#ifdef SQL + #include "gssql.h" // all the SQL part +#endif +#include "multi_query.h" // scanning and queries +#ifdef GSWEB + #include "gswskin.h" // internal index page + #include "gsweb.h" // web interface +#endif + + + +int main(int argc, char *argv[]) { + enctypex_data_t enctypex_data; + struct sockaddr_in peer; + ipport_t *ipport, + *ipbuffer; + u32 servers; + int sd, + len, + i, + execlen = 0, + hbmethod = 0, + dynsz, + itsok = 0, + iwannaloop = 0, + doitlater_type = 0, + mini_query_type = 0; + u16 heartbeat_port = 0; + u8 *buff = NULL, + *gamestr = NULL, + validate[VALIDATESZ + 1], + secure[SECURESZ + 1], + outtype = 0, + *tmpexec = NULL, + *execstring = NULL, + *execstring_ip = NULL, + *execstring_port = NULL, + *filter = "", + *execptr = NULL, + *doitlater_str = NULL, + *multigamename = NULL, + *multigamenamep = NULL, + *ipc = NULL, + *fname = NULL, + *fdlist = NULL, + *mini_query_host = NULL, + *mini_query_port = NULL, + enctypex_info[QUERYSZ]; + +#ifdef GSWEB + u16 gswport = 0; + u8 *gswip = NULL, + *gswopt = NULL, + *gswe = NULL, + *gswc = NULL; +#endif + +#ifdef WIN32 + WSADATA wsadata; + WSAStartup(MAKEWORD(1,0), &wsadata); +#endif + + setbuf(stdin, NULL); + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + fdout = stdout; + *gslist_path = 0; + +#ifdef WINTRAY +#else + if(argc < 2) { + show_help(); + exit(1); + } +#endif + + for(i = 1; i < argc; i++) { + if(stristr(argv[i], "--help")) { + show_help(); + return(0); + } + if(((argv[i][0] != '-') && (argv[i][0] != '/')) || (strlen(argv[i]) != 2)) { + fprintf(stderr, "\n" + "Error: recheck your options (%s is not valid)\n" + "\n", argv[i]); + exit(1); + } + + switch(argv[i][1]) { + case '-': + case '/': + case '?': + case 'h': { + show_help(); + return(0); + break; + } + case 'n': + case 'N': { + i++; + if(!argv[i]) { + fprintf(stderr, "\n" + "Error: you must select a gamename\n" + "Use -l for the full list or -s for searching a specific game\n" + "\n"); + exit(1); + } + gamestr = argv[i]; + break; + } + case 'l': { + doitlater_type = DOITLATER_LIST; + break; + } + case 's': { + i++; + if(!argv[i]) { + fprintf(stderr, "\n" + "Error: you must specify a text pattern to search in the game database\n" + "\n"); + exit(1); + } + doitlater_type = DOITLATER_LIST; + doitlater_str = argv[i]; + break; + } + case 'u': { + doitlater_type = DOITLATER_UPD; + break; + } + case 'U': { + doitlater_type = DOITLATER_REBCFG; + break; + } + case 'i': { + i++; + CHECKARG + mini_query_type = 0; + mini_query_host = argv[i]; + if(argv[i + 1] && (argv[i + 1][0] != '-')) mini_query_port = argv[++i]; + break; + } + case 'I': { + i++; + CHECKARG + mini_query_type = 8; + mini_query_host = argv[i]; + if(argv[i + 1] && (argv[i + 1][0] != '-')) mini_query_port = argv[++i]; + break; + } + case 'd': { + i++; + if(ARGHELP1) { + fprintf(stderr, "- list of supported game queries and their number:\n"); + i = 0; + while(switch_type_query(i, NULL, NULL, NULL, 2)) { + if(!(++i % 3)) fputc('\n', stderr); + } + fprintf(stderr, "\n or a C string containing the custom packet to send like: -d \"hel\\x6c\\x6f\"\n"); + return(0); + } + //mini_query_type = atoi(argv[i]); + set_custom_multi_query(mini_query_type) + mini_query_host = argv[++i]; + if(argv[i + 1] && (argv[i + 1][0] != '-')) mini_query_port = argv[++i]; + break; + } + case 'f': { + i++; + if(ARGHELP) { + show_filter_help(); + return(0); + } + filter = argv[i]; + break; + } + case 'r': { + i++; + CHECKARG + + execstring = argv[i]; + execlen = strlen(execstring) + 23; + + tmpexec = malloc(execlen); + if(!tmpexec) std_err(); + + execstring_ip = strstr(execstring, "#IP"); + execstring_port = strstr(execstring, "#PORT"); + if(execstring_ip) *execstring_ip = 0; + if(execstring_port) *execstring_port = 0; + + execlen = strlen(execstring); + memcpy(tmpexec, execstring, execlen); + break; + } + case 'o': { + i++; + if(ARGHELP) { + show_output_help(); + return(0); + } + outtype = atoi(argv[i]); + if(outtype > 6) outtype = 0; + if(!outtype) { + fdout = fopen(argv[i], "wb"); + if(!fdout) std_err(); + } + break; + } +#ifdef GSWEB + case 'w': { + i++; + if(!argv[i] || !argv[i + 1]) { + fprintf(stderr, "\n" + "Error: you need to specify the local IP and port to bind\n" + "\n"); + exit(1); + } + gswip = argv[i]; + gswport = atoi(argv[++i]); + break; + } + case 'W': { + i++; + if(!argv[i]) { + fprintf(stderr, "\n" + "Error: you need to specify the options for gsweb separated by comma:\n" + " refresh=0 disable the refresh buffer/button\n" + "\n"); + exit(1); + } + gswopt = argv[i]; + do { + gswe = strchr(gswopt, '='); + gswc = strchr(gswopt, ','); + if(gswc) *gswc = 0; + if(gswe) { + *gswe++ = 0; + } else { + gswe = "1"; + } + if(!stricmp(gswopt, "refresh")) gsw_allow_refresh = (atoi(gswe) ? 1 : 0); + if(!stricmp(gswopt, "admin")) gsw_admin_IP = gswe; + gswopt = gswc + 1; + } while(gswc); + break; + } +#endif + case 'q': { + quiet = 1; + break; + } + case 'x': { + i++; + if(!argv[i]) { + fprintf(stderr, "\n" + "Error: you must specify the master server and optionally its port\n" + "\n"); + exit(1); + } + msport = MSXPORT; + mshost = strchr(argv[i], ':'); + if(mshost) { + msport = atoi(mshost + 1); + *mshost = 0; + } + mshost = argv[i]; + mymshost = mshost; + break; + } + case 'b': { + i++; + CHECKARG + msport = HBPORT; + heartbeat_port = atoi(argv[i]); + hbmethod = 1; + break; + } + case 'B': { + i++; + CHECKARG + msport = HBPORT; + heartbeat_port = atoi(argv[i]); + hbmethod = 2; + break; + } + case 'L': { + i++; + if(!argv[i]) { + fprintf(stderr, "\n" + "Error: you must specify the amount of seconds for the loop\n" + "\n"); + exit(1); + } + iwannaloop = atoi(argv[i]); + break; + } + case 't': { + i++; + if(!argv[i]) { + fprintf(stderr, "\n" + "Error: you must select an enctype number\n" + "\n"); + exit(1); + } + if(tolower(argv[i][0]) == 'x') { + myenctype = -1; + } else { + myenctype = atoi(argv[i]); + } + break; + } + case 'c': { + show_countries(); + return(0); + break; + } + case 'y': { + i++; + if(!argv[i] || !argv[i + 1]) { + fprintf(stderr, "\n" + "Error: you must choose a gamename and a game key\n" + "\n"); + exit(1); + } + gamestr = argv[i]; + i++; + // retro-compatibility only + break; + } + case 'Y': { + i++; + if(!argv[i] || !argv[i + 1]) { + fprintf(stderr, "\n" + "Error: you must choose a gamename and a gamekey\n" + "\n"); + exit(1); + } + msgamename = argv[i]; + i++; + msgamekey = argv[i]; + if(strlen(msgamekey) > ((VALIDATESZ * 3) / 4)) { + fprintf(stderr, "\n" + "Error: the gamekey you have specified is too long\n" + "\n"); + exit(1); + } + break; + } + case 'p': { + i++; + CHECKARG + len = mystrcpy(gslist_path, argv[i], sizeof(gslist_path)); + if(gslist_path[len - 1] != PATH_SLASH) { + gslist_path[len] = PATH_SLASH; + gslist_path[len + 1] = 0; + } + break; + } + case 'm': { + doitlater_type = DOITLATER_CFG; + break; + } + case 'M': { + doitlater_type = DOITLATER_CFG0; + break; + } + case 'Q': { + i++; + if(ARGHELP) { + fprintf(stderr, "- list of supported game queries and their number:\n"); + i = 0; + while(switch_type_query(i, NULL, NULL, NULL, 2)) { + if(!(++i % 3)) fputc('\n', stderr); + } + fprintf(stderr, "\n or a C string containing the custom packet to send like: -Q \"hel\\x6c\\x6f\"\n"); + return(0); + } + //megaquery = atoi(argv[i]); + set_custom_multi_query(megaquery) + break; + } + case 'D': { + i++; + if(!argv[i]) { + fprintf(stderr, "\n" + "Error: you must choose the milliseconds to wait\n" + "\n"); + exit(1); + } + scandelay = atoi(argv[i]); + #ifdef WIN32 + #else + scandelay *= 1000; + #endif + break; + } + case 'X': { + i++; + if(!argv[i]) { + fprintf(stderr, "\n" + "Error: you must specify the informations you want to return from enctypex\n" + " for example: -t -1 -X \\hostname\\gamever\\gametype\\gamemode\\numplayers\n" + "\n"); + exit(1); + } + enctypex_query = argv[i]; + if(!enctypex_query[0] || !strcmp(enctypex_query, "\\") || !strcmp(enctypex_query, "\\\\")) { + enctypex_query = ENCTYPEX_QUERY_GSLIST; + } + break; + } +#ifdef SQL + case 'S': { + i++; + if(ARGHELP) { + show_sql(); + exit(1); + } + if(!argv[i] || !argv[i + 1] || !argv[i + 2] || !argv[i + 3] || !argv[i + 4] || !argv[i + 5] || !argv[i + 6]) { + fprintf(stderr, "\n" + "Error: some parameters are missed, use -S to see all the required arguments\n" + "\n"); + exit(1); + } + sql = 1; + #define AUTONULL(x) x = argv[i++]; \ + if(!x[0]) x = NULL; + AUTONULL(sql_host); + if(!sql_host) sql_host = "127.0.0.1"; + sql_database = argv[i++]; + AUTONULL(sql_username); + AUTONULL(sql_password); + sql_query = argv[i++]; + AUTONULL(sql_queryb); + AUTONULL(sql_queryl); + #undef AUTONULL + i--; // restore + break; + } +#endif + case 'G': { + force_natneg = 1; + break; + } + case 'E': { + ignore_errors = 1; + break; + } + case 'C': { + enctypex_data_cleaner_level = 0; + break; + } + case 'e': { + show_examples(); + return(0); + break; + } + case 'v': { + fdout = gslfopen(GSLISTCFG, "rb"); + if(fdout) fclose(fdout); + fprintf(stderr, + "Gslist home "GSLISTHOME"\n" + "Database folder %s\n" + "\n", gslist_path); + return(0); + break; + } + case 'z': { + i++; + if(!argv[i]) { + fprintf(stderr, "\n" + "Error: you must choose the input servers list\n" + "\n"); + exit(1); + } + fdlist = argv[i]; + gamestr = ""; + mshost = "127.0.0.1"; + mymshost = mshost; + break; + } + case 'R': { + enctypex_type = 0x20; + break; + } + case '0': { + no_reping = 1; + break; + } + default: { + fprintf(stderr, "\n" + "Error: wrong argument (%s)\n" + "\n", argv[i]); + exit(1); + break; + } + } + } + +if (!quiet) fprintf(stderr, GSTITLE); + +#ifndef WIN32 + scandelay *= 1000; +#endif + + if(mini_query_host) { + prepare_query(gamestr, mini_query_type, mini_query_host, mini_query_port); + return(0); + } + + dnsdb(NULL); // initialize the dns cache database + +#ifdef WINTRAY + quiet = 1; + gslist_hInstance = GetModuleHandle(NULL); + gswip = "127.0.0.1"; + if(!gswport) gswport = GSWPORT; + gsweb(resolv(gswip), gswport); + return(0); +#endif +#ifdef GSWEB + if(gswip) { + if(!gswport) gswport = GSWPORT; + gsweb(resolv(gswip), gswport); + return(0); + } +#endif + + if(doitlater_type) { + switch(doitlater_type) { + case DOITLATER_LIST: show_list(doitlater_str); break; + case DOITLATER_UPD: gsfullup_aluigi(); break; + case DOITLATER_CFG: make_gslistcfg(0); break; + case DOITLATER_CFG0: make_gslistcfg(1); break; + case DOITLATER_REBCFG: build_cfg(); break; + default: break; + } + return(0); + } + + if(!gamestr) { + if (!quiet) fprintf(stderr, "\n" + "Error: The game is not available or has not been specified.\n" + " Use -n to specify a gamename\n" + "\n"); + exit(1); + } + +#ifdef SQL + if(sql) { + if(megaquery < 0) { // multi_query_custom_binary not supported here + if (!quiet) fprintf(stderr, "\nError: you must specify both -Q and -S for using the SQL functions\n\n"); + exit(1); + } + gssql_init(); + } +#endif + + gslist_step_1(gamestr, filter); + + peer.sin_addr.s_addr = msip; + peer.sin_port = htons(msport); + peer.sin_family = AF_INET; + + buff = malloc(BUFFSZ + 1); + if(!buff) std_err(); + dynsz = BUFFSZ; + + if(hbmethod) { + gslist_heartbeat(buff, gamestr, validate, hbmethod, heartbeat_port, &peer); + return(0); + } + + multigamename = gamestr; + +get_list: + if(fdlist) { + buff = gslist_input_list(fdlist, &len, buff, &dynsz); + ipport = (ipport_t *)buff; + ipbuffer = ipport; + goto handle_servers; + } + + multigamenamep = strchr(gamestr, ','); + if(multigamenamep) *multigamenamep = 0; + if(!quiet) fprintf(stderr, "Gamename: %s\n", gamestr); + + sd = gslist_step_2(&peer, buff, secure, gamestr, validate, filter, &enctypex_data); + if(sd < 0) std_err(); + + switch(outtype) { + case 0: break; + case 1: + case 3: spr(&fname, "%s.gsl", gamestr); break; + case 2: + case 4: spr(&fname, "%s", FILEOUT); break; + case 5: + case 6: fdout = stdout; break; + default: break; + } + + if(fname) { + if(!quiet) fprintf(stderr, "- output file: %s\n", fname); + fdout = fopen(fname, "wb"); + if(!fdout) std_err(); + FREEX(fname); + } + + ipport = gslist_step_3(sd, validate, &enctypex_data, &len, &buff, &dynsz); + + if(outtype == 6) { + if (!quiet) fprintf(stderr, "\n\n"); + show_dump(buff, len, fdout); + fputc('\n', stderr); + //goto gslist_exit; + outtype = 0; + } + + itsok = gslist_step_4(secure, buff, &enctypex_data, &ipport, &len); + ipbuffer = ipport; + +handle_servers: + servers = 0; + while(len >= 6) { + ipc = myinetntoa(ipport->ip); + + if(!enctypex_query[0]) { // avoids double displaying when -X is in use + if (!quiet) switch(outtype) { + case 0: fprintf(fdout, "%15s %hu\n", ipc, ntohs(ipport->port)); break; + case 5: + case 1: + case 2: fprintf(fdout, "%s:%hu\n", ipc, ntohs(ipport->port)); break; + case 3: + case 4: fwrite((u8 *)ipport, 6, 1, fdout); break; + default: break; + } + } + + if(execstring) { + execptr = tmpexec + execlen; + if(execstring_ip && !execstring_port) { + execptr += sprintf(execptr, "%s", ipc); + strcpy(execptr, execstring_ip + 3); + + } else if(execstring_port && !execstring_ip) { + execptr += sprintf(execptr, "%hu", ntohs(ipport->port)); + strcpy(execptr, execstring_port + 5); + + } else if(execstring_ip < execstring_port) { + execptr += sprintf(execptr, "%s", ipc); + execptr += sprintf(execptr, "%s", execstring_ip + 3); + execptr += sprintf(execptr, "%hu", ntohs(ipport->port)); + strcpy(execptr, execstring_port + 5); + + } else if(execstring_port < execstring_ip) { + execptr += sprintf(execptr, "%hu", ntohs(ipport->port)); + execptr += sprintf(execptr, "%s", execstring_port + 5); + execptr += sprintf(execptr, "%s", ipc); + strcpy(execptr, execstring_ip + 3); + } + + if (!quiet) fprintf(stderr, " Execute: \"%s\"\n", tmpexec); + system(tmpexec); + } + + servers++; + ipport++; + len -= 6; + } + + if(!quiet && itsok) fprintf(stderr, "\n%u servers found\n\n", servers); + + if((myenctype < 0) && ipport && enctypex_query[0]) { + for(len = 0;;) { + len = enctypex_decoder_convert_to_ipport(buff + enctypex_data.start, enctypex_data.offset - enctypex_data.start, NULL, enctypex_info, sizeof(enctypex_info), len); + if(len <= 0) break; + if(enctypex_type == 0x20) { + lame_room_visualization(enctypex_info); + continue; + } + if(sql) { +#ifdef SQL + gssql(enctypex_info); +#endif + } else { + if (!quiet) fprintf(fdout, "%s\n", enctypex_info); + } + } + } + if((megaquery >= 0) || ((megaquery == -2) && multi_query_custom_binary)) { + if (!quiet) fprintf(stderr, "- querying servers:\n"); + mega_query_scan(gamestr, megaquery, ipbuffer, servers, 2); // 3 is the default timeout +#ifdef SQL + if(sql) gssql_later(); +#endif + } + + fflush(fdout); + if(outtype) fclose(fdout); + // -o filename will be closed when the program is terminated + + if(multigamenamep) { + *multigamenamep = ','; + gamestr = multigamenamep + 1; + if(myenctype < 0) { // needed here because each game uses a different hostname (from gslist_step_1) + mshost = enctypex_msname(gamestr, NULL); + msport = MSXPORT; + if(!quiet) fprintf(stderr, "- resolve additional master server %s\n", mshost); + msip = dnsdb(mshost); + peer.sin_addr.s_addr = msip; + peer.sin_port = htons(msport); + } + goto get_list; + } else { + gamestr = multigamename; + } + + if(iwannaloop) { + for(i = 0; i < iwannaloop; i++) { + sleep(ONESEC); + } + goto get_list; + } + +//gslist_exit: + FREEX(tmpexec); + FREEX(buff); + return(0); +} + + + +void lame_room_visualization(u8 *buff) { + int n1, + n2, + n3, + n4; + u8 *p, + *r; + + p = strchr(buff, ' '); + if(!p) return; + sscanf(buff, "%d.%d.%d.%d", &n1, &n2, &n3, &n4); + r = strstr(p, "\\hostname\\"); + if(r) { + r += 10; + } else { + r = p; + } + if (!quiet) fprintf(fdout, "#GPG!%-5d %s\n", (n1 << 24) | (n2 << 16) | (n3 << 8) | n4, r); +} + + + +void prepare_query(u8 *gamestr, int query, u8 *ip, u8 *port) { + u8 *p = NULL; + + if(!ip || (!(p = strchr(ip, ':')) && !port)) { + if (!quiet) fprintf(stderr, "\n" + "Error: you must insert an host and a query port\n" + "\n"); + exit(1); + } + if(p) { + *p++ = 0; + } else { + p = port; + } + multi_query(gamestr, query, resolv(ip), atoi(p)); + fputc('\n', stderr); +} + + + +int gsfullup_aluigi(void) { + FILE *fd; + u8 buff[GSLISTSZ + 1], + *p; + + cool_download(GSLISTCFG, ALUIGIHOST, 80, "papers/" GSLISTCFG); + + fd = gslfopen(GSLISTCFG, "rb"); + if(!fd) std_err(); + while(fgets(buff, sizeof(buff), fd)) { + if(strncmp(buff, GSLISTVER, sizeof(GSLISTVER) - 1)) continue; + p = buff + sizeof(GSLISTVER) - 1; + delimit(p); + if(strcmp(p, VER)) { + if (!quiet) fprintf(stderr, "\n" + "- a new version of Gslist (%s) has been released:\n" + "\n" + " "GSLISTHOME"\n" + "\n", p); + } + break; + } + fclose(fd); + + return(0); +} + + + +u8 *gslist_input_list(u8 *fdlist, int *len, u8 *buff, int *dynsz) { + ipport_t *ipport; + FILE *fd; + int i; + u8 tmp[QUERYSZ], + *ip, + *p; + + if (!quiet) fprintf(stderr, "- open file %s\n", fdlist); + fd = fopen(fdlist, "rb"); + if(!fd) std_err(); + + for(i = 0; fgets(tmp, sizeof(tmp), fd); i++); // pre-alloc + *len = i * 6; + if(*len > *dynsz) { + *dynsz = *len; + buff = realloc(buff, *dynsz); + if(!buff) std_err(); + } + ipport = (ipport_t *)buff; + + rewind(fd); + for(i = 0; fgets(tmp, sizeof(tmp), fd);) { + for(p = tmp; *p && ((*p == ' ') || (*p == '\t') || (*p == ':')); p++); + ip = p; + for(; *p && ((*p != ' ') && (*p != '\t') && (*p != ':')); p++); + *p++ = 0; + if(ip[0] <= ' ') continue; // includes also !ip[0] + ipport[i].ip = resolv(ip); + for(; *p && ((*p == ' ') || (*p == '\t') || (*p == ':')); p++); + ipport[i].port = htons(atoi(p)); + i++; + } + *len = i * 6; // no need realloc since it's smaller than dynsz + + fclose(fd); + return(buff); +} + + diff --git a/gslist/src/gslist.ico b/gslist/src/gslist.ico new file mode 100644 index 0000000000000000000000000000000000000000..0715327a3ad05f7d3446fdbaaec15564d9143e08 GIT binary patch literal 3262 zcmdUuu};G<5Qbe9i3OAmv9%jK1!dzwcm!6YAa&p|y7Cx}Kpp!E-RMSwR4EjynsYC? z{`J{WBGdsliT?b)`|j+x#`Nen92$CFnwK+UE{rkPbdzOn>306V7|DsD>v-cxe8%eM z-xnfGMq&TnRM%RFlHB&Dqy&XPOvm-a9+#&0fB@$0r3f>_ZW9}hrm3FONZ zrVA4V1~?4|3^|x0a5S4WekP0zqJm~csM|YeTg+NW2@gfDcu5!u0XltnsAvi!StCcP z1WldW#%VxFIpXM6Wg&Y(%c(D~G@@imn|svZi4FR~A)!%hYF|1G2wimdp$VcnTs`Is z9Ghfatp$!y48lv$1ag8K5-ZxkkCh1Xd4HhuSUUDh)pY|=zJlvqgtOKq+*atXdHDa* zS(+@47tC8Nvm{(OXx@v*$e0eMv^Qf|&u1V#c_81L@)A3}3d$3mAZ>!N|2FYmV~)-5 z+7HEBH@0-Z-CZQs&hD+fd9>TRdAfOi9j%jW@||RhRg&ea!8Cs#OqaPW%6xWSj^C1O uY;E>odS(7(%H`Btm7iaES@iRtqQ6)dr;|;QjJ6N+)G>E#SEH^+rGEi%gyLKP literal 0 HcmV?d00001 diff --git a/gslist/src/gslist_icon.rc b/gslist/src/gslist_icon.rc new file mode 100644 index 0000000..2f288bd --- /dev/null +++ b/gslist/src/gslist_icon.rc @@ -0,0 +1,22 @@ +#include + +#define ID_MYICON WM_USER + 1 +#define WM_TRAY_NOTIFY WM_USER + 2 +#define IDR_POPUP_MENU WM_USER + 3 +#define ID_APP_RELOAD WM_USER + 4 +#define ID_APP_ABOUT WM_USER + 5 +#define ID_APP_EXIT WM_USER + 6 + +ID_MYICON ICON gslist.ico + +IDR_POPUP_MENU MENU PRELOAD DISCARDABLE +BEGIN + POPUP "POPUP_MENU" + BEGIN + MENUITEM "&Reload the browser", ID_APP_RELOAD + MENUITEM SEPARATOR + MENUITEM "&About", ID_APP_ABOUT + MENUITEM SEPARATOR + MENUITEM "E&xit", ID_APP_EXIT + END +END diff --git a/gslist/src/gsmsalg.h b/gslist/src/gsmsalg.h new file mode 100644 index 0000000..b26ba82 --- /dev/null +++ b/gslist/src/gsmsalg.h @@ -0,0 +1,190 @@ +/* +GSMSALG 0.3.3 +by Luigi Auriemma +e-mail: aluigi@autistici.org +web: aluigi.org + + +INTRODUCTION +============ +With the name Gsmsalg I define the challenge-response algorithm needed +to query the master servers that use the Gamespy "secure" protocol (like +master.gamespy.com for example). +This algorithm is not only used for this type of query but also in other +situations like the so called "Gamespy Firewall Probe Packet" and the +master server hearbeat that is the challenge string sent by the master +servers of the games that use the Gamespy SDK when game servers want to +be included in the online servers list (UDP port 27900). + + +HOW TO USE +========== +The function needs 4 parameters: +- dst: the destination buffer that will contain the calculated + response. Its length is 4/3 of the challenge size so if the + challenge is 6 bytes long, the response will be 8 bytes long + plus the final NULL byte which is required (to be sure of the + allocated space use 89 bytes or "((len * 4) / 3) + 3") + if this parameter is NULL the function will allocate the + memory for a new one automatically +- src: the source buffer containing the challenge string received + from the server. +- key: the gamekey or any other text string used as algorithm's + key, usually it is the gamekey but "might" be another thing + in some cases. Each game has its unique Gamespy gamekey which + are available here: + http://aluigi.org/papers/gslist.cfg +- enctype: are supported 0 (plain-text used in old games, heartbeat + challenge respond, enctypeX and more), 1 (Gamespy3D) and 2 + (old Gamespy Arcade or something else). + +The return value is a pointer to the destination buffer. + + +EXAMPLE +======= + #include "gsmsalg.h" + + char *dest; + dest = gsseckey( + NULL, // dest buffer, NULL for auto allocation + "ABCDEF", // the challenge received from the server + "kbeafe", // kbeafe of Doom 3 and enctype set to 0 + 0); // enctype 0 + + +LICENSE +======= + Copyright 2004,2005,2006,2007,2008 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl.txt +*/ + +#include +#include + + + +unsigned char gsvalfunc(int reg) { + if(reg < 26) return(reg + 'A'); + if(reg < 52) return(reg + 'G'); + if(reg < 62) return(reg - 4); + if(reg == 62) return('+'); + if(reg == 63) return('/'); + return(0); +} + + + +unsigned char *gsseckey( + unsigned char *dst, + unsigned char *src, + unsigned char *key, + int enctype) { + + int i, + size, + keysz; + unsigned char enctmp[256], + tmp[66], + x, + y, + z, + a, + b, + *p; + static const unsigned char enctype1_data[256] = /* pre-built */ + "\x01\xba\xfa\xb2\x51\x00\x54\x80\x75\x16\x8e\x8e\x02\x08\x36\xa5" + "\x2d\x05\x0d\x16\x52\x07\xb4\x22\x8c\xe9\x09\xd6\xb9\x26\x00\x04" + "\x06\x05\x00\x13\x18\xc4\x1e\x5b\x1d\x76\x74\xfc\x50\x51\x06\x16" + "\x00\x51\x28\x00\x04\x0a\x29\x78\x51\x00\x01\x11\x52\x16\x06\x4a" + "\x20\x84\x01\xa2\x1e\x16\x47\x16\x32\x51\x9a\xc4\x03\x2a\x73\xe1" + "\x2d\x4f\x18\x4b\x93\x4c\x0f\x39\x0a\x00\x04\xc0\x12\x0c\x9a\x5e" + "\x02\xb3\x18\xb8\x07\x0c\xcd\x21\x05\xc0\xa9\x41\x43\x04\x3c\x52" + "\x75\xec\x98\x80\x1d\x08\x02\x1d\x58\x84\x01\x4e\x3b\x6a\x53\x7a" + "\x55\x56\x57\x1e\x7f\xec\xb8\xad\x00\x70\x1f\x82\xd8\xfc\x97\x8b" + "\xf0\x83\xfe\x0e\x76\x03\xbe\x39\x29\x77\x30\xe0\x2b\xff\xb7\x9e" + "\x01\x04\xf8\x01\x0e\xe8\x53\xff\x94\x0c\xb2\x45\x9e\x0a\xc7\x06" + "\x18\x01\x64\xb0\x03\x98\x01\xeb\x02\xb0\x01\xb4\x12\x49\x07\x1f" + "\x5f\x5e\x5d\xa0\x4f\x5b\xa0\x5a\x59\x58\xcf\x52\x54\xd0\xb8\x34" + "\x02\xfc\x0e\x42\x29\xb8\xda\x00\xba\xb1\xf0\x12\xfd\x23\xae\xb6" + "\x45\xa9\xbb\x06\xb8\x88\x14\x24\xa9\x00\x14\xcb\x24\x12\xae\xcc" + "\x57\x56\xee\xfd\x08\x30\xd9\xfd\x8b\x3e\x0a\x84\x46\xfa\x77\xb8"; + + if(!dst) { + dst = malloc(89); + if(!dst) return(NULL); + } + size = strlen(src); + if((size < 1) || (size > 65)) { + dst[0] = 0; + return(dst); + } + keysz = strlen(key); + + for(i = 0; i < 256; i++) { + enctmp[i] = i; + } + + a = 0; + for(i = 0; i < 256; i++) { + a += enctmp[i] + key[i % keysz]; + x = enctmp[a]; + enctmp[a] = enctmp[i]; + enctmp[i] = x; + } + + a = 0; + b = 0; + for(i = 0; src[i]; i++) { + a += src[i] + 1; + x = enctmp[a]; + b += x; + y = enctmp[b]; + enctmp[b] = x; + enctmp[a] = y; + tmp[i] = src[i] ^ enctmp[(x + y) & 0xff]; + } + for(size = i; size % 3; size++) { + tmp[size] = 0; + } + + if(enctype == 1) { + for(i = 0; i < size; i++) { + tmp[i] = enctype1_data[tmp[i]]; + } + } else if(enctype == 2) { + for(i = 0; i < size; i++) { + tmp[i] ^= key[i % keysz]; + } + } + + p = dst; + for(i = 0; i < size; i += 3) { + x = tmp[i]; + y = tmp[i + 1]; + z = tmp[i + 2]; + *p++ = gsvalfunc(x >> 2); + *p++ = gsvalfunc(((x & 3) << 4) | (y >> 4)); + *p++ = gsvalfunc(((y & 15) << 2) | (z >> 6)); + *p++ = gsvalfunc(z & 63); + } + *p = 0; + + return(dst); +} + diff --git a/gslist/src/gsmyfunc.h b/gslist/src/gsmyfunc.h new file mode 100644 index 0000000..3963438 --- /dev/null +++ b/gslist/src/gsmyfunc.h @@ -0,0 +1,933 @@ +/* + Copyright 2005-2011 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl-2.0.txt +*/ + +#ifndef WIN32 + void std_err(void) { + perror("\nError"); + exit(1); + } +#endif + + + +#define FREEX(X) freex((void *)&X) +void freex(void **buff) { + if(!buff || !*buff) return; + free(*buff); + *buff = NULL; +} + + + +u8 *myinetntoa(in_addr_t ip) { // avoids warnings and blablabla + struct in_addr ipaddr; + ipaddr.s_addr = ip; + return(inet_ntoa(ipaddr)); +} + + + +int udpsocket(void) { + static const struct linger ling = {1,1}; + int sd; + + sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if(sd < 0) std_err(); + setsockopt(sd, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling)); + return(sd); +} + + + +int tcpsocket(void) { + static const struct linger ling = {1,1}; + int sd; + + sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(sd < 0) std_err(); + setsockopt(sd, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling)); + return(sd); +} + + + +/* +finds the value of key in the data buffer and return a new +string containing the value or NULL if nothing has been found +no modifications are made on the input data +*/ +u8 *keyval(u8 *data, u8 *key) { + int len, + nt = 0, + skip = 1; + u8 *p, + *val; + + for(;;) { + p = strchr(data, '\\'); + + if(nt & 1) { + // key + if(p && !strnicmp(data, key, p - data)) { + skip = 0; + } + } else { + // value + if(!skip) { + if(!p) p = data + strlen(data); + len = p - data; + val = malloc(len + 1); + if(!val) std_err(); + memcpy(val, data, len); + val[len] = 0; + return(val); + } + } + + if(!p) break; + nt++; + data = p + 1; + } + return(NULL); +} + + + +u8 *recv_basic_secure(int sd, u8 *data, int max) { + int t, + len = 0; + u8 *sec = NULL; + + while(len < max) { + t = recv(sd, data + len, max - len, 0); + if(t < 0) std_err(); + if(!t) break; + len += t; + data[len] = 0; + sec = keyval(data, "secure"); + if(sec) break; + if(!data[len - 1]) break; + } + + return(sec); +} + + + +int timeout(int sock, int sec) { + struct timeval tout; + fd_set fd_read; + int err; + + tout.tv_sec = sec; + tout.tv_usec = 0; + FD_ZERO(&fd_read); + FD_SET(sock, &fd_read); + err = select(sock + 1, &fd_read, NULL, NULL, &tout); + if(err < 0) std_err(); + if(!err) return(-1); + return(0); +} + + + +in_addr_t resolv(char *host) { + struct hostent *hp; + in_addr_t host_ip; + + host_ip = inet_addr(host); + if(host_ip == htonl(INADDR_NONE)) { + hp = gethostbyname(host); + if(!hp) { // I prefer to exit instead of returning INADDR_NONE + fprintf(stderr, "\nError: Unable to resolv hostname (%s)\n\n", host); + exit(1); + } else host_ip = *(in_addr_t *)(hp->h_addr); + } + return(host_ip); +} + + + +u8 *delimit(u8 *data) { + while(*data && (*data != '\n') && (*data != '\r')) data++; + *data = 0; + return(data); +} + + + +int mystrcpy(u8 *dst, u8 *src, int max) { + u8 *p; + + for(p = dst; *src && --max; p++, src++) *p = *src; + *p = 0; + return(p - dst); +} + + + + /* returns a new clean string */ +u8 *mychrdup(u8 *src) { + int len, + max; + u8 *dst; + + len = max = strlen(src); + dst = malloc(max + 1); + if(!dst) std_err(); + + max = enctypex_data_cleaner(dst, src, max); + + // probably useless free memory + if(max < len) { + dst = realloc(dst, max + 1); + if(!dst) std_err(); + } + + return(dst); +} + + + +int mysnprintf(u8 *buff, int len, u8 *fmt, ...) { + va_list ap; + int ret; + + va_start(ap, fmt); + ret = vsnprintf(buff, len, fmt, ap); + va_end(ap); + + if((ret < 0) || (ret >= len)) { + ret = len; + buff[len] = 0; + } + return(ret); +} + + + + /* + the core of the local config files + it works just like the normal fopen() + if you use a NULL mode will be returned the (allocated) + full path of the file + */ +void *gslfopen(const char *path, const char *mode) { +#ifdef WIN32 + HKEY key; + char home[GSMAXPATH + 1]; + DWORD len; +#else + const char *home; +#endif + char retpath[GSMAXPATH + 64 + 1], + *cmd; + + if(!*gslist_path) { +#ifdef WIN32 + len = GSMAXPATH; + if(!RegOpenKeyEx(HKEY_CURRENT_USER, APPDATA, 0, KEY_READ, &key)) { + if(!RegQueryValueEx(key, "AppData", NULL, NULL, home, &len)) { + mysnprintf(gslist_path, sizeof(gslist_path), "%s\\gslist\\", home); + mkdir(gslist_path); + } + RegCloseKey(key); + } + if(!*gslist_path) strcpy(gslist_path, ".\\"); +#else + home = getenv("HOME"); + if(!home) { + fputs("\n" + "Error: impossible to know your $HOME or Application Data directory where\n" + " reading/writing the Gslist configuration files.\n" + " Modify the source code of the program or report the problem to me.\n" + "\n", stderr); + exit(1); + } + mysnprintf(gslist_path, sizeof(gslist_path), "%s/.gslist/", home); + mkdir(gslist_path, 0755); +#endif + } + +// mysnprintf(retpath, sizeof(retpath), "%s%s", gslist_path, path); + sprintf(retpath, "%s%s", gslist_path, path); + + if(!mode) return(strdup(retpath)); + + if(!quiet) { + if(stristr(mode, "r+")) cmd = "update"; + else if(stristr(mode, "a")) cmd = "append"; + else if(stristr(mode, "w")) cmd = "create"; + else cmd = "open"; + fprintf(stderr, "- %s file %s\n", cmd, retpath); + } + + return(fopen(retpath, mode)); +} + + + +u8 *replace(u8 *buff, u8 *from, u8 *to) { + int fromlen, + tolen; + u8 *p; + + fromlen = strlen(from); + tolen = strlen(to); + + while((p = (u8 *)stristr(buff, from))) { + memmove(p + tolen, p + fromlen, strlen(p) + 1); + memcpy(p, to, tolen); + } + return(buff); +} + + +int vspr(u8 **buff, u8 *fmt, va_list ap) { + int len, + mlen; + u8 *ret; + + mlen = strlen(fmt) + 128; + + for(;;) { + ret = malloc(mlen); + if(!ret) return(0); // return(-1); + len = vsnprintf(ret, mlen, fmt, ap); + if((len >= 0) && (len < mlen)) break; + if(len < 0) { // Windows style + mlen += 128; + } else { // POSIX style + mlen = len + 1; + } + FREEX(ret); + } + + *buff = ret; + return(len); +} + + + +int spr(u8 **buff, u8 *fmt, ...) { + va_list ap; + int len; + + va_start(ap, fmt); + len = vspr(buff, fmt, ap); + va_end(ap); + + return(len); +} + + + +int udpspr(int sd, struct sockaddr_in *peer, u8 *fmt, ...) { + va_list ap; + int len, + new = 0; + u8 *buff; + + va_start(ap, fmt); + len = vspr(&buff, fmt, ap); + va_end(ap); + + if(!sd) { + new = 1; + sd = udpsocket(); + } + + len = sendto(sd, buff, len, 0, (struct sockaddr *)peer, sizeof(struct sockaddr_in)); + if(new) close(sd); + FREEX(buff); + return(len); +} + + + +int tcpspr(int sd, u8 *fmt, ...) { + va_list ap; + int len; + u8 *buff; + + va_start(ap, fmt); + len = vspr(&buff, fmt, ap); + va_end(ap); + + len = send(sd, buff, len, 0); + FREEX(buff); + return(len); +} + + + +int tcpxspr(int sd, u8 *gamestr, u8 *msgamestr, u8 *validate, u8 *filter, u8 *info, int type) { // enctypex + int len; + u8 *buff, + *p; + + len = 2 + 7 + strlen(gamestr) + 1 + strlen(msgamestr) + 1 + strlen(validate) + strlen(filter) + 1 + strlen(info) + 1 + 4; + buff = malloc(len); + + p = buff; + p += 2; + *p++ = 0; + *p++ = 1; + *p++ = 3; + *p++ = 0; // 32 bit + *p++ = 0; + *p++ = 0; + *p++ = 0; + p += sprintf(p, "%s", gamestr) + 1; // the one you are requesting + p += sprintf(p, "%s", msgamestr) + 1; // used for the decryption algorithm + p += sprintf(p, "%s%s", validate, filter) + 1; + p += sprintf(p, "%s", info) + 1; + *p++ = 0; + *p++ = 0; + *p++ = 0; + *p++ = type; + // bits which compose the "type" byte: + // 00: plain server list, sometimes the master server returns also the informations if requested + // 01: requested informations of the server, like \hostname\numplayers and so on + // 02: nothing??? + // 04: available informations on the master server??? hostname, mapname, gametype, numplayers, maxplayers, country, gamemode, password, gamever + // 08: nothing??? + // 10: ??? + // 20: peerchat IRC rooms + // 40: ??? + // 80: nothing??? + + len = p - buff; + buff[0] = len >> 8; + buff[1] = len; + + len = send(sd, buff, len, 0); + FREEX(buff); + return(len); +} + + + + /* returns -1 if error, 1 if only par, 2 if par and val, 0 if [] */ +int myreadini(FILE *fd, u8 *line, int linesz, u8 **par, u8 **val) { + u8 *p, + *l; + + *par = NULL; + *val = NULL; + + if(!fgets(line, linesz, fd)) return(-1); + + delimit(line); + + while(*line && (*line <= ' ') && (*line != '=')) line++; // first spaces + *par = line; + + while(*line && (*line > ' ') && (*line != '=')) line++; // delimit par + *line++ = 0; + + while(*line && (*line <= ' ') && (*line != '=')) line++; // remove spaces after par + *val = line; + + for(p = line + strlen(line) - 1; p >= line; p--) { // remove last spaces + if(*p > ' ') { + *(p + 1) = 0; + break; + } + } + + p = *par; + if(*p == '[') { + p++; + l = strchr(p, ']'); + if(l) *l = 0; + if(strlen(p) > CNAMELEN) p[CNAMELEN] = 0; + *par = p; + return(0); + } + if(!(*val)[0]) return(1); + return(2); +} + + + +u8 *strduplow(u8 *str) { + u8 *p; + + str = strdup(str); + for(p = str; *p; p++) { + *p = tolower(*p); + } + return(str); +} + + + +#define MAXDNS 64 + + /* the following function caches the IP addresses */ + /* actually the instructions which replace the older */ + /* entries are not the best in the world... but work */ + +in_addr_t dnsdb(char *host) { + typedef struct { + in_addr_t ip; + u8 *host; + time_t time; + } db_t; + + static db_t *db; + static int older; + in_addr_t fastip; + int i; + + if(!host) { + db = malloc(sizeof(db_t) * MAXDNS); // allocate + if(!db) std_err(); + + for(i = 0; i < MAXDNS; i++) { + db[i].ip = INADDR_NONE; + db[i].host = NULL; + db[i].time = time(NULL); + } + + older = 0; + return(0); + } + + if(!host[0]) return(INADDR_NONE); + + fastip = inet_addr(host); + if(fastip != INADDR_NONE) return(fastip); + + for(i = 0; i < MAXDNS; i++) { + if(!db[i].host) break; // new host to add + + if(!strcmp(db[i].host, host)) { // host in cache + db[i].time = time(NULL); // update time + return(db[i].ip); + } + + if(db[i].time < db[older].time) { // what's the older entry? + older = i; + } + } + + if(i == MAXDNS) i = older; // take the older one + + if(db[i].host) free(db[i].host); + + db[i].ip = resolv(host); + if(db[i].ip == INADDR_NONE) { + db[i].host = NULL; + return(INADDR_NONE); + } + + db[i].host = strduplow(host); // low case! + + db[i].time = time(NULL); + + return(db[i].ip); +} + + + +void gslist_step_1(u8 *gamestr, u8 *filter) { + //if(enctypex_query[0] && (myenctype >= 0)) myenctype = -1; + if(myenctype < 0) { + if((u8 *)msgamename == (u8 *)MSGAMENAME) { // change if default + msgamename = (u8 *)MSGAMENAMEX; + msgamekey = (u8 *)MSGAMEKEYX; + } + if(!mymshost) { // lame work-around in case we use -x IP + mshost = enctypex_msname(gamestr, NULL); + msport = MSXPORT; + } + } + if((enctypex_type == 0x20) && !enctypex_query[0]) { + enctypex_query = "\\hostname"; + } + + if(!quiet) { + fprintf(stderr, + "Gamename: %s\n" + "Enctype: %d\n" + "Filter: %s\n" + "Resolving %s ... ", + gamestr, + myenctype, + filter, + mshost); + } + + msip = dnsdb(mshost); + + if(!quiet) fprintf(stderr, "%s:%hu\n", myinetntoa(msip), msport); +} + + + +int gslist_step_2(struct sockaddr_in *peer, u8 *buff, u8 *secure, u8 *gamestr, u8 *validate, u8 *filter, enctypex_data_t *enctypex_data) { + int sd; + u8 *sec = NULL; + + sd = tcpsocket(); + if(connect(sd, (struct sockaddr *)peer, sizeof(struct sockaddr_in)) + < 0) goto quit; + + if(myenctype < 0) { + memset(enctypex_data, 0, sizeof(enctypex_data_t)); // needed + enctypex_decoder_rand_validate(validate); + + if(!quiet) { + fprintf(stderr, + "MSgamename: %s\n" + "MSgamekey: %s\n" + "Random id: %s\n" + "Info query: %s\n", + msgamename, + msgamekey, + validate, + enctypex_query); + } + + if(tcpxspr(sd, + gamestr, + msgamename, + validate, + filter, + enctypex_query, // \\hostname\\gamever\\numplayers\\maxplayers\\mapname\\gametype + enctypex_type) < 0) goto quit; + } else { + sec = recv_basic_secure(sd, buff, BUFFSZ); + if(sec) { + mystrcpy(secure, sec, SECURESZ); + gsseckey(validate, sec, msgamekey, myenctype); + if(!quiet) fprintf(stderr, "Validate: %s -> %s\n", secure, validate); + FREEX(sec); + } else { + if(!quiet) { + fprintf(stderr, + "Alert: This master server doesn't seem to support the \"secure\" Gamespy protocol\n" + " This is the received reply: %s\n" + " I try to send the query with an empty \"validate\" field\n" + "\n", buff); + } + *validate = 0; + *secure = 0; + } + + if(tcpspr(sd, + PCK, + msgamename, + myenctype, + validate, + gamestr, + *filter ? "\\where\\" : "", + filter) < 0) goto quit; + } + return(sd); +quit: + close(sd); + return(-1); +} + + + +ipport_t *gslist_step_3(int sd, u8 *validate, enctypex_data_t *enctypex_data, int *ret_len, u8 **ret_buff, int *ret_dynsz) { + ipport_t *ipport; + int err, + len, + dynsz; + u8 *buff; + + buff = *ret_buff; + dynsz = *ret_dynsz; + + ipport = NULL; + len = 0; // *ret_len not needed + if(!quiet) fprintf(stderr, "Receiving: "); + + while(!timeout(sd, 10)) { + err = recv(sd, buff + len, dynsz - len, 0); + if(err <= 0) break; + if(!quiet) fputc('.', stderr); + len += err; + if(len >= dynsz) { + dynsz += BUFFSZ; + buff = realloc(buff, dynsz); + if(!buff) std_err(); + } + if(myenctype < 0) { // required because it's a streamed list and the socket is not closed by the server + ipport = (ipport_t *)enctypex_decoder(msgamekey, validate, buff, &len, enctypex_data); + if(ipport && enctypex_decoder_convert_to_ipport((u8 *)ipport, len - ((u8 *)ipport - buff), NULL, NULL, 0, 0)) break; // end of the stream + } + } + close(sd); + if(!quiet) fprintf(stderr, " %u bytes\n", len); + + *ret_len = len; + *ret_buff = buff; + *ret_dynsz = dynsz; + return(ipport); +} + + + +int gslist_step_4(u8 *secure, u8 *buff, enctypex_data_t *enctypex_data, ipport_t **ret_ipport, int *ret_len) { + static u8 *enctypextmp = NULL; + ipport_t *ipport; + int len, + itsok; + + ipport = *ret_ipport; + len = *ret_len; + itsok = 1; + if(!myenctype && !strncmp(buff + len - 7, "\\final\\", 7)) { + ipport = (ipport_t *)buff; + len -= 7; + } else if((myenctype == 1) && len) { + ipport = (ipport_t *)enctype1_decoder( + secure, + buff, + &len); + } else if((myenctype == 2) && len) { + ipport = (ipport_t *)enctype2_decoder( + msgamekey, + buff, + &len); + } else if((myenctype < 0) && len && ipport) { + // because for some games like AA the server sends 5 bytes for each IP + // so if I use the same untouched buffer as input and output I overwrite its fields + // the amount of allocated bytes wasted by this operation is enough small (for example 200k on 1megabyte of received data) + enctypextmp = realloc(enctypextmp, (len / 5) * 6); + if(!enctypextmp) std_err(); + len = enctypex_decoder_convert_to_ipport(buff + enctypex_data->start, len - enctypex_data->start, enctypextmp, NULL, 0, 0); + if(len < 0) { // yeah, the handling of the master server's error doesn't look so good although all perfect, but it's a rare event and I wanted to handle it + if(enctypex_data->offset > enctypex_data->start) { + len = enctypex_data->offset - enctypex_data->start; + memmove(buff, buff + enctypex_data->start + ((len > 6) ? 6 : 0), len - ((len > 6) ? 6 : 0)); + } else { + len = 0; + } + ipport = NULL; + itsok = 0; + } else { + ipport = (ipport_t *)enctypextmp; + } + } else { + ipport = NULL; + itsok = 0; + } + if(!ipport) itsok = 0; + if(len < 0) { + itsok = 0; + len = 0; + } + + if(!quiet) fprintf(stderr, "-----------------------\n"); + + if(!itsok && !quiet) { + if(!len) { + fprintf(stderr, "\n" + "Error: the gamename doesn't exist in the master server\n" + "\n"); + } else { + fprintf(stderr, "\n" + "Alert: the master server has not accepted your query for the following error:\n" + " %.*s\n" + "\n", len, buff); + } + len = 0; + } + + *ret_ipport = ipport; + *ret_len = len; + return(itsok); +} + + + +void get_key(u8 *gamestr, u8 *gamekey, u8 *gamefull); +void gslist_heartbeat(u8 *buff, u8 *gamestr, u8 *validate, int hbmethod, u16 heartbeat_port, struct sockaddr_in *peer) { + struct sockaddr_in peerl; + int sd, + i, + err, + psz, + on = 1; + u8 gamekey[32], + *sec = NULL; + + if(hbmethod == 2) { + fprintf(stderr, "- the heartbeat for port %hu will be sent each %d minutes\n", + HBMINUTES / 60, + heartbeat_port); + + for(;;) { + udpspr(0, peer, GSHB1, heartbeat_port, gamestr); + + for(i = HBMINUTES; i; i--) { + fprintf(stderr, " %3d\r", i); + sleep(ONESEC); + } + } + } else if(hbmethod == 1) { + fprintf(stderr, "- the game port %hu will be temporary bound each %d minutes, watch the details:\n", + heartbeat_port, + HBMINUTES / 60); + + peerl.sin_addr.s_addr = htonl(INADDR_ANY); + peerl.sin_port = htons(heartbeat_port); + peerl.sin_family = AF_INET; + + for(;;) { + sd = udpsocket(); + + fprintf(stderr, "- binding: "); + if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) + < 0) std_err(); + if(bind(sd, (struct sockaddr *)&peerl, sizeof(struct sockaddr_in)) + < 0) std_err(); + + udpspr(sd, peer, GSHB1, heartbeat_port, gamestr); + if(!quiet) fputc('.', stderr); + + for(;;) { + if(timeout(sd, TIMEOUT) < 0) { + get_key(gamestr, gamekey, NULL); + printf("\n- activate gsnatneg(%s, %s):\n", gamestr, gamekey); + gsnatneg_verbose = 1; + for(;;) { + if(gsnatneg(sd, gamestr, gamekey, NULL, INADDR_ANY, 0, &peerl, NULL) < 0) { + fprintf(stderr, "\n" + "Error: socket timeout, probably your firewall blocks the UDP packets to or from the master server\n" + "\n"); + exit(1); + } + } + } + + psz = sizeof(struct sockaddr_in); + err = recvfrom(sd, buff, BUFFSZ, 0, (struct sockaddr *)peer, &psz); + if(err <= 0) continue; + + // here we check if the source IP is the same or is in the same B + // subnet of the master server, this is needed + if((peer->sin_addr.s_addr & 0xffff) == (msip & 0xffff)) break; + if(!quiet) fputc('x', stderr); + } + if(!quiet) fputc('.', stderr); + buff[err] = 0; + + sec = keyval(buff, "secure"); + if(sec) { // old, no longer used by the master server + gsseckey(validate, sec, msgamekey, 0); + fprintf(stderr, "- Validate: %s -> %s\n", sec, validate); + FREEX(sec); + udpspr(sd, peer, GSHB2a, validate); + } else { + udpspr(sd, peer, GSHB2b, gamestr); + } + if(!quiet) fputc('.', stderr); + + close(sd); + fprintf(stderr, " free\n"); + + for(i = HBMINUTES; i; i--) { + fprintf(stderr, " %3d\r", i); + sleep(ONESEC); + } + + // needed to avoid that the sockaddr_in structure + // is overwritten with a different host and port + peer->sin_addr.s_addr = msip; + peer->sin_port = htons(msport); + peer->sin_family = AF_INET; + } + } +} + + + +int cstring(u8 *input, u8 *output, int maxchars, int *inlen) { + int n, + len; + u8 *p, + *o; + + if(!input || !output) { + if(inlen) *inlen = 0; + return(0); + } + + p = input; + o = output; + while(*p) { + if(maxchars >= 0) { + if((o - output) >= maxchars) break; + } + if(*p == '\\') { + p++; + switch(*p) { + case 0: return(-1); break; + //case '0': n = '\0'; break; + case 'a': n = '\a'; break; + case 'b': n = '\b'; break; + case 'e': n = '\e'; break; + case 'f': n = '\f'; break; + case 'n': n = '\n'; break; + case 'r': n = '\r'; break; + case 't': n = '\t'; break; + case 'v': n = '\v'; break; + case '\"': n = '\"'; break; + case '\'': n = '\''; break; + case '\\': n = '\\'; break; + case '?': n = '\?'; break; + case '.': n = '.'; break; + case ' ': n = ' '; break; + case 'x': { + //n = readbase(p + 1, 16, &len); + //if(len <= 0) return(-1); + if(sscanf(p + 1, "%02x%n", &n, &len) != 1) return(-1); + if(len > 2) len = 2; + p += len; + } break; + default: { + //n = readbase(p, 8, &len); + //if(len <= 0) return(-1); + if(sscanf(p, "%3o%n", &n, &len) != 1) return(-1); + if(len > 3) len = 3; + p += (len - 1); // work-around for the subsequent p++; + } break; + } + *o++ = n; + } else { + *o++ = *p; + } + p++; + } + *o = 0; + len = o - output; + if(inlen) *inlen = p - input; + return(len); +} + + diff --git a/gslist/src/gsnatneg.c b/gslist/src/gsnatneg.c new file mode 100644 index 0000000..97b82cf --- /dev/null +++ b/gslist/src/gsnatneg.c @@ -0,0 +1,962 @@ +/* +GS natneg client 0.2 +by Luigi Auriemma +e-mail: aluigi@autistici.org +web: aluigi.org + + +INTRODUCTION +============ +Gamespy natneg is a way used by Gamespy to allow the people behind router +and NAT (so those who can't receive direct connections from Internet) to +create public servers and at the same time allows clients to query and join +these servers. + +The function in this code is very easy to use and can be implemented in a +trasparent way in any program for adding the client-side support to the +Gamespy natneg. + +Note that the code looks very chaotic because I have thought to various +ways to implement it and so I have opted for a temporary solution that +can be improved in future if I need other features. + + +HOW TO USE +========== +Exists only one function to use and it's gsnatneg() which returns the +socket descriptor (> 0) in case of success or a negative value in case of +errors. +The negative number is the line of gsnatneg.c where happened the error in +negative notation, so -100 means the problem was located at line 100 of +this source code. + +The following are the arguments of the function: + sd the UDP socket you have created in your client, this + parameter is required since the master server will create + a direct UDP connection between the port used by that + socket and the server + if s <= 0 the function will create a new socket + gamename the Gamespy name of the game, it's needed since the + Gamespy master server needs to locate the IP of the + server you want to contact in its big database, the full + list of Gamespy gamenames is available here: + http://aluigi.org/papers/gslist.cfg + if it's NULL or has not string in it then the function + will ask to insert the gamename at runtime (in console) + update: you can use also some others gamenames other than + the correct one probably because most of them run on the + same master server, anyway it's better to use the correct + gamename + gamekey needed only in server mode, set it to NULL + host the hostname or the IP address in text format of the + server to contact (like "1.2.3.4" or "host.example.com") + if unused set it to NULL + ip as above but it's directly the IP in the inet_addr format. + if unused set it to INADDR_NONE for client or INADDR_ANY + for server + port the port of the server to contact + set to 0 if unused + *peer a sockaddr_in structure which will be filled with the + correct port of the server, because due to the NAT + negotiation the port with which will be made the + communication may differ than the one displayed in the + server list. if this parameter is NULL it will not be + used. + from version 0.1.3 you can pass the original peer + structure you use in your program avoiding to specify + host/ip and port separately + callback int myrecv(int sd, struct sockaddr_in *peer, uint8_t *buff, int len); + set it to NULL + +so for taking the server's IP the library will first check "host", then +"ip" and finally "peer" as last choice. + + +EXAMPLES +======== + int sd; + sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if(gsnatneg(sd, "halor", NULL, "1.2.3.4", INADDR_NONE, 2302, &peer, NULL) < 0) exit(1); + // or + if(gsnatneg(sd, "halor", NULL, NULL, inet_addr("1.2.3.4"), 2302, &peer, NULL) < 0) exit(1); + // or + if(gsnatneg(sd, "halor", NULL, NULL, INADDR_NONE, 0, &peer, NULL) < 0) exit(1); + + // if you want you can also terminate and free the gsnatneg resources using: + gsnatneg(-1, NULL, NULL, NULL 0, 0, NULL, NULL); + + +LICENSE +======= + Copyright 2008-2012 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl.txt +*/ + +#ifndef GSNATNEG_H +#define GSNATNEG_H + +#include +#include +#include +#include +#include +#include +#include "gsmsalg.h" + +#ifdef WIN32 + #include + + #ifndef close + #define close closesocket + #endif + #ifndef in_addr_t + #define in_addr_t uint32_t + #endif +#else + #include + #include + #include + #include + #include + #include + + #ifndef stricmp + #define stricmp strcasecmp + #endif +#endif + + + +#define GSNATNEG_BUFFSZ 0xffff +#define GSNATNEG_GOTOQUITX { ret_err = -__LINE__; goto quit; } +#define GSNATNEG_CLOSESD(X) if(X > 0) { close(X); X = -1; } +#define GSNATNEG_FREE(X) if(X) { free(X); X = NULL; } +//#define GSNATNEG_THREAD_SAFE // uhmmm + + + +static int gsnatneg_verbose = 0; + + + +static int gsnatneg_timeout(int sock, int secs) { + struct timeval tout; + fd_set fd_read; + + tout.tv_sec = secs; + tout.tv_usec = 0; + FD_ZERO(&fd_read); + FD_SET(sock, &fd_read); + if(select(sock + 1, &fd_read, NULL, NULL, &tout) + <= 0) return(-1); + return(0); +} + + + +static in_addr_t gsnatneg_resolv(uint8_t *host) { + struct hostent *hp; + in_addr_t host_ip; + + host_ip = inet_addr(host); + if(host_ip == INADDR_NONE) { + hp = gethostbyname(host); + if(!hp) return(INADDR_NONE); + host_ip = *(in_addr_t *)hp->h_addr; + } + return(host_ip); +} + + + +static int gsnatneg_make_tcp(void) { + static struct linger ling = {1,1}; + int sd; + + sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(sd < 0) return(-1); + setsockopt(sd, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling)); + return(sd); +} + + + +static int gsnatneg_make_udp(void) { + static struct linger ling = {1,1}; + static int on = 1; + int sd; + + sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if(sd < 0) return(-1); + setsockopt(sd, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling)); + setsockopt(sd, SOL_SOCKET, SO_BROADCAST, (char *)&on, sizeof(on)); + setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); + return(sd); +} + + + +static in_addr_t gsnatneg_get_sock_ip_port(int sd, uint16_t *port, struct sockaddr_in *ret_peer, int remote) { + struct sockaddr_in peer; + int psz, + ret; + + if(port) *port = 0; + if(ret_peer) memset(ret_peer, 0, sizeof(struct sockaddr_in)); + psz = sizeof(struct sockaddr_in); + if(remote) { + ret = getpeername(sd, (struct sockaddr *)&peer, &psz); + } else { + ret = getsockname(sd, (struct sockaddr *)&peer, &psz); + } + if(ret < 0) return(INADDR_NONE); + if(port) *port = ntohs(peer.sin_port); + if(ret_peer) memcpy(ret_peer, &peer, sizeof(struct sockaddr_in)); + return(peer.sin_addr.s_addr); +} + + + +static int gsnatneg_putrr(uint8_t *data, int len) { + uint32_t seed; + int i; + + seed = time(NULL); + for(i = 0; i < len; i++) { + seed = (seed * 0x343FD) + 0x269EC3; + data[i] = (((seed >> 16) & 0x7fff) % 93) + 33; + } + data[i++] = 0; // len must have +1 considered + return(i); +} + + + +static int gsnatneg_putxx(uint8_t *data, uint32_t num, int bits) { + int i, + bytes; + + if(bits <= 4) bytes = bits; + else bytes = bits >> 3; + for(i = 0; i < bytes; i++) { + data[i] = num >> (i << 3); + } + return(bytes); +} + + + +static int gsnatneg_getxx(uint8_t *data, uint32_t *ret, int bits) { + uint32_t num; + int i, + bytes; + + if(bits <= 4) bytes = bits; + else bytes = bits >> 3; + for(num = i = 0; i < bytes; i++) { + num |= (data[i] << (i << 3)); + } + if(ret) *ret = num; + return(bytes); +} + + + +static int gsnatneg_putss(uint8_t *buff, uint8_t *data) { + int len; + + len = strlen(data) + 1; + memcpy(buff, data, len); + return(len); +} + + + +/* +static int gsnatneg_putmm(uint8_t *buff, uint8_t *data, int len) { + if(len < 0) len = strlen(data) + 1; + memcpy(buff, data, len); + return(len); +} +*/ + + + +static int gsnatneg_putcc(uint8_t *buff, int chr, int len) { + memset(buff, chr, len); + return(len); +} + + + +static int gsnatneg_putpv(uint8_t *buff, uint8_t *par, uint8_t *val) { + uint8_t *p; + + p = buff; + p += gsnatneg_putss(p, par); + p += gsnatneg_putss(p, val); + return(p - buff); +} + + + +static int gsnatneg_send(int sd, in_addr_t ip, uint16_t port, uint8_t *buff, int len) { + struct sockaddr_in peer; + uint8_t tmp[2]; + + if(!buff) { + peer.sin_addr.s_addr = ip; + peer.sin_port = htons(port); + peer.sin_family = AF_INET; + + if(gsnatneg_verbose) fprintf(stderr, "* TCP connection to %s:%u\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port)); + if(connect(sd, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)) + < 0) return(-1); + return(0); + } + if(gsnatneg_verbose) { + peer.sin_addr.s_addr = gsnatneg_get_sock_ip_port(sd, &peer.sin_port, NULL, 1); + fprintf(stderr, "* TCP send %d bytes to %s:%u\n", len, inet_ntoa(peer.sin_addr), peer.sin_port); + } + gsnatneg_putxx(tmp, htons(2 + len), 16); + if(send(sd, tmp, 2, 0) != 2) return(-1); + if(send(sd, buff, len, 0) != len) return(-1); + return(0); +} + + + +static int gsnatneg_sendto(int sd, in_addr_t ip, uint16_t port, uint8_t *buff, int len, struct sockaddr_in *use_peer) { + static struct { + int sd; + in_addr_t ip; + uint16_t port; + uint8_t *buff; + int len; + struct sockaddr_in *use_peer; + } resend = {0, 0, 0, NULL, 0, NULL}; + + struct sockaddr_in tmp_peer, + *peer; + int resent; + + if((sd <= 0) && !buff && (len <= 0)) { +#ifdef GSNATNEG_THREAD_SAFE + return(-1); // resend can't be used in a thread-safe environment +#endif + sd = resend.sd; + ip = resend.ip; + port = resend.port; + buff = resend.buff; + len = resend.len; + use_peer = resend.use_peer; + memset(&resend, 0, sizeof(resend)); + resent = 1; + } else { + resent = 0; + } + + if(sd <= 0) return(-1); + if(ip == INADDR_NONE) return(0); // or -1? + if(use_peer) { + peer = use_peer; + } else { + peer = &tmp_peer; + peer->sin_addr.s_addr = ip; + peer->sin_port = htons(port); + peer->sin_family = AF_INET; + } + if(peer->sin_addr.s_addr == INADDR_NONE) return(-1); + if(!buff) { + connect(sd, (struct sockaddr *)peer, sizeof(struct sockaddr_in)); + return(0); + } + if(gsnatneg_verbose) fprintf(stderr, "* UDP %ssend %d bytes to %s:%u\n", resent ? "re" : "", len, inet_ntoa(peer->sin_addr), ntohs(peer->sin_port)); + if(sendto(sd, buff, len, 0, (struct sockaddr *)peer, sizeof(struct sockaddr_in)) + != len) return(-1); + +#ifndef GSNATNEG_THREAD_SAFE + if(!resent) { + resend.sd = sd; + resend.ip = ip; + resend.port = port; + resend.buff = buff; + resend.len = len; + resend.use_peer = use_peer; + } +#endif + return(0); +} + + + +static int gsnatneg_tcprecv(int sd, uint8_t *buff, int buffsz) { + int t, + len; + + len = buffsz; + while(len) { + if(gsnatneg_timeout(sd, 3) < 0) return(-1); + t = recv(sd, buff, len, 0); + if(t <= 0) return(-1); + buff += t; + len -= t; + } + return(buffsz); +} + + + +/* +static int gsnatneg_recv(int sd, uint8_t *buff, int buffsz) { + struct sockaddr_in peer; + int len; + uint8_t tmp[2]; + + if(gsnatneg_tcprecv(sd, tmp, 2) < 0) return(-1); + len = (tmp[0] << 8) | tmp[1]; + len -= 2; + if(len < 0) return(-1); + if(len > buffsz) return(-1); + if(gsnatneg_tcprecv(sd, buff, len) < 0) return(-1); + if(gsnatneg_verbose) { + peer.sin_addr.s_addr = gsnatneg_get_sock_ip_port(sd, &peer.sin_port, NULL, 1); + fprintf(stderr, "* TCP recv %d bytes from %s:%u\n", len, inet_ntoa(peer.sin_addr), ntohs(peer.sin_port)); + } + return(len); +} +*/ + + + +static int gsnatneg_recvfrom(int sd, struct sockaddr_in *peer, uint8_t *buff, int size) { + int len, + psz; + + //if(gsnatneg_timeout(sd, 2) < 0) return(-1); + if(peer) { + psz = sizeof(struct sockaddr_in); + len = recvfrom(sd, buff, size, 0, (struct sockaddr *)peer, &psz); + } else { + len = recvfrom(sd, buff, size, 0, NULL, NULL); + } + if(len < 0) return(-1); + if(gsnatneg_verbose) fprintf(stderr, "* UDP recv %d bytes from %s:%u\n", len, peer ? inet_ntoa(peer->sin_addr) : "unknown", peer ? ntohs(peer->sin_port) : 0); + return(len); +} + + + +static in_addr_t gsnatneg_gsresolv(uint8_t *gamename, int num) { + in_addr_t ip; + uint32_t i, + c, + server_num; + int len; + uint8_t tmp[256]; + + server_num = 0; + if(gamename && !gamename[0]) gamename = NULL; + if(gamename) { + for(i = 0; gamename[i]; i++) { + c = tolower(gamename[i]); + server_num = c - (server_num * 0x63306ce7); + } + server_num %= 20; + } + + if(num > 0) { // natneg + if(gamename) len = snprintf(tmp, sizeof(tmp), "%s.natneg%d.gamespy.com", gamename, num); + else len = snprintf(tmp, sizeof(tmp), "natneg%d.gamespy.com", num); + } else if(num < 0) { + if(gamename) len = snprintf(tmp, sizeof(tmp), "%s.master.gamespy.com", gamename); + else len = snprintf(tmp, sizeof(tmp), "master.gamespy.com"); + } else { // ms? + if(gamename) len = snprintf(tmp, sizeof(tmp), "%s.ms%d.gamespy.com", gamename, server_num); + else len = snprintf(tmp, sizeof(tmp), "master.gamespy.com"); + } + if((len < 0) || (len >= (int)sizeof(tmp))) return(INADDR_NONE); + if(gsnatneg_verbose) fprintf(stderr, "* resolv %s", tmp); + ip = gsnatneg_resolv(tmp); + if(gsnatneg_verbose) fprintf(stderr, " --> %s\n", inet_ntoa(*(struct in_addr *)&ip)); + return(ip); +} + + + +int gsnatneg(int sd, uint8_t *gamename, uint8_t *gamekey, uint8_t *host, in_addr_t ip, uint16_t port, struct sockaddr_in *ret_peer, int (*mycallback)()) { + +#define GSNATNEG_MYCALLBACK \ + if(mycallback) { \ + if(mycallback(sd, &peer, buff, len) < 0) { \ + mycallback = NULL; \ + GSNATNEG_GOTOQUITX \ + } \ + } + +#ifndef GSNATNEG_THREAD_SAFE + static +#endif + in_addr_t + gsmsx = INADDR_NONE, // master + gsmsh = INADDR_NONE, // hearbeat, almost the same of gsmsx + gsnatneg1 = INADDR_NONE, // natneg1 + gsnatneg2 = INADDR_NONE, // natneg2 + myip = INADDR_NONE; + +#ifndef GSNATNEG_THREAD_SAFE + static +#endif + in_addr_t xip = INADDR_NONE; + +#ifndef GSNATNEG_THREAD_SAFE + static +#endif + uint32_t seed = 0, + send_natneg = 0, + max_timeout = 0; + +#ifndef GSNATNEG_THREAD_SAFE + static +#endif + int st = -1, + need_natneg = 0; + +#ifndef GSNATNEG_THREAD_SAFE + static +#endif + uint16_t myport = 0, + xport = 0; + +#ifndef GSNATNEG_THREAD_SAFE + static +#endif + uint8_t gamenamex[128] = "", + gamekeyx[32] = "", + *buff_alloc = NULL; + + struct sockaddr_in peer; + uint32_t t; + int i, + ok, + len, + ret_err = -1, + client = -1, + done = 0, + new_sock = 0; + uint8_t tmp[64], + pck[2], + *buff = NULL, // idea for implementing the own recvfrom + type, + *p; + + // free resources + if(!gamename && !gamekey && !host && !ip && !port && !ret_peer) { + GSNATNEG_FREE(buff_alloc) + GSNATNEG_CLOSESD(sd) + xip = INADDR_NONE; + myport = 0; + xport = 0; + send_natneg = 0; + st = -1; + need_natneg = 0; + new_sock = 0; + GSNATNEG_GOTOQUITX + } + + if(sd <= 0) { + sd = gsnatneg_make_udp(); + if(sd < 0) GSNATNEG_GOTOQUITX + new_sock = sd; + } + if(host) { + ip = gsnatneg_resolv(host); + } + if(ip == INADDR_NONE) { + if(!ret_peer) GSNATNEG_GOTOQUITX + ip = ret_peer->sin_addr.s_addr; + } + if(ip == INADDR_ANY) { + client = 0; + } else { + client = 1; + } + if(!port) { + if(!ret_peer) { + if(client) GSNATNEG_GOTOQUITX + gsnatneg_get_sock_ip_port(sd, &port, NULL, 0); + } else { + port = ntohs(ret_peer->sin_port); + } + } + if(!client && (new_sock > 0)) { + peer.sin_addr.s_addr = INADDR_ANY; + peer.sin_port = htons(port); + peer.sin_family = AF_INET; + if(bind(sd, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)) + < 0) GSNATNEG_GOTOQUITX + } + + if(gsnatneg_verbose) { + if(client) { + fprintf(stderr, "* NAT negotiation client to %s:%u\n", inet_ntoa(*(struct in_addr *)&ip), port); + } else { + gsnatneg_get_sock_ip_port(sd, &port, NULL, 0); + fprintf(stderr, "* NAT negotiation server on port %u\n", port); + } + } + + /* + if(!gamename) { + gamename = "wiinat"; + if(!gamekey) gamekey = "4Fuy9m"; + } + */ + if(client) { + if(!gamename || !gamename[0]) { + if(gamenamex[0]) { + gamename = gamenamex; // backup + } else { + printf( + "* Gamespy NAT negotiation *\n" + "* insert the Gamespy gamename of the game in use on the remote server:\n" + " if you don't know it take a look here http://aluigi.org/papers/gslist.cfg\n" + "> "); + fgets(tmp, sizeof(tmp), stdin); + for(p = tmp; *p && (*p != '\r') && (*p != '\n'); p++); + *p = 0; + gamename = tmp; + } + if(!gamename[0]) GSNATNEG_GOTOQUITX + } + } else { + if(!gamename || !gamename[0]) { + if(gamenamex[0]) gamename = gamenamex; // backup + else GSNATNEG_GOTOQUITX + } + if(!gamekey || !gamekey[0]) { + if(!gamekeyx[0]) gamekey = gamekeyx; // backup + else GSNATNEG_GOTOQUITX + } + } + + if( + (!gamename || !gamename[0]) || + (gamename && (!gamenamex[0] || stricmp(gamename, gamenamex))) + ) { + gsmsx = gsnatneg_gsresolv(gamename, 0); // gamename.ms?.gamespy.com + if(gsmsx == INADDR_NONE) GSNATNEG_GOTOQUITX + gsmsh = gsnatneg_gsresolv(gamename, -1); // gamename.master.gamespy.com + if(gsmsh == INADDR_NONE) GSNATNEG_GOTOQUITX + gsnatneg1 = gsnatneg_gsresolv(gamename, 1); // gamename.natneg1.gamespy.com + if(gsnatneg1 == INADDR_NONE) GSNATNEG_GOTOQUITX + gsnatneg2 = gsnatneg_gsresolv(gamename, 2); // gamename.natneg2.gamespy.com + if(gsnatneg2 == INADDR_NONE) GSNATNEG_GOTOQUITX + if(gamename) strncpy(gamenamex, gamename, sizeof(gamenamex)); + else gamenamex[0] = 0; + gamenamex[sizeof(gamenamex) - 1] = 0; + } + + if(!buff_alloc) { + buff_alloc = malloc(GSNATNEG_BUFFSZ + 1); + if(!buff_alloc) GSNATNEG_GOTOQUITX + } + buff = buff_alloc; + + if(need_natneg < 0) need_natneg = 0; + /*if(!seed) must be different for each NAT request! */ seed = time(NULL); // needed + +redo: + max_timeout = time(NULL) + 3; + for(;;) { + if((uint32_t)time(NULL) >= (uint32_t)send_natneg) { + if(client) { + st = gsnatneg_make_tcp(); // connect to gamename.ms?.gamespy.com + if(st < 0) GSNATNEG_GOTOQUITX + if(gsnatneg_send(st, gsmsx, 28910, NULL, 0) < 0) GSNATNEG_GOTOQUITX + + p = buff; + p += gsnatneg_putxx(p, 0, 8); + p += gsnatneg_putxx(p, 1, 8); + p += gsnatneg_putxx(p, 3, 8); + p += gsnatneg_putxx(p, 0, 32); + p += gsnatneg_putss(p, gamename); + p += gsnatneg_putss(p, gamename); + p += gsnatneg_putrr(p, 8); + p += gsnatneg_putss(p, ""); // parameters filter + *p++ = 0; + *p++ = 0; + *p++ = 0; + *p++ = 2; + if(gsnatneg_send(st, 0, 0, buff, p - buff) < 0) GSNATNEG_GOTOQUITX + // the reply of the server isn't really necessary + if(!gsnatneg_timeout(st, 5)) { + len = gsnatneg_tcprecv(st, buff, 11); + if(len < 0) GSNATNEG_GOTOQUITX + if(!memcmp(buff, "Query Error", len)) { + while(len < GSNATNEG_BUFFSZ) { + if(recv(st, buff + len, 1, 0) <= 0) break; //GSNATNEG_GOTOQUITX + if(!buff[len]) break; + len++; + } + fprintf(stderr, "* %.*s\n", len, buff); + GSNATNEG_GOTOQUITX + } + } + p = buff; + p += gsnatneg_putxx(p, 2, 8); + p += gsnatneg_putxx(p, ip, 32); + p += gsnatneg_putxx(p, htons(port), 16); + p += gsnatneg_putxx(p, 0xfd, 8); + p += gsnatneg_putxx(p, 0xfc, 8); + p += gsnatneg_putxx(p, 0xb26a661e, 32); + p += gsnatneg_putxx(p, seed, 32); + if(gsnatneg_send(st, 0, 0, buff, p - buff) < 0) GSNATNEG_GOTOQUITX + myip = gsnatneg_get_sock_ip_port(st, NULL, NULL, 0); // sd will give error + send_natneg = -1; // never + t = seed; + goto contact_natneg; + } else { + p = buff; + p += gsnatneg_putxx(p, 3, 8); + p += gsnatneg_putxx(p, seed, 32); + //p += gsnatneg_putpv(p, "localip0", "127.0.0.1"); + //p += gsnatneg_putpv(p, "publicip0", "127.0.0.1"); + //sprintf(tmp, "%u", port); + //p += gsnatneg_putpv(p, "localport", tmp); + //p += gsnatneg_putpv(p, "publicport",tmp); + p += gsnatneg_putpv(p, "natneg", (!need_natneg) ? "0" : "1"); + p += gsnatneg_putpv(p, "gamename", gamename); + p += gsnatneg_putpv(p, "", ""); + p += gsnatneg_putpv(p, "", ""); + if(gsnatneg_sendto(sd, gsmsh, 27900, buff, p - buff, NULL) < 0) GSNATNEG_GOTOQUITX + send_natneg = time(NULL) + 60; + } + } + + if(client && !mycallback) { + if((uint32_t)time(NULL) >= (uint32_t)max_timeout) { + if(gsnatneg_sendto(0, 0, 0, NULL, 0, NULL) < 0) { + if(done) break; + GSNATNEG_GOTOQUITX + } else { + max_timeout = time(NULL) + 2; + } + } + } + if(gsnatneg_timeout(sd, 1) < 0) continue; + len = gsnatneg_recvfrom(sd, &peer, buff, GSNATNEG_BUFFSZ); + if(len < 0) GSNATNEG_GOTOQUITX + if(client && !mycallback) { + max_timeout = time(NULL) + 2; + } + if(len < 3) continue; + + p = buff; + pck[0] = *p++; + pck[1] = *p++; + if((pck[0] == 0xfe) && (pck[1] == 0xfd)) { + type = *p++; + + if(type == 0x01) { + if(((p + 4) - buff) > len) continue; + p += gsnatneg_getxx(p, &t, 32); + if(t != seed) continue; + len -= (p - buff); // len = strlen(p); + if(len < 0) continue; + len = ((len * 4) / 3) + 3; + if(len > (int)sizeof(tmp)) continue; + p[len] = 0; + if(!gamekey) GSNATNEG_GOTOQUITX + gsseckey(tmp, p, gamekey, 0); + + p = buff; + p += gsnatneg_putxx(p, 1, 8); + p += gsnatneg_putxx(p, seed, 32); + p += gsnatneg_putss(p, tmp); + if(gsnatneg_sendto(sd, 0, 0, buff, p - buff, &peer) < 0) GSNATNEG_GOTOQUITX + + } else if(type == 0x02) { + p = buff; // "GameSpy Firewall Probe Packet" + for(i = 2; i < len; i++) { + *p++ = buff[i]; + } + if(gsnatneg_sendto(sd, 0, 0, buff, p - buff, &peer) < 0) GSNATNEG_GOTOQUITX + + } else if(type == 0x04) { + need_natneg++; // GameSpy tells us that it received no reply so we are behing NAT + if(need_natneg > 1) GSNATNEG_GOTOQUITX + send_natneg = time(NULL); + + } else if(type == 0x0a) { + if(((p + 4) - buff) > len) continue; + gsnatneg_getxx(p, &t, 32); + + p = buff; + p += gsnatneg_putxx(p, 8, 8); + p += gsnatneg_putxx(p, t, 32); + if(gsnatneg_sendto(sd, 0, 0, buff, p - buff, &peer) < 0) GSNATNEG_GOTOQUITX + + } else if(type == 0x06) { + if((17 + 4) > len) continue; + gsnatneg_getxx(buff + 17, &t, 32); + + p = buff; // "GameSpy Firewall Probe Packet" + for(i = 0; i < 9; i++) { + *p++ = buff[2 + i]; + } + buff[0] = 0x07; + if(gsnatneg_sendto(sd, 0, 0, buff, p - buff, &peer) < 0) GSNATNEG_GOTOQUITX + + contact_natneg: + + // probably not needed but it's for compatibility with the protocol + if(myip == INADDR_NONE) { + myip = gsnatneg_get_sock_ip_port(sd, NULL, NULL, 0); + //if((myip == INADDR_NONE) || (myip == INADDR_ANY)) myip = inet_addr("127.0.0.1"); + } + + p = buff; + p += gsnatneg_putxx(p, 0xfd, 8); + p += gsnatneg_putxx(p, 0xfc, 8); + p += gsnatneg_putxx(p, 0xb26a661e, 32); + p += gsnatneg_putxx(p, 2, 8); // natneg version + p += gsnatneg_putxx(p, 0, 8); // step, buff[12] + p += gsnatneg_putxx(p, t, 32); // id for tracking the reply + p += gsnatneg_putxx(p, 0, 8); // natneg number, buff[12] + p += gsnatneg_putxx(p, client ? 0 : 1, 8); // ??? + p += gsnatneg_putxx(p, 1, 8); // ??? + p += gsnatneg_putxx(p, myip, 32); // client's IP + p += gsnatneg_putxx(p, 0, 16); // client's port, buff + 19 + p += gsnatneg_putss(p, gamename); + + buff[12] = 0; + if(gsnatneg_sendto(sd, gsnatneg1, 27901, buff, p - buff, NULL) < 0) GSNATNEG_GOTOQUITX + + buff[12] = 1; + if(gsnatneg_sendto(sd, gsnatneg1, 27901, buff, p - buff, NULL) < 0) GSNATNEG_GOTOQUITX + + buff[12] = 2; + gsnatneg_get_sock_ip_port(sd, &myport, NULL, 0); + gsnatneg_putxx(buff + 19, htons(myport), 16); + if(gsnatneg_sendto(sd, gsnatneg2, 27901, buff, p - buff, NULL) < 0) GSNATNEG_GOTOQUITX + + } else { + if(gsnatneg_verbose) fprintf(stderr, "* fefd unknown type %02x\n", type); + GSNATNEG_MYCALLBACK + } + + } else if((pck[0] == 0xfd) && (pck[1] == 0xfc)) { + if(((p + 4 + 1 + 1) - buff) > len) continue; + p += gsnatneg_getxx(p, &t, 32); // 0xb26a661e + p++; + type = *p++; + if(type == 0x01) { + // ignore + + } else if((type == 0x05) || (type == 0x07)) { + if(len < 18) continue; + ok = 0; + if(type == 0x05) { + if(len < 18) continue; + gsnatneg_getxx(buff + 12, &t, 32); + xip = t; + gsnatneg_getxx(buff + 16, &t, 16); + xport = ntohs(t); + if(gsnatneg_verbose) fprintf(stderr, "* %s:%u\n", inet_ntoa(*(struct in_addr *)&xip), xport); + if(!client) { + ip = xip; + port = xport; + } + + buff[7] = 0x06; + p = buff; + p += 12; + // data built by recvfrom? + p += gsnatneg_putcc(p, 0, 9); + buff[13] = client ? 0 : 1; + if(gsnatneg_sendto(sd, 0, 0, buff, p - buff, &peer) < 0) GSNATNEG_GOTOQUITX + + ok = 1; + t = 0; + peer.sin_addr.s_addr = ip; + peer.sin_port = htons(port); + + } else { + if((peer.sin_addr.s_addr == ip) && (peer.sin_port == htons(port))) { + ok = 1; + } else if((peer.sin_addr.s_addr == xip) && (peer.sin_port == htons(xport))) { + // NAT + ok = 2; + } else { + // for hosts behind our same NAT + ok = 3; + } + if(ok) { + if(client) { + if(buff[19]) break; + } + t = buff[18]; + if(client) t++; + } + } + if(ok) { + if((peer.sin_addr.s_addr == ip) && (peer.sin_port == htons(port))) done = 1; + + p = buff; + p += gsnatneg_putxx(p, 0xfd, 8); + p += gsnatneg_putxx(p, 0xfc, 8); + p += gsnatneg_putxx(p, 0xb26a661e, 32); + p += gsnatneg_putxx(p, 2, 8); // client ? 2 : 3 + p += gsnatneg_putxx(p, 7, 8); + p += gsnatneg_putxx(p, seed, 32); + p += gsnatneg_putxx(p, peer.sin_addr.s_addr, 32); + p += gsnatneg_putxx(p, peer.sin_port, 16); + p += gsnatneg_putxx(p, t, 8); + if(client) { + t = 0; + } else { + if(t && (peer.sin_addr.s_addr == ip) && (peer.sin_port == htons(port))) t = 1; + else t = 0; + } + p += gsnatneg_putxx(p, t, 8); + if(gsnatneg_sendto(sd, 0, 0, buff, p - buff, &peer) < 0) GSNATNEG_GOTOQUITX + if(buff[19]) break; + } + + } else { + if(gsnatneg_verbose) fprintf(stderr, "* fdfc unknown type %02x\n", type); + GSNATNEG_MYCALLBACK + } + } else { + GSNATNEG_MYCALLBACK + } + } + + ret_err = sd; +quit: + if(mycallback) goto redo; + GSNATNEG_CLOSESD(st) +#ifdef GSNATNEG_THREAD_SAFE + GSNATNEG_FREE(buff_alloc) +#endif + send_natneg = 0; + return(ret_err); +} + +#endif diff --git a/gslist/src/gsshow.h b/gslist/src/gsshow.h new file mode 100644 index 0000000..8d44333 --- /dev/null +++ b/gslist/src/gsshow.h @@ -0,0 +1,253 @@ +/* + Copyright 2005-2011 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl-2.0.txt +*/ + +void show_list(u8 *name) { + FILE *fd; + u8 buff[GSLISTSZ + 1]; + + fd = gslfopen(GSLISTCFG, "rb"); + if(!fd) { + fprintf(stderr, "- you must use the -u option the first time or use -p if %s is in another folder", GSLISTCFG); + std_err(); + } + + if(!quiet) { + fprintf(fdout, + "DESCRIPTION GAMENAME KEY\n" + "-------------------------------------------------------------------------------\n"); + } + + if(name) { + while(fgets(buff, sizeof(buff), fd)) { + if(stristr(buff, name)) fputs(buff, fdout); + } + } else { + while(fgets(buff, sizeof(buff), fd)) { + fputs(buff, fdout); + } + } + fclose(fd); +} + + + +void show_countries(void) { + int i; + + for(i = 0; countries[i][0]; i++) { + fprintf(stderr, "%s %s\n", countries[i][0], countries[i][1]); + } +} + + + +void show_help(void) { + fprintf(stdout, + "-n GAMENAME servers list of the game GAMENAME (use -l or -s to know it)\n" + " you can also specify multiple GAMENAMEs: -n GN1,GN2,...,GNN\n" + "-l complete list of supported games and their details\n" + "-s PATTERN search for games in the database (case insensitve)\n" + "-u update the database of supported games (http://%s)\n" + "-i HOST PORT send a query to a remote server that supports the old Gamespy\n" + " query protocol ("OLDGSINFO"). HOST:PORT is accepted too\n" + "-I HOST PORT as above but uses the new Gamespy query protocol (FE FD ...)\n" + "-d T HOST PORT another option for various queries. The T parameter is referred\n" + " to the type of query you want to use. Use -d ? for the list\n" + "-f FILTERS specify a filter to apply to the servers list. Use -f ? for help\n" + "-r \"prog...\" lets you to execute a specific program for each IP found.\n" + " there are 2 available parameters: #IP and #PORT automatically\n" + " substituited with the IP and port of the each online game server\n" + "-o OUT specify a different output for the servers list (default output\n" + " is screen). Use -o ? for the list of options\n" +#ifdef GSWEB + "-w IP PORT start the web interface binding your local IP and port\n" + " Use 127.0.0.1 and 80 if you have doubts and then connect your\n" + " browser to http://127.0.0.1. Use 0 for all the interfaces\n" + "-W OPTIONS comma separated options for gsweb with the format OPTION=VALUE\n" + " refresh=0 disable the refresh buffer/button\n" + " admin=127.0.0.1 IP of the admin with access to all the sections\n" +#endif + "-q quiet output, many informations will be not showed\n" + "-x S[:P] specify a different master server (S) and port (P is optional)\n" + " default is %s:%hu\n" + "-b PORT heartbeats sender, your IP address will be included in the\n" + " online servers list, PORT is the query port of your server.\n" + "-B PORT as above but the port is not bound, your server must support\n" + " the old Gamespy query protocol ("OLDGSINFO") for being included\n" + "-L SEC continuous servers list loop each SEC seconds\n" + "-t NUM enctype: 0 (only old games), 1 (Gamespy3d), 2 and -1/X (default)\n" + "-c list of country codes to use with the \"country\" filter\n" + "-Y NAME KEY custom gamename and game key for \"accessing\" the master server\n" + "-p PATH directory where your want to read/store the configuration files\n" + "-m manual generation of "GSLISTCFG" (-M from scratch), users use -u\n" + "-Q T super query, each host in the list will be queried and the info\n" + " will be showed with the backslash '\\' delimiter or added to SQL\n" + " T is the same parameter you can see using -d\n" + " Source engine, DirectPlay and ASE queries are not supported\n" + " use ever -X instead of -Q because it's faster since the info are\n" + " got directly from the master server\n" + "-X INFO similar to the above option except that all the info are sent by\n" + " the same master server saving time, bandwidth and is error free,\n" + " the INFO parameter has the format \\parameter1\\parameter2 like:\n" + " -t -1 -X \"\\hostname\\gamever\\numplayers\\maxplayers\\mapname\"\n" + " works only with enctype -1 and has no NAT limits\n" + " use -X \"\" or -X \\ for the pre-build parameters built in gslist\n" + "-D MS milliseconds to wait between each information query with -Q (%d)\n" +#ifdef SQL + "-S show the SQL options (experimental!!!)\n" + "-E ignore the SQL errors and continues after some seconds\n" +#endif + "-C do not filter colors from the game info replied by the servers\n" + "-G force the usage of gsnatneg when querying the servers\n" + "-z FILE get the list of servers (IP PORT or IP:PORT and so on) from the\n" + " text file FILE, useful only for specific query scannings\n" + "-R retrieve all the Gamespy peerchat rooms of a specific game\n" + "-e some usage examples\n" + "-v show the version of Gslist and its database's folder\n" + "-0 don't wait when using -d/D/i/I\n" + "\n", ALUIGIHOST, MS, MSPORT, scandelay); +} + + + +void show_examples(void) { + fputs( + "Some examples of the Gslist's usage:\n" + "\n" + "-N ut2 retrieves the list of UT2003'servers\n" + "-n 168 retrieves the list of UT2003'servers (168 is the\n" + " current identifier of UT2003, but it can change when you\n" + " update the games'list with the -u or -U options)\n" + "-n ut2,racedriver servers list of both UT2003 and Toca Race Driver\n" + "-N ut2 -f \"country=IT\" to retrieve all the italian servers\n" + "-q -N ut2 retrieves the list of UT2003'servers but shows less info\n" + "-o 1 -N ut2 the servers'list will be dumped in the file ut2.gls\n" + "-o 2 -N ut2 the servers'list will be dumped in "FILEOUT"\n" + "-o 3 -N ut2 the servers'list will be saved in binary form in ut2.gls\n" + "-o 4 -N ut2 same as above but in the file "FILEOUT"\n" + "-o x.txt -N ut2 saves the output in the file x.txt (just as you see it on\n" + " screen but without errors and headers), like > x.txt\n" + "-o 6 full hexadecimal visualization of the raw servers list\n" + "-l shows the list of all the available games\n" + "-s serious shows all the games having a name containing \"serious\"\n" + " and also -s SeRiou or -s SERIOUS is the same\n" + "-i 1.2.3.4 1234 uses the Gamespy query protocol to retrieve remote info\n" + " (not all the games support this protocol)\n" + "-I 1.2.3.4 1234 as above but uses the new Gamespy protocol\n" + "-d 1 1.2.3.4 1234 as above but uses a Quake 3 query\n" + "-u update the list of supported games\n" + "-x localhost -n 2 retrieves the servers'list of the game identified by the\n" + " number 2 using the masterserver at localhost:28900\n" + " If you use -x localhost:1234 will be contacted the master\n" + " server port 1234\n" + "-o file.txt -l the games'list will be dumped in file.txt\n" + "-r \"ping -n 1 #IP\" will ping each IP found\n" + "-r \"gsinfo #IP #PORT\" will launch the program gsinfo\n" + "-b 7778 -N ut2 adds your IP to the game list of Unreal Tournament 2003\n" + " your port 7778 will be temporary bound and your server\n" + " will be not reacheable for some milliseconds\n" + "-B 7778 -N ut2 as above but no ports will be bound. Your server must\n" + " support the "OLDGSINFO" query\n" + "-c shows the list of country codes existent\n" + "-L 60 -N ut2 retrieves the servers of UT2003 each minute\n" + "-w 127.0.0.1 80 web interface enabled\n" + "-v shows the version of Gslist you are using\n" + "-t -1 -X \\hostname -n halor same output of -Q but all the info (hostname in\n" + " this case) comes directly from the same master server\n" + " if you want to receive specific informations quickly and\n" + " without problems of NATted servers, this IS your option\n" + "\n" + "I think these examples are enough, however contact me if you have problems\n" + "\n", stdout); +} + + + +void show_sql(void) { + fputs("\n" + "NOTE THAT SQL IS IMPLEMENTED BUT IS NOT DOCUMENTED AND IS EXPERIMENTAL!\n" + "\n" + "-S H DB U P Q QB QL\n" + " H = host or IP address of the SQL server (use 0.0.0.0 for debug)\n" + " DB = name of the database to use\n" + " U = username for accessing the SQL database\n" + " P = password\n" + " Q = main SQL query string, watch below *\n" + " QB = specify an SQL query to do before the main one. If this query fails\n" + " will be executed the main one too. For example you can use an UPDATE\n" + " here that if fails will be followed by the main query (an INSERT) that\n" + " will add the new entry into the database\n" + " QL = specify a fixed SQL query (so NOT #IP and other dynamic values) to\n" + " be executed immediately after each scan (like a DELETE for example,)\n" + " to use for old entries\n" + "\n" + " Use \"\" for skipping parameters, useful if you want to avoid QB and QL\n" + "\n" + "* The SQL query is just a SQL query containing the values you want to add\n" + " inserting a # before them like #GAMENAME for the gamename value and so on.\n" + " #IP is used for the IP address, #PORT for its port and #DATE for a decimal\n" + " number that identifies the current time in seconds (time(NULL)).\n" + " Remember to use '' for text values. An example of SQL_query is:\n" + " \"INSERT INTO mytable VALUES ('#IP',#PORT,'#GAMENAME','#GAMEVER',#NUMPLAYERS,#MAXPLAYERS);\"\n" + "\n", stdout); +} + + + +void show_filter_help(void) { + fputs("\n" + " Valid operators are:\n" + " <>, !=, >=, !<, <=, !>, =, <, >, (, ), +, -, *, /, %,\n" + " AND, NOT, OR, LIKE, NOT LIKE, IS NULL, IS NOT NULL\n" + "\n" + " Some valid items are:\n" + " hostaddr, hostport, gamever, country (check the -c option)\n" + " hostname, mapname, gametype, gamemode, numplayers, maxplayers\n" + "\n" + " Exist also other parameters specifics for each game like:\n" + " password, dedicated, fraglimit, minnetver, maxteams and so on\n" + "\n" + " Wildcard character: %\n" + " String delimiter: '\n" + "\n" + " Examples: -f \"((hostname LIKE '%Bob%') AND (country='US'))\"\n" + " -f \"(numplayers > 0)\"\n" + " -f \"(gamever LIKE '%1.00.06%')\" -n halor\n" + "\n", stdout); +} + + + +void show_output_help(void) { + fputs("\n" + " 1 = text output to a file for each game (ex: serioussam.gsl).\n" + " string: 1.2.3.4:1234 (plus a final line-feed)\n" + " 2 = text output as above but to only one file ("FILEOUT")\n" + " 3 = binary output to a file for each game: 4 bytes IP, 2 port\n" + " example: (hex) 7F0000011E62 = 127.0.0.1 7778\n" + " 4 = binary output as above but to only one file ("FILEOUT")\n" + " 5 = exactly like 1 but to stdout\n" + " 6 = hexadecimal visualization of the raw servers list as is\n" + " FILENAME = if OUT is a filename all the screen output will be\n" + " dumped into the file FILENAME\n" + "\n", stdout); +} + + diff --git a/gslist/src/gssql.h b/gslist/src/gssql.h new file mode 100644 index 0000000..454c1fb --- /dev/null +++ b/gslist/src/gssql.h @@ -0,0 +1,219 @@ +/* + Copyright 2006,2007,2008,2009,2010 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl-2.0.txt +*/ + +#include + + + +#define SQLSZ 262144 /* useful to avoid security problems without checks */ +#define SQLTIMEOUT 300000 + + + +MYSQL dbase; +int sqldebug = 0; +u8 *sqlquery = NULL; + + + +void sql_fix(u8 *data) { + u8 *p; + + for(p = data; *p; p++) { + switch(*p) { + case '\'': + case '\"': + case '&': + case '^': + case '?': + case '{': + case '}': + case '(': + case ')': + case '[': + case ']': + case '-': + case ';': + case '~': + case '|': + case '$': + case '!': + case '<': + case '>': + case '*': + case '%': + case ',': *p = '.'; break; + default: break; + } + } +} + + + +void sql_parse(u8 *data, u8 *limit, u8 *query) { // data can be modified without problems + int nt = 0; + u8 minitmp[32], + *p, + *port, + *par = NULL; + + port = strchr(data, ':'); + if(!port) return; + port++; + p = strchr(port, ' '); + if(!p) return; + + port[-1] = 0; + *p++ = 0; + if(*p == '\\') *p++ = 0; // avoid possible bug + + replace(query, "#IP", data); + replace(query, "#PORT", port); + sprintf(minitmp,"%u", (unsigned)time(NULL)); + replace(query, "#DATE", minitmp); + + for(data = p; data < limit; data = p + 1, nt++) { + p = strchr(data, '\\'); + if(p) *p = 0; + + if(nt & 1) { + if(!quiet) fprintf(stderr, "%s\n", data); + if(par) { + //enctypex_data_cleaner(par, par, strlen(par)); // parameters don't need to be clean + enctypex_data_cleaner(data, data, -1); + sql_fix(par); + sql_fix(data); + replace(query, par, data); + } + } else { + if(!*data) break; + if(!quiet) fprintf(stderr, "%28s: ", data); + par = data - 1; + *par = '#'; + } + if(!p) break; + } +} + + + +int gssql_init(void) { + fprintf(stderr, "\n- MySQL server connection\n"); + + if(!strcmp(sql_host, "0.0.0.0")) { // debugging + sqldebug = 1; + } else { + sqldebug = 0; + if(!mysql_init(&dbase)) { + fprintf(stderr, "\nError: database initialization error\n\n"); + exit(1); + } + if(!mysql_real_connect(&dbase, sql_host, sql_username, sql_password, sql_database, 0, NULL, 0)) { + fprintf(stderr, "\nError: %s\n\n", mysql_error(&dbase)); + exit(1); + } + } + + sqlquery = malloc(SQLSZ + 1); + if(!sqlquery) std_err(); + return(0); +} + + + +int gssql_later(void) { + if(sql_queryl && (sql_queryl[0] >= ' ')) { // lame fix? + if(!quiet) fprintf(stderr, "\n* SQL_LATER:\n %s\n", sql_queryl); + + if(!sqldebug) { + if(mysql_real_query(&dbase, sql_queryl, strlen(sql_queryl))) { + fprintf(stderr, "\nError: %s\n\n", mysql_error(&dbase)); + if(!ignore_errors) exit(1); + sleep(ONESEC); + return(-1); + } + if(!mysql_affected_rows(&dbase)) { + fprintf(stderr, "\nAlert: no rows have been affected by the SQL operation\n\n"); + } + } + } + return(0); +} + + + +int gssql_close(void) { + if(!sqldebug) { + mysql_close(&dbase); + FREEX(sqlquery); + } + return(0); +} + + + +int gssql(u8 *data) { + static int datab_size = 0; + int datalen; + static u8 *datab = NULL; + + datalen = strlen(data); + if(!datalen) return(0); + + if(sql_queryb) { + if(datalen > datab_size) { + datab_size = datalen + 1; // final NULL + datab = realloc(datab, datab_size); + if(!datab) std_err(); + } + strcpy(datab, data); + strcpy(sqlquery, sql_queryb); + sql_parse(datab, datab + datalen, sqlquery); + if(!quiet) fprintf(stderr, "\n* SQL BEFORE:\n %s\n", sqlquery); + if(!sqldebug) { + if(mysql_real_query(&dbase, sqlquery, strlen(sqlquery))) { + fprintf(stderr, "\nError: %s\n\n", mysql_error(&dbase)); + if(!ignore_errors) exit(1); + sleep(ONESEC); + return(-1); + } + if(mysql_affected_rows(&dbase)) return(0); + if(!quiet) fprintf(stderr, "* operation failed, continue with the normal query\n"); + } + } + + strcpy(sqlquery, sql_query); + sql_parse(data, data + datalen, sqlquery); + if(!quiet) fprintf(stderr, "\n* SQL:\n %s\n", sqlquery); + if(!sqldebug) { + if(mysql_real_query(&dbase, sqlquery, strlen(sqlquery))) { + fprintf(stderr, "\nError: %s\n\n", mysql_error(&dbase)); + if(!ignore_errors) exit(1); + sleep(ONESEC); + return(-1); + } + if(!mysql_affected_rows(&dbase)) { + fprintf(stderr, "\nAlert: no rows have been affected by the SQL operation\n\n"); + } + } + return(0); +} + + diff --git a/gslist/src/gsweb.h b/gslist/src/gsweb.h new file mode 100644 index 0000000..9f53368 --- /dev/null +++ b/gslist/src/gsweb.h @@ -0,0 +1,2506 @@ +/* + Copyright 2005-2011 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl-2.0.txt +*/ + +#define GSWHTTP "HTTP/1.1 200 OK\r\n" \ + "Connection: close\r\n" \ + "Content-Type: text/html\r\n" \ + "\r\n" +#define GSWCFG "gsweb.cfg" +#define GSWFONTSZ 11 +#define GSWQUERYSZ 512 +#define DETECTSZ 300 +#define GSWDEFTIMEOUT 2 +#define GSWFILTERSZ 128 +#define HTTPAMP "&" // & or & + +#define GSWSEND(x) send(sock, x, sizeof(x) - 1, 0) + + + +#ifdef WINTRAY + #include "gswtray.h" +#endif + + + +int gsw_allow_refresh = 1; /* options */ +u8 *gsw_admin_IP = NULL; + + + +gsw_data_t *gsw_data; +gsw_fav_data_t *gsw_fav_data; +int gsw_tot, + gsw_fav_tot, + gsw_maxping, + gsw_noping, + gsw_maxservers, + gsw_font_size; +u8 *gsw_player, + *gsw_filter; // temporary + + + +int gsw_get_query_for_game(u8 *game) { + int i; + + for(i = 0; i < gsw_tot; i++) { + if(!strcmp(gsw_data[i].game, game)) return(gsw_data[i].query); + } + + return(0); +} + + + +u8 *gsw_calc_query(u8 *buff, int type) { + int i; + u8 *p, + *query; + + p = buff; + p += sprintf(p, ""); + + return(buff); +} + + + +void gsw_apply_font(int size) { + u8 *p; + + p = (u8 *)stristr(gsw_skin_head, "font-size:"); + if(p) { + p[10] = (size / 10) + '0'; + p[11] = (size % 10) + '0'; + } + gsw_font_size = size; +} + + + +void gsweb_initconf() { + if(myenctype < 0) { + msip = 0; + if(!enctypex_query[0]) enctypex_query = ENCTYPEX_QUERY_GSLIST; + } else { + fprintf(stderr, "- resolv %s ... ", mshost); + msip = resolv(mshost); // dnsdb is not needed here + fprintf(stderr, "%s\n", myinetntoa(msip)); + } + + gsw_player = strdup("player"); + gsw_tot = 0; + gsw_fav_tot = 0; + gsw_filter = strdup(""); + gsw_data = NULL; + gsw_fav_data = NULL; + gsw_maxping = GSWDEFTIMEOUT; + gsw_noping = 0; + gsw_maxservers = 0; + gsw_apply_font(GSWFONTSZ); + gsw_refresh.game = NULL; + gsw_refresh.buff = NULL; + gsw_refresh.len = 0; +} + + + +void gsweb_saveconf(int save_filter) { + FILE *fd; + int i; + + fd = gslfopen(GSWCFG, "wb"); + if(!fd) std_err(); + + fprintf(fd, "player=%s\n", gsw_player); /* player */ + + for(i = 0; i < gsw_tot; i++) { /* game */ + fprintf(fd, "game=%s;%s;%s;%s;%d;%s\n", + gsw_data[i].game, + gsw_data[i].key, + gsw_data[i].full, + gsw_data[i].path, + gsw_data[i].query, + gsw_data[i].filter); + } + + for(i = 0; i < gsw_fav_tot; i++) { /* favorite */ + fprintf(fd, "fav=%s;%s;%hu;%s\n", + gsw_fav_data[i].game, + myinetntoa(gsw_fav_data[i].ip), + gsw_fav_data[i].port, + gsw_fav_data[i].pass); + } + + fprintf(fd, "maxping=%d\n", gsw_maxping); /* ping */ + fprintf(fd, "noping=%d\n", gsw_noping); /* noping */ + fprintf(fd, "maxservers=%d\n", gsw_maxservers); /* maxservers */ + fprintf(fd, "font_size=%d\n", gsw_font_size); /* font_size */ + fprintf(fd, "enctype=%d\n", myenctype); /* enctype */ + if(save_filter) { + fprintf(fd, "filter=%s\n", gsw_filter); /* filter */ + } + + fclose(fd); +} + + + +int gsw_sort_game_description(void) { + gsw_data_t xchg; + int i, + j, + sorted = 0; + + for(i = 0; i < (gsw_tot - 1); i++) { + for(j = i + 1; j < gsw_tot; j++) { + if(gsw_stricmp(gsw_data[j].full, gsw_data[i].full) < 0) { + xchg = gsw_data[j]; + gsw_data[j] = gsw_data[i]; + gsw_data[i] = xchg; + sorted++; + } + } + } + + return(sorted); +} + + + +void gsweb_loadconf(void) { + FILE *fd; + u8 buff[32 + GSMAXPATH + 1], + *val, + *key, + *full, + *path, + *query, + *filter, + *ip, + *port, + *pass; + + fprintf(stderr, "- load configuration file %s\n", GSWCFG); + + fd = gslfopen(GSWCFG, "rb"); + if(!fd) { + fd = gslfopen(GSWCFG, "wb"); + if(!fd) std_err(); + fclose(fd); + return; + } + + while(fgets(buff, sizeof(buff), fd)) { + delimit(buff); + + #define FINDIT(x,y,z) x = strchr(y, z); \ + if(x) { \ + *x++ = 0; \ + } else { \ + x = ""; \ + } + + FINDIT(val, buff, '='); + + if(!strcmp(buff, "player")) { /* player */ + MYDUP(gsw_player, val); + + } else if(!strcmp(buff, "game")) { /* game */ + FINDIT(key, val, ';'); + FINDIT(full, key, ';'); + FINDIT(path, full, ';'); + FINDIT(query, path, ';'); + FINDIT(filter, query, ';'); + + gsw_data = realloc((u8 *)gsw_data, (gsw_tot + 1) * sizeof(gsw_data_t)); + if(!gsw_data) std_err(); + memset(&gsw_data[gsw_tot], 0, sizeof(gsw_data_t)); + + MYDUP(gsw_data[gsw_tot].game, val); + MYDUP(gsw_data[gsw_tot].key, key); + MYDUP(gsw_data[gsw_tot].full, full); + MYDUP(gsw_data[gsw_tot].path, path); + gsw_data[gsw_tot].query = atoi(query); + MYDUP(gsw_data[gsw_tot].filter, filter); + + gsw_tot++; + + } else if(!strcmp(buff, "fav")) { /* fav */ + FINDIT(ip, val, ';'); + FINDIT(port, ip, ';'); + FINDIT(pass, port, ';'); + + gsw_fav_data = realloc((u8 *)gsw_fav_data, (gsw_fav_tot + 1) * sizeof(gsw_fav_data_t)); + if(!gsw_fav_data) std_err(); + memset(&gsw_fav_data[gsw_fav_tot], 0, sizeof(gsw_fav_data_t)); + + MYDUP(gsw_fav_data[gsw_fav_tot].game, val); + gsw_fav_data[gsw_fav_tot].ip = resolv(ip); + gsw_fav_data[gsw_fav_tot].port = atoi(port); + MYDUP(gsw_fav_data[gsw_fav_tot].pass, pass); + + gsw_fav_tot++; + + } else if(!strcmp(buff, "filter")) { /* filter (compatibility only!) */ + MYDUP(gsw_filter, val); + + } else if(!strcmp(buff, "maxping")) { /* ping */ + gsw_maxping = atoi(val); + + } else if(!strcmp(buff, "noping")) { /* noping */ + gsw_noping = atoi(val); + + } else if(!strcmp(buff, "maxservers")) { /* maxservers */ + gsw_maxservers = atoi(val); + + } else if(!strcmp(buff, "font_size")) { /* font size */ + gsw_apply_font(atoi(val)); + + } else if(!strcmp(buff, "enctype")) { + myenctype = atoi(val); + } + } + + #undef FINDIT + + fclose(fd); + fprintf(stderr, "- %d games loaded and configured\n", gsw_tot); + fprintf(stderr, "- %d favorites loaded and configured\n", gsw_fav_tot); + + if(gsw_sort_game_description()) gsweb_saveconf(0); +} + + + +int gsw_game_exists(u8 *value) { + int i; + + for(i = 0; i < gsw_tot; i++) { + if(!strcmp(gsw_data[i].game, value)) return(i); + } + return(-1); +} + + + +int gsw_read_cfg_value(u8 *fname, u8 *game, u8 *parcmp, u8 *buff, int max, FILE *fd) { + int type, + found = 0; + u8 *par, + *val; + + if(fname) { + fd = gslfopen(fname, "rb"); + if(!fd) return(0); + } else { + rewind(fd); // fast solution + } + + while((type = myreadini(fd, buff, max, &par, &val)) >= 0) { + if(!type) { /* gamename */ + if(found) break; + if(!strcmp(par, game)) found++; + } else if(found) { /* parameter/value */ + if(!strcmp(par, parcmp)) break; + } + } + + if(fname) fclose(fd); + + if(val) return(mystrcpy(buff, val, max)); + return(0); +} + + + +void gsw_memcpy_filter(u8 *dst, u8 *src, int len, u8 *filter) { + int i; + + if(filter) { + for(i = 0; i < len; i++) { + dst[i] = src[i]; + if(strchr(filter, dst[i])) dst[i] = ' '; + } + } else { + memcpy(dst, src, len); + } +} + + +int gsw_substitute(u8 *buff, int bufflen, u8 *from, u8 *to, int add_apex) { + int fromlen = strlen(from), + tolen = strlen(to); + u8 *p; + + while((p = (u8 *)stristr(buff, from))) { // case insensitive + if(add_apex && (*(p - 1) != '\"') && (p[fromlen] != '\"')) { + memmove(p + 1 + tolen + 1, p + fromlen, bufflen + 1); + *p++ = '\"'; + //memcpy(p, to, tolen); + gsw_memcpy_filter(p, to, tolen, "\"\'"); + *(p + tolen) = '\"'; + tolen += 2; + } else { + memmove(p + tolen, p + fromlen, bufflen + 1); + //memcpy(p, to, tolen); + gsw_memcpy_filter(p, to, tolen, "\"\'"); + } + bufflen += (tolen - fromlen); + } + return(bufflen); +} + + + +u8 *gsw_http_value(u8 *req, u8 *par, u8 *value) { + u8 *p; + + *(u8 **)par = NULL; + *(u8 **)value = NULL; + + p = strchr(req, '?'); /* par */ + if(p) { + p++; + } else { + p = req; + } + *(u8 **)par = p; + + p = strchr(p, '='); /* value */ + if(!p) return(NULL); + *p++ = 0; + *(u8 **)value = p; + + p = strchr(p, '&'); /* next */ + if(!p) return(NULL); + *p++ = 0; + return(p); +} + + + +void gsw_http2chr(u8 *data, int len) { + int chr; + u8 *out; + + for(out = data; len; out++, data++, len--) { + if(*data == '%') { + sscanf(data + 1, "%02x", &chr); + *out = chr; + len -= 2; + data += 2; + continue; + } else if(*data == '+') { + *out = ' '; + } else { + *out = *data; + } + } + *out = 0; +} + + + +void gsweb_update(int sock) { + GSWSEND(GSWDEFAULT3); + GSWSEND(GSWUPDATE1); + + gsfullup_aluigi(); + if( + (cool_download(FULLCFG, ALUIGIHOST, 80, "papers/" FULLCFG) < 0) || + (cool_download(DETECTCFG, ALUIGIHOST, 80, "papers/" DETECTCFG) < 0)) { + if(cool_download(KNOWNCFG, HOST, 28900, "software/services/index.aspx") > 0) { + verify_gamespy(); + } + cool_download(DETECTCFG, HOST, 28900, "software/services/index.aspx?mode=detect"); + } + + GSWSEND(GSWUPDATE2); +} + + + +void gsweb_update_aluigi(int sock) { + GSWSEND(GSWDEFAULT3); + GSWSEND(GSWUPDATE1); + + gsfullup_aluigi(); + cool_download(FULLCFG, ALUIGIHOST, 80, "papers/" FULLCFG); + cool_download(DETECTCFG, ALUIGIHOST, 80, "papers/" DETECTCFG); + + GSWSEND(GSWUPDATE2); +} + + + +void gsweb_show_bottom(int sock, u8 *game, int servers, u8 *psa) { + int i, + thisone = 0; + u8 *filter = "", + *filter_status; + + for(i = 0; i < gsw_tot; i++) { + if(strcmp(gsw_data[i].game, game)) continue; + filter = gsw_data[i].filter; + thisone = i; + break; + } + if(filter[0]) { + filter_status = "ON"; + } else { + filter_status = "off"; + } + if(gsw_filter[0]) { + filter = gsw_filter; + filter_status = "ON (temp)"; + } + + tcpspr(sock, + "" // not closed before + "%s" + "%s" + "hosts %d" + "filter %s" + "" + "
" + "" + " sort: " + "" + "" +// "" + " "); + if(myenctype >= 0) tcpspr(sock, ""); + + tcpspr(sock, + "
" + "" + "" + "" + "%s" + "%s" + "", +// game, + GSWDEFAULT1, + psa); + } else { + tcpspr(sock, + "" + "" + "" + "" + "" + "" + "%s" + "%s" + "", + GSWDEFAULT1, + psa); + } +} + + + +void gsweb_index(int sock) { + GSWSEND(gsw_skin_games); + gsweb_show_bottom(sock, "", 0, ""); +} + + + +void gsweb_show_ipport(int sock, u8 *game, ipdata_t *gip, u8 *pass, int favport) { + int gyr = 0; // 0 = g, 1 = y, 2 = r + u8 mypass[64], + myfav[256], + *ipc, + *entryc = "", + *playersc = "", + *modec = "", + *ded = "", + *pwd = "", + *pwdc = "", + *pb = "", + *pbc = "", + *rank = ""; + +#define GREEN " class=\"g\"" +#define YELLOW " class=\"y\"" +#define RED " class=\"r\"" +#define BLUE " class=\"b\"" + entryc = GREEN; + + ipc = myinetntoa(gip->ip); + if((gip->ded == '1') || ((gip->ded | 0x20) == 't')) { + ded = "D"; + } + if((gip->pwd == '1') || ((gip->pwd | 0x20) == 't')) { + pwd = "P"; + pwdc = YELLOW; + gyr = 1; + } + if((gip->pb == '1') || ((gip->pb | 0x20) == 't')) { + pb = "PB"; + pbc = BLUE; + //gyr = 1; + } + if((gip->rank == '1') || ((gip->rank | 0x20) == 't')) { + rank = "R"; + } + if(!gip->players) { + playersc = YELLOW; + gyr = 1; + } else if(gip->players >= gip->max) { + playersc = RED; + gyr = 2; + } + if(!gip->max) { + playersc = RED; + gyr = 2; + } + if(gip->mode && strstr(gip->mode, "close")) { + modec = RED; + gyr = 2; + } + if(gyr == 1) { + entryc = YELLOW; + } else if(gyr == 2) { + entryc = RED; + } + + if(pass && *pass) { + mysnprintf(mypass, sizeof(mypass), HTTPAMP "pass=%s", pass); + } else { + *mypass = 0; + } + + if(favport) { + mysnprintf(myfav, sizeof(myfav), + //"
" + //"" + //"" + //"" + //"
", + "x", + ipc, favport); + // favport is REQUIRED because sometimes the query port is different than the game port... + } else { + //*myfav = 0; + mysnprintf(myfav, sizeof(myfav), + "o", + game, ipc, ntohs(gip->qport), mypass); + } + + tcpspr(sock, + "" + "%s" // fav + "%s:%hu" + "%.50s" // name + "%.50s" // map + "%.30s" // type + "%u/%u" // players + "%.30s" // version + "%.30s" // mod + "%s" // dedicated + "%s" // password + "%s" // punkbuster + "%s" // ranked/rated + "%.50s" // mode + "%s" // country + "%s" // query port + "%d" // ping + "", + myfav, + entryc, game, ipc, ntohs(gip->port), mypass, ipc, ntohs(gip->port), + gip->name ? gip->name : (u8 *)"", + gip->map ? gip->map : (u8 *)"", + gip->type ? gip->type : (u8 *)"", + playersc, gip->players, + gip->max, + gip->ver ? gip->ver : (u8 *)"", + gip->mod ? gip->mod : (u8 *)"", + ded, + pwdc, pwd, + pbc, pb, + rank, + modec, gip->mode ? gip->mode : (u8 *)"", + gip->country ? gip->country : (u8 *)"", + (gip->port == gip->qport) ? (u8 *)"" : myitoa(ntohs(gip->qport)), + gip->ping); + +#undef GREEN +#undef YELLOW +#undef RED +#undef BLUE +} + + + +void gsweb_join(int sock, u8 *req) { + ipdata_t *gip; + ipport_t ipport; + u32 ping = gsw_maxping; + int i, + query = -1, + thisone = 0; + u16 port = 0; + u8 tmpquery[GSWQUERYSZ + 1], + *p, + *par, + *value, + *game = "", + *ip = NULL, + *pass = ""; + + if(req) { + p = req; + do { + p = gsw_http_value(p, (u8 *)&par, (u8 *)&value); + if(!value) break; + + if(!strcmp(par, "ip")) { + ip = value; + + } else if(!strcmp(par, "port")) { + port = atoi(value); + + } else if(!strcmp(par, "game")) { + game = value; + + } else if(!strcmp(par, "ping")) { + ping = atoi(value); + + } else if(!strcmp(par, "query")) { + query = atoi(value); + + } else if(!strcmp(par, "pass")) { + pass = value; + } + } while(p); + } + + GSWSEND(GSWDEFAULT3); + + for(i = 0; i < gsw_tot; i++) { + if(strcmp(gsw_data[i].game, game)) continue; + thisone = i; + break; + } + + GSWSEND( + "" + "" + "IP:PORT"); + + tcpspr(sock, /* PING */ + "
" + "" + "%s" + "" + "
", + ip ? ip : (u8 *)"", + port ? ":" : "", + port ? myitoa(port) : (u8 *)"", + gsw_calc_query(tmpquery, 0)); + + tcpspr(sock, /* PING2 */ + "
" + "" + "" + "" + "
" + ); + + tcpspr(sock, /* JOIN */ + "
" + "" + "" + "password:" + "" + "
" + ); + + tcpspr(sock, /* FAV */ + "
" + "" + "" + "password:" + "" + "
" + ); + + GSWSEND(""); + + if(!ip) return; + + p = strchr(ip, ':'); + if(p) { + *p = 0; + port = atoi(p + 1); + } + + if(!port) { + GSWSEND("
No valid port specified, use IP:PORT
"); + return; + } + if(query < 0) { + if(!*game) return; + query = gsw_get_query_for_game(game); + } + + GSWSEND(gsw_skin_games); + + ipport.ip = resolv(ip); + ipport.port = htons(port); + + gip = calloc(2, sizeof(ipdata_t)); + if(!gip) std_err(); + + multi_scan(game, query, &ipport, 1, gip, ping); + if(gip[0].sort == IPDATA_SORT_CLEAR) gip[0].ping = 0; + gsweb_show_ipport(sock, game, &gip[0], "", 0); + + free_ipdata(gip, 2); + + GSWSEND(""); +} + + + +void gsweb_fav(int sock, u8 *req) { + ipdata_t *gip; + ipport_t ipport; + in_addr_t ip; + int i, + query, + op = 0; + u16 port = 0; + u8 *p, + *par, + *value, + *game = "", + *ipc = NULL, + *pass = ""; + + if(req) { + p = req; + do { + p = gsw_http_value(p, (u8 *)&par, (u8 *)&value); + if(!value) break; + + if(!strcmp(par, "ip")) { + ipc = value; + + } else if(!strcmp(par, "port")) { + port = atoi(value); + + } else if(!strcmp(par, "game")) { + game = value; + + } else if(!strcmp(par, "pass")) { + pass = value; + + } else if(!strcmp(par, "add")) { + op = 1; + + } else if(!strcmp(par, "rem")) { + op = 2; + } + } while(p); + + if(!ipc || !port) return; + + p = strchr(ipc, ':'); + if(p) { + *p = 0; + port = atoi(p + 1); + } + ip = resolv(ipc); + + if(op == 1) { + for(i = 0; i < gsw_fav_tot; i++) { + if((gsw_fav_data[i].ip == ip) && (gsw_fav_data[i].port == port)) return; + } + + + gsw_fav_data = realloc((u8 *)gsw_fav_data, (gsw_fav_tot + 1) * sizeof(gsw_fav_data_t)); + if(!gsw_data) std_err(); + memset(&gsw_fav_data[gsw_fav_tot], 0, sizeof(gsw_fav_data_t)); + + MYDUP(gsw_fav_data[gsw_fav_tot].game, game); + gsw_fav_data[gsw_fav_tot].ip = ip; + gsw_fav_data[gsw_fav_tot].port = port; + MYDUP(gsw_fav_data[gsw_fav_tot].pass, pass); + + gsw_fav_tot++; + gsweb_saveconf(0); + + GSWSEND("server added to favorites"); + return; + + } else if(op == 2) { + for(i = 0; i < gsw_fav_tot; i++) { + if((gsw_fav_data[i].ip == ip) && (gsw_fav_data[i].port == port)) break; + } + if(i < gsw_fav_tot) { + gsw_fav_tot--; + if(i != gsw_fav_tot) { + memmove(&gsw_fav_data[i], &gsw_fav_data[gsw_fav_tot], sizeof(gsw_fav_data_t)); + } + gsweb_saveconf(0); + } + GSWSEND("server removed from favorites"); + return; + } + } + + GSWSEND(gsw_skin_games); + + gip = calloc(2, sizeof(ipdata_t)); + if(!gip) std_err(); + + for(i = 0; i < gsw_fav_tot; i++) { + ipport.ip = gsw_fav_data[i].ip; + ipport.port = htons(gsw_fav_data[i].port); + query = gsw_get_query_for_game(gsw_fav_data[i].game); + + memset(gip, 0, sizeof(ipdata_t)); + multi_scan(game, query, &ipport, 1, gip, gsw_maxping); + if(gip[0].sort == IPDATA_SORT_CLEAR) gip[0].ping = 0; + gsweb_show_ipport(sock, gsw_fav_data[i].game, &gip[0], gsw_fav_data[i].pass, gsw_fav_data[i].port); + } + + free_ipdata(gip, 2); + + GSWSEND("

if all the servers of one game can't be queried go in the "Config" section and choose a different "query" for that game,
for example replace "Gamespy \\status\\" with "Gamespy 3""); +} + + + +int enctypex_info_to_gip(ipdata_t *gip, u8 *data) { + int i, + t, + nt = 0; + u8 *p, + *port, + *par = NULL, + *limit; + + limit = data + strlen(data); + + //memset(gip, 0, sizeof(ipdata_t)); not needed + + port = strchr(data, ':'); + if(!port) return(-1); + port++; + p = strchr(port, ' '); + if(!p) return(-1); + + port[-1] = 0; + *p++ = 0; + if(*p == '\\') *p++ = 0; // avoid possible bug + + gip->ip = resolv(data); + gip->port = htons(atoi(port)); + gip->qport = gip->port; + + for(data = p; data < limit; data = p + 1, nt++) { + p = strchr(data, '\\'); + if(p) *p = 0; + + if(nt & 1) { + if(par) { + for(i = 1; i < 8192; i <<= 1) { // value referred to handle_query_par + t = handle_query_par(par, i); + if(!handle_query_val(data, t, gip)) break; + } + } + } else { + if(!*data) break; + par = data; + } + if(!p) break; + } + + return(0); +} + + + +int enctypeX_to_gsweb(ipdata_t *gip, u8 *buff, enctypex_data_t *enctypex_data) { + int len, + servers; + u8 enctypex_info[QUERYSZ]; + + servers = 0; + for(len = 0;;) { + len = enctypex_decoder_convert_to_ipport(buff + enctypex_data->start, enctypex_data->offset - enctypex_data->start, NULL, enctypex_info, sizeof(enctypex_info), len); + if(len <= 0) break; + if(enctypex_info_to_gip(&gip[servers], enctypex_info) < 0) continue; + servers++; + } + return(servers); +} + + + +void gsweb_list(int sock, u8 *req) { + enctypex_data_t enctypex_data; + ipdata_t *gip; + ipport_t *ipport; + struct sockaddr_in peer; + u32 ping; + int i, + servers = 0, + sd, + len, + dynsz, + query, + refresh = 0, + sort_type = GSW_SORT_PING, + free_servers, + itsok; + u8 *buff = NULL, + psa[280], + *gamestr = "", + *gamekey = "", + *filter = "", + validate[89], + secure[66], + *p, + *par, + *value, + *enctypextmp = NULL; + + if(!req) { + gsweb_index(sock); + return; + } + + if(myenctype < 0) { + sort_type = GSW_SORT_PLAYER; + } + + ping = gsw_maxping; + query = -1; + p = req; + do { + p = gsw_http_value(p, (u8 *)&par, (u8 *)&value); + if(!value) break; + + if(!strcmp(par, "game")) { + if(*value) gamestr = value; + + } else if(!strcmp(par, "ping")) { + ping = atoi(value); + + } else if(!strcmp(par, "query")) { + query = atoi(value); + + } else if(!strcmp(par, "key")) { + gamekey = value; + + } else if(!strcmp(par, "filter")) { + filter = value; + + } else if(!strcmp(par, "refresh")) { + refresh = 1; + + } else if(!strcmp(par, "sort")) { + sort_type = atoi(value); + } + } while(p); + + if(!gamestr || !*gamestr) goto quit; + + i = gsw_game_exists(gamestr); + if(i < 0) { + if(query < 0) query = 0; /* default \status\ */ + } else { + if(!*gamestr) gamestr = gsw_data[i].game; + if(query < 0) query = gsw_data[i].query; + if(!*filter) filter = gsw_data[i].filter; + } + if(gsw_filter[0]) filter = gsw_filter; + + if(gsw_allow_refresh && refresh && gsw_refresh.game && !strcmp(gsw_refresh.game, gamestr)) { + len = gsw_refresh.len; + ipport = (ipport_t *)gsw_refresh.buff; + goto scanner; + } else { + refresh = 0; + FREEX(gsw_refresh.buff); + gsw_refresh.game = NULL; + gsw_refresh.buff = NULL; + gsw_refresh.len = 0; + } + + gslist_step_1(gamestr, filter); + + peer.sin_addr.s_addr = msip; + peer.sin_port = htons(msport); + peer.sin_family = AF_INET; + + buff = malloc(BUFFSZ + 1); + if(!buff) std_err(); + dynsz = BUFFSZ; + + sd = gslist_step_2(&peer, buff, secure, gamestr, validate, filter, &enctypex_data); + if(sd < 0) { + if(!quiet) fputs("- connection refused\n", stderr); + goto quit; + } + + ipport = gslist_step_3(sd, validate, &enctypex_data, &len, &buff, &dynsz); + + itsok = gslist_step_4(secure, buff, &enctypex_data, &ipport, &len); + +scanner: + GSWSEND(gsw_skin_games); + + if(!len) goto quit; + + servers = (len / 6) + 1; /* +1 is needed for zeroing the latest IP! */ + free_servers = servers; /* do NOT touch */ + gip = calloc(servers, sizeof(ipdata_t)); /* calloc for zeroing everything */ + if(!gip) std_err(); + for(i = 0; i < servers; i++) { + gip[i].ping = -1; + } + + if(gsw_maxservers && (gsw_maxservers < servers) && !quiet) { + fprintf(stderr, "- limit warning: ping %d servers\n", servers); + } + + if(myenctype < 0) { + if(ipport) { + servers = enctypeX_to_gsweb(gip, buff, &enctypex_data); + } else { + servers = 0; + } + } else { + servers = multi_scan(gamestr, query, ipport, servers - 1, gip, ping); + } + + if(gsw_allow_refresh && !refresh) { + len = servers * 6; + FREEX(gsw_refresh.buff); + gsw_refresh.buff = malloc(len); + if(!gsw_refresh.buff) std_err(); + if(ipport) memcpy(gsw_refresh.buff, ipport, len); + MYDUP(gsw_refresh.game, gamestr); + gsw_refresh.len = len; + } + + if(gsw_maxservers && (gsw_maxservers < servers)) { + servers = gsw_maxservers; + } + + gsw_sort_IP(gip, servers, sort_type); + for(i = 0; i < servers; i++) { + if(gsw_noping && (gip[i].sort == IPDATA_SORT_CLEAR)) continue; + gsweb_show_ipport(sock, gamestr, &gip[i], "", 0); + } + + free_ipdata(gip, free_servers); + +quit: + if(!gsw_read_cfg_value(DETECTCFG, gamestr, "PSA", psa, sizeof(psa), NULL)) { + *psa = 0; + } + FREEX(enctypextmp); + FREEX(buff); + + gsweb_show_bottom(sock, gamestr, servers, psa); +} + + + +void gsweb_clean_exec(u8 *data) { + u8 *p; + + for(p = data; *p; p++) { + if(strchr("><;&|", *p)) *p = ' '; + } +} + + + +void gsweb_play(int sock, u8 *req, int localip) { + int i = -1, + len, + ret; + u8 buff[FULLSZ + 128], + *exe, + *p, + *l, + *par, + *value, + *ip = NULL, + *port = NULL, + *pass = NULL; + + if(!req) { + gsweb_index(sock); + return; + } + + p = req; + do { + p = gsw_http_value(p, (u8 *)&par, (u8 *)&value); + if(!value) break; + + if(!strcmp(par, "game")) { + i = gsw_game_exists(value); + + } else if(!strcmp(par, "ip")) { + ip = value; + + } else if(!strcmp(par, "port")) { + port = value; + + } else if(!strcmp(par, "pass")) { /* works but is manual for the moment! */ + pass = value; + } + } while(p); + + if(!ip || !port || (i < 0)) { + gsweb_index(sock); + return; + } + + exe = strrchr(gsw_data[i].path, PATH_SLASH); + if(exe) { + *exe = 0; + ret = chdir(gsw_data[i].path); + *exe = PATH_SLASH; + if(ret < 0) { + tcpspr(sock, + "

Error while entering in the game folder: %s
", + gsw_data[i].path); + return; + } + exe++; + } else { + exe = gsw_data[i].path; + } +#ifndef WIN32 + p = malloc(strlen(exe) + 3); + sprintf(p, "./%s", exe); + exe = p; +#endif + + if(!exe || !exe[0]) { + tcpspr(sock, + "

Error: there is no executable path assigned to this game, set it in Config
", + gsw_data[i].path); + return; + } + + GSWSEND(GSWDEFAULT3); + + len = gsw_read_cfg_value(FULLCFG, gsw_data[i].game, "jointemplate", buff, FULLSZ, NULL); + if(len) { + len = gsw_substitute(buff, len, "#EXEPATH#", exe, 1); + len = gsw_substitute(buff, len, "#SERVERIP#", ip, 0); + len = gsw_substitute(buff, len, "#SERVERPORT#", port, 0); + len = gsw_substitute(buff, len, "#PLAYERNAME#", gsw_player, 1); + len = gsw_substitute(buff, len, "#PLAYER#", gsw_player, 1); + + /* NOT YET SUPPORTED!!! */ + len = gsw_substitute(buff, len, "#LOCALIP#", "", 0); + len = gsw_substitute(buff, len, "#ALLIPS#", "", 0); + len = gsw_substitute(buff, len, "#GAMEVARIANT#", "", 1); + len = gsw_substitute(buff, len, "#ROOMNAME#", "", 1); + len = gsw_substitute(buff, len, "#GROUPID#", "", 1); + len = gsw_substitute(buff, len, "#PLAYEREMAIL#", "", 1); + len = gsw_substitute(buff, len, "#PLAYERPID#", "", 1); + len = gsw_substitute(buff, len, "#PLAYERPW#", "", 1); + len = gsw_substitute(buff, len, "#PASSWORD#", "", 1); + len = gsw_substitute(buff, len, "#GPNAME#", "", 1); + len = gsw_substitute(buff, len, "#CHATNAME#", "", 1); + len = gsw_substitute(buff, len, "#GAMETYPE#", "", 1); + len = gsw_substitute(buff, len, "#MAXCLIENTS#", "", 1); + len = gsw_substitute(buff, len, "#GAMETYPECMD#", "", 1); + + // #RULE(gamever)# + // #RULE('gamever')# + // #REGVAL(HKEY_LOCAL_MACHINE\Software\Monolith Productions\Aliens vs. Predator 2\1.0\InstallDir)# + + p = strstr(buff, "[$SERVERPW$"); + if(p) { + if(pass) { + memmove(p, p + 11, (len + 1) - 11); + len = gsw_substitute(buff, len - 11, "#SERVERPW#", pass, 1); + l = strchr(p, ']'); + if(l) { + memmove(l, l + 1, (len + 1) - (l - buff)); + len--; + } + } else { + l = strchr(p, ']'); + if(l) { + l++; + } else { + l = buff + len; + } + memmove(p, l, (len + 1) - (l - buff)); + len -= (l - p); + } + } + + } else { + if(!quiet) fprintf(stderr, "- this game doesn't support direct IP join, I launch the game\n"); + len = sprintf(buff, "\"%s\"", exe); + } + +#ifndef WIN32 + FREEX(exe); +#endif + + if(localip && !quiet) fprintf(stderr, "- Execute: %s\n", buff); + send(sock, buff, len, 0); + + if(!localip) return; + + p = strchr(buff + 1, '\"'); + if(p != (buff + 1)) { + + gsweb_clean_exec(buff); + gsweb_clean_exec(p); + +#ifdef WIN32 + if(p) { + p++; + *p = 0; + p++; + } + + ret = (int)ShellExecute( + NULL, + "open", + buff, + p, + NULL, + SW_SHOW); +#else + strcat(buff, " &"); + ret = system(buff); +#endif + + tcpspr(sock, "

Return code %d
", ret); + } +} + + + +void gsweb_show_filter_option(int sock, u8 *name, u8 *val1, u8 *op, u8 *val2) { + tcpspr(sock, // lame solution, consider it a work-around + "" + "" + "" + "" + GSWFILTERX, + name, + op, + val1, + val2); +} + + + +void gsweb_show_filter(int sock, u8 *name, int type) { +#define TEXT1 "" + "" + "" + "" + GSWFILTERX, + name, + type ? TEXT2 : TEXT1, + name); + +#undef TEXT1 +#undef TEXT2 +} + + + +void gsweb_set_filter(int sock, u8 *req) { + int i, + mlen; + u8 *p, + *f, + *par, + *value, + *op; + + if(req) { + mlen = strlen(req); + p = req; + do { + p = gsw_http_value(p, (u8 *)&par, (u8 *)&value); + if(!value) break; + + if(!strcmp(par, "full")) { + MYDUP(gsw_filter, value); + continue; + } + + FREEX(gsw_filter); + gsw_filter = malloc((mlen * 3) + 1); /* not really right */ + if(!gsw_filter) std_err(); + + f = gsw_filter; + *f++ = '('; + op = value; /* avoid errors */ + do { + if(!strcmp(par, "op")) { + op = value; + if(!*op) op = "=="; + + } else if(!strcmp(par, "more") && *value) { + if((*(f - 1) == ')')) { + f += sprintf(f, " %s ", value); + } + + } else if(*value && (*(value + 1) != '\'')) { + f += sprintf(f, + "(%s %s %s%s%s)", + par, op, + (*op >= 'A') ? "'" : "", + value, + (*op >= 'A') ? "'" : ""); + } + if(!p) break; + p = gsw_http_value(p, (u8 *)&par, (u8 *)&value); + } while(value); + + if(*(f - 1) == '(') { + f--; + } else { + if(*(f - 1) == ' ') { + for(f--; *f != ')'; f--); + f++; + } + *f++ = ')'; + } + *f = 0; + } while(p); + } + + GSWSEND(GSWDEFAULT3); + + tcpspr(sock, + "%s" + "%s" + "current filter
" + "you can set it manually with the following entry or using the various options below
" + "" + "%s" + "%s" + "%s", + GSWFILTER1, + GSWFILTER2, + gsw_filter, + GSWFILTER3, + GSWDEFAULT2, + GSWFILTER2); + + gsweb_show_filter(sock, "hostaddr", 0); + gsweb_show_filter(sock, "hostport", 1); + gsweb_show_filter(sock, "gamever", 1); + + GSWSEND( + "" + "" + "" + ""); + GSWSEND(GSWFILTERX); + + gsweb_show_filter(sock, "hostname", 0); + gsweb_show_filter(sock, "mapname", 0); + gsweb_show_filter(sock, "gametype", 0); + gsweb_show_filter(sock, "gamemode", 0); + gsweb_show_filter(sock, "numplayers", 1); + gsweb_show_filter(sock, "maxplayers", 1); + gsweb_show_filter_option(sock, "no empty", "numplayers", ">", "0"); + gsweb_show_filter_option(sock, "no full", "numplayers", "<", "maxplayers"); + gsweb_show_filter_option(sock, "no password*", "password", "!=", "1"); + gsweb_show_filter_option(sock, "no punkbuster*", "sv_punkbuster", "!=", "1"); + gsweb_show_filter_option(sock, "punkbuster*", "sv_punkbuster", "==", "1"); + + GSWSEND(GSWFILTER4); +} + + + +void gsw_config_form(int sock, u8 *msg, u8 *name, u8 *value, int input_size) { + tcpspr(sock, + "" + "" + "", + msg, + name, + value, + input_size); +} + + + +void gsw_config_form_game(int sock, int pos) { + u8 tmpquery[GSWQUERYSZ + 1]; + + tcpspr(sock, + "" + "" + ""); +} + + + +void gsweb_config(int sock, u8 *req) { + int i = -1, + saveme = 0; + u8 *p, + *par, + *value; + + if(req) { + p = req; + do { + p = gsw_http_value(p, (u8 *)&par, (u8 *)&value); + if(!value) break; + + if(!strcmp(par, "player")) { + MYDUP(gsw_player, value); + saveme++; + + } else if(!strcmp(par, "game")) { + i = gsw_game_exists(value); + if(i < 0) continue; + + } else if(!strcmp(par, "path")) { + if(i < 0) continue; + MYDUP(gsw_data[i].path, value); + saveme++; + + } else if(!strcmp(par, "query")) { + if(i < 0) continue; + gsw_data[i].query = atoi(value); + saveme++; + + } else if(!strcmp(par, "filter")) { + if(i < 0) continue; + MYDUP(gsw_data[i].filter, value); + saveme++; + + } else if(!strcmp(par, "remove")) { + if(i < 0) continue; + gsw_tot--; + if(i != gsw_tot) { + memmove(&gsw_data[i], &gsw_data[gsw_tot], sizeof(gsw_data_t)); + gsw_sort_game_description(); + } + saveme++; + + } if(!strcmp(par, "maxping")) { + gsw_maxping = atoi(value); + saveme++; + + } if(!strcmp(par, "noping")) { + atoi(value) ? (gsw_noping = 1) : (gsw_noping = 0); + saveme++; + + } if(!strcmp(par, "maxservers")) { + gsw_maxservers = atoi(value); + saveme++; + + } if(!strcmp(par, "font_size")) { + gsw_apply_font(atoi(value)); + saveme++; + + } if(!strcmp(par, "enctype")) { + myenctype = atoi(value); + saveme++; + } + } while(p); + } + + if(saveme) gsweb_saveconf(0); + + GSWSEND(GSWDEFAULT4); + + gsw_config_form(sock, + "Player name", + "player", + gsw_player, + 64); + + for(i = 0; i < gsw_tot; i++) { + gsw_config_form_game(sock, i); + } + + gsw_config_form(sock, + "Max ping timeout in seconds", + "maxping", + myitoa(gsw_maxping), + 10); + + gsw_config_form(sock, + "set to 1 for not showing the unpingable (no replies) servers", + "noping", + myitoa(gsw_noping), + 10); + + gsw_config_form(sock, + "Max servers visualized (0 for no limit)", + "maxservers", + myitoa(gsw_maxservers), + 10); + + gsw_config_form(sock, + "Font size", + "font_size", + myitoa(gsw_font_size), + 10); + + gsw_config_form(sock, + "Enctype: -1 is fast but shows no pings while 1 and 2 show pings but are slower", + "enctype", + myitoa(myenctype), + 10); +} + + + +int gsw_game_cmp(u8 *s1, u8 *s2) { + while(*s1) { + if(*s1 != *s2) return(1); + s1++; + s2++; + } + return(0); +} + + + +int gsw_get_query(u8 *game, FILE *fastfd) { + u8 tmp[FULLSZ + 1], + tmpgame[CNAMELEN + 1], + *p; + + mystrcpy(tmpgame, game, sizeof(tmpgame)); + p = tmpgame + strlen(tmpgame) - 3; + if(!strcmp(p, "ps2")) strcpy(p, "pc"); + + if(gsw_read_cfg_value(fastfd ? NULL : FULLCFG, tmpgame, "serverclass", tmp, sizeof(tmp), fastfd) || + gsw_read_cfg_value(fastfd ? NULL : FULLCFG, tmpgame, "veserverclass", tmp, sizeof(tmp), fastfd)) { + if(!strcmp(tmp, "qr2")) { + return(8); + } else if(!strcmp(tmp, "quake3")) { + return(1); + } else if(!strcmp(tmp, "doom3")) { + return(6); + } else if(!strcmp(tmp, "halflife")) { + return(4); + } else if(!strcmp(tmp, "source")) { + return(9); + } else if(!strcmp(tmp, "tribes2")) { + return(10); + } + } + + /* work-arounds */ + #define W(x,y) if(!gsw_game_cmp(x, tmpgame)) return(y); + + W("closecomftf", 8) + else W("cmr4p", 8) + else W("cmr5p", 8) + else W("halflife", 4) + else W("juiced", 8) + else W("mclub2", 8) + else W("mclub3", 8) + else W("quake3", 1) + else W("racedriver2", 8) + else W("source", 9) + else W("stef1", 1) + + #undef W + + return(0); +} + + + +int gsweb_add_game(u8 *game, u8 *full, u8 *path) { + if(!game || !full) return(-1); + if(!path) path = ""; + if(gsw_game_exists(game) >= 0) return(-1); + + gsw_data = realloc((u8 *)gsw_data, (gsw_tot + 1) * sizeof(gsw_data_t)); + if(!gsw_data) std_err(); /* if the adding fails we will continue */ + memset(&gsw_data[gsw_tot], 0, sizeof(gsw_data_t)); /* to have an allocated buffer for later */ + + MYDUP(gsw_data[gsw_tot].game, game); + MYDUP(gsw_data[gsw_tot].path, path); + MYDUP(gsw_data[gsw_tot].key, ""); + MYDUP(gsw_data[gsw_tot].filter, ""); + +/* + get_key(game, gsw_data[gsw_tot].key, gsw_data[gsw_tot].full); + GAMEKEY is no longer supported + but can be easily re-implemented + in any moment if needed +*/ + + if(full[0]) MYDUP(gsw_data[gsw_tot].full, full); + if(path) MYDUP(gsw_data[gsw_tot].path, path); + gsw_data[gsw_tot].query = gsw_get_query(game, NULL); + gsw_tot++; + + gsw_sort_game_description(); // sorting + return(0); +} + + + +void gsweb_add(int sock, u8 *req, int fast) { + u8 *p, + *par, + *value, + *game = NULL, + *full = NULL, + *path = NULL; + + if(!req) { + gsweb_index(sock); + return; + } + + p = req; + do { + p = gsw_http_value(p, (u8 *)&par, (u8 *)&value); + if(!value) break; + + if(!strcmp(par, "game")) { + game = value; + + } else if(!strcmp(par, "full")) { + full = value; + + } else if(!strcmp(par, "path")) { + path = value; + } + } while(p); + + if(!gsweb_add_game(game, full, path)) { + gsweb_saveconf(0); + } + + if(fast) return; + gsweb_config(sock, NULL); +} + + + +void gsweb_search(int sock, u8 *req) { + FILE *fd, + *fastfd; + u8 buff[GSLISTSZ + 1], + tmpquery[GSWQUERYSZ + 1], + url[FULLSZ + 1], + tmpfull[CFNAMELEN + 1], + *p, + *f, + *par, + *value, + *game; + + fd = gslfopen(GSLISTCFG, "rb"); + if(!fd) { + gsweb_update(sock); + return; + } + + GSWSEND(GSWDEFAULT1); + GSWSEND(GSWSEARCHT); + + if(req) { + p = req; + req = NULL; + do { + p = gsw_http_value(p, (u8 *)&par, (u8 *)&value); + if(!value) break; + + if(!strcmp(par, "q")) { + req = value; // req has a double use here + } + } while(p); + } + + if(!req) goto goto_gsweb_search; + + fastfd = gslfopen(FULLCFG, "rb"); // avoids fopen/fclose + if(!fastfd) { + gsweb_update(sock); + return; + } + + while(fgets(buff, sizeof(buff), fd)) { + if(stristr(buff, req) || !*req) { + zero_equal(buff + CFNAMEEND); + zero_equal(buff + CNAMEEND); + if(buff[CKEYOFF] > ' ') { + buff[CKEYOFF + 6] = 0; + } else { + buff[CKEYOFF] = 0; + } + game = buff + CNAMEOFF; + + if(!gsw_read_cfg_value(NULL, game, "newsurl", url, sizeof(url), fastfd)) { + *url = 0; + } + + p = buff + CFNAMEOFF; + f = tmpfull; + while(*p) { + if(*p == ' ') { + *f = '+'; + } else { + *f = *p; + } + p++; + f++; + } + strcpy(tmpfull, buff + CFNAMEOFF); // required for the spaces + for(p = tmpfull; *p; p++) { + if(*p == ' ') *p = '+'; + } + + tcpspr(sock, + "" + "" + "" + "" + "" + "" + "" + "" + "", + buff + CFNAMEOFF, + game, + buff + CKEYOFF, + game, + tmpfull, + game, + game, + (myenctype < 0) ? (u8 *)"enctypeX" : gsw_calc_query(tmpquery, gsw_get_query(game, fastfd)), + *url ? "O" : ""); + } + } + + fclose(fastfd); + +goto_gsweb_search: + fclose(fd); + GSWSEND(gsw_skin_search); +} + + + +int gsweb_scan_dir(u8 *filedir, int filedirsz, gsw_scan_data_t *list) { + int i, + namelen; + int plen, + ret = -1; + +#ifdef WIN32 + static int winnt = -1; + OSVERSIONINFO osver; + WIN32_FIND_DATA wfd; + HANDLE hFind; + + if(winnt < 0) { + osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osver); + if(osver.dwPlatformId >= VER_PLATFORM_WIN32_NT) { + winnt = 1; + } else { + winnt = 0; + } + } + + plen = strlen(filedir); + strcpy(filedir + plen, "\\*.*"); + plen++; + + if(winnt) { // required to avoid problems with Vista and Windows7! + hFind = FindFirstFileEx(filedir, FindExInfoStandard, &wfd, FindExSearchNameMatch, NULL, 0); + } else { + hFind = FindFirstFile(filedir, &wfd); + } + if(hFind == INVALID_HANDLE_VALUE) return(0); + do { + if(!strcmp(wfd.cFileName, ".") || !strcmp(wfd.cFileName, "..")) continue; + + namelen = strlen(wfd.cFileName); + if((plen + namelen) >= filedirsz) goto quit; + strcpy(filedir + plen, wfd.cFileName); + + if(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if(gsweb_scan_dir(filedir, filedirsz, list) < 0) goto quit; + continue; + } + + if(namelen < 4) continue; + if(stricmp(wfd.cFileName + namelen - 4, ".exe")) continue; + + for(i = 0; list[i].game && list[i].exe; i++) { + if(!stricmp(wfd.cFileName, list[i].exe)) { + if(!quiet) fprintf(stderr, "- %30s %s\n", list[i].game, filedir); + gsweb_add_game(list[i].game, list[i].full, filedir); + break; + } + } + } while(FindNextFile(hFind, &wfd)); + ret = 0; + +quit: + FindClose(hFind); +#else + // not implemented, useless +#endif + filedir[plen - 1] = 0; + return(ret); +} + + + +void gsweb_scan_drive(int sock, u8 *drive) { + gsw_scan_data_t *list; + FILE *fd; + u32 startoff; + int i, + num; + u8 mypath[GSMAXPATH + 1]; + + fd = gslfopen(DETECTCFG, "rb"); + if(!fd) { + gsweb_update(sock); + return; + } + + num = 500; + list = malloc((num + 1) * sizeof(gsw_scan_data_t)); + if(!list) std_err(); + + i = 0; + for(;;) { + startoff = find_config_entry(fd, &list[i].game); + if(!startoff) break; + + if(!read_config_entry( + fd, + startoff, + "findexename", + &list[i].exe)) continue; + + read_config_entry( + fd, + startoff, + "commonname", + &list[i].full); + + if(++i == num) { + num += 250; + list = realloc(list, (num + 1) * sizeof(gsw_scan_data_t)); + if(!list) std_err(); + } + } + + list[i + 1].game = NULL; + list[i + 1].full = NULL; + list[i + 1].exe = NULL; + + mystrcpy(mypath, drive, sizeof(mypath)); + gsweb_scan_dir(mypath, sizeof(mypath), list); + + fclose(fd); + free_gsw_scan_data(list, num + 1); + gsweb_saveconf(0); +} + + + +void gsweb_scan_registry(int sock) { +#ifdef WIN32 + HKEY key, + root; + FILE *fd; + u32 startoff; + int len; + u8 tmpgame[CNAMELEN + 1], + tmpbuff[GSMAXPATH + 1], + *regpath, + *fullname, + *p, + *tmp, + *value, + *save; + + fd = gslfopen(DETECTCFG, "rb"); + if(!fd) { + gsweb_update(sock); + return; + } + + for(;;) { + startoff = find_config_entry(fd, &tmp); + if(!startoff) break; + + mystrcpy(tmpgame, tmp, sizeof(tmpgame)); + FREEX(tmp); + + save = read_config_entry( + fd, + startoff, + "findregkey", + &tmp); + if(!save) continue; + + p = strchr(save, '\\'); + if(p) *p = 0; + if(!strcmp(save, "HKEY_CLASSES_ROOT")) { + root = HKEY_CLASSES_ROOT; + } else if(!strcmp(save, "HKEY_CURRENT_USER")) { + root = HKEY_CURRENT_USER; + } else if(!strcmp(save, "HKEY_LOCAL_MACHINE")) { + root = HKEY_LOCAL_MACHINE; + } else if(!strcmp(save, "HKEY_USERS")) { + root = HKEY_USERS; + } else if(!strcmp(save, "HKEY_PERFORMANCE_DATA")) { + root = HKEY_PERFORMANCE_DATA; + } else if(!strcmp(save, "HKEY_CURRENT_CONFIG")) { + root = HKEY_CURRENT_CONFIG; + } else if(!strcmp(save, "HKEY_DYN_DATA")) { + root = HKEY_DYN_DATA; + } else { + continue; + } + + save = p + 1; + value = strrchr(save, '\\'); + if(!value) continue; + *value++ = 0; + + len = RegOpenKeyEx(root, save, 0, KEY_READ, &key); + FREEX(tmp); + if(len) continue; + + len = sizeof(tmpbuff) - 1; + if(!RegQueryValueEx(key, value, NULL, NULL, tmpbuff, (void *)&len)) { + tmpbuff[len - 1] = '\\'; + + read_config_entry( + fd, + startoff, + "addtoregpath", + ®path); + + if(regpath) { + read_config_entry( + fd, + startoff, + "commonname", + &fullname); + + if(fullname) { + len += mystrcpy(tmpbuff + len, regpath, sizeof(tmpbuff) - len); + gsw_substitute(tmpbuff, len, "\\\\", "\\", 0); + gsweb_add_game(tmpgame, fullname, tmpbuff); + FREEX(fullname); + } + + FREEX(regpath); + } + } + + RegCloseKey(key); + } + + fclose(fd); + gsweb_saveconf(0); +#endif +} + + + +void gsweb_scan_games(int sock, u8 *req) { +#ifdef WIN32 + int i, + drives; + u8 drvstr[4], + *p, + *par, + *value; + + if(!req) { + drives = GetLogicalDrives(); + + GSWSEND(GSWDEFAULT3); + GSWSEND( + "" + "registry
"); + + for(i = 0; i < 26; i++) { + if(drives & (1 << i)) { + sprintf(drvstr, "%c:\\", i + 'a'); + if(GetDriveType(drvstr) != DRIVE_FIXED) continue; + tcpspr(sock, + "%s
", + drvstr, drvstr); + } + } + + GSWSEND(""); + return; + } + + p = req; + do { + p = gsw_http_value(p, (u8 *)&par, (u8 *)&value); + if(!value) break; + + if(!strcmp(par, "registry")) { + if(atoi(value)) gsweb_scan_registry(sock); + + } else if(!strcmp(par, "drive")) { + if(*value) gsweb_scan_drive(sock, value); + + } + } while(p); + +#endif + + gsweb_config(sock, NULL); +} + + + +void gsweb_about(int sock) { + GSWSEND(GSWDEFAULT3); + tcpspr(sock, + gsw_skin_about, + gslist_path, gslist_path); + + GSWSEND(gsw_skin_games); + tcpspr(sock, + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
%s
%s" + "" + "
country" + "" + "
" + "%s" + "
" + "" + "" + "
" + "
" // useful + "
" + "
" // useful + "
%s
" + "
(%s)
" + "
" + "gamename
" + " executable's path
" + " find the game's executable and paste the link in the path entry above!
" + "%s query
", + gsw_data[pos].full, + gsw_data[pos].game, + gsw_data[pos].game, + gsw_data[pos].path, + gsw_calc_query(tmpquery, gsw_data[pos].query)); + + if(gsw_filter && gsw_filter[0]) { + tcpspr(sock, + " apply the current temporary filter
", + gsw_filter); + } + if(gsw_data[pos].filter && gsw_data[pos].filter[0]) { + tcpspr(sock, + " filter currently in use for this game
" + " reset the game's filter
", + gsw_data[pos].filter); + } else { + tcpspr(sock, + " no custom filter currently in use for this game, go in Filter first
"); + } + tcpspr(sock, + " remove this game entry from Gslist
" + " confirm the changes for this game entry" + "
" + "
" // useful + "
%s%s%sOO" + "
" + "" + "%s" + "" + "
%s%s%s
add favoriteIP:PORThost namemap namegame typeplayers/max_playersversionmod or gamededicatedpasswordpunkbusterranked or ratedgame modecountryquery portping ms
" + "" + "" + "" + "" + "" + "" + "" + "" + "
OKthe server has no password, it's not full or empty... a perfect server where playing
CAN JOIN BUT...the server is empty or uses a password
CAN'T JOINthe server is full or there are no players and it's protected by password too
PUNKBUSTERused only to highlight the pb field for the servers which use PunkBuster
" + "" + ""); +} + + + +void gsweb_kick(int sock) { + GSWSEND(GSWDEFAULT3); + GSWSEND(GSWKICK); +} + + + +quick_thread(client, int sock) { + int t, + len, + localip = 0; + u8 buff[1024], /* enough for any parameter */ + *req, + *p, + *l = NULL; + + if(sock < 0) { /* local IP: negative = local IP */ + sock = -sock; + localip = 1; + } + + p = buff; + len = sizeof(buff) - 1; + do { + t = recv(sock, p, len, 0); + if(t < 0) goto client_quit; + if(!t) break; + p += t; + *p = 0; + l = strchr(buff, '\n'); + } while(!l && (len -= t)); + + if(!l) goto client_quit; + + gsw_http2chr(buff, l - buff); /* no XSS checks */ + + req = strchr(buff, ' '); + if(req) { + *req++ = 0; + } else { + req = buff; + } + + p = strrchr(req, ' '); + if(p) *p = 0; + + if(!localip && p && !quiet) { + fprintf(stderr, " %s\n", req); + } + + if(*req == '/') req++; /* req is the request, like /index */ + p = strchr(req, '?'); + if(p) *p++ = 0; /* p are the parameters */ + + GSWSEND(GSWHTTP); + GSWSEND(gsw_skin_head); + GSWSEND(gsw_skin_window); + + if(!strcmp(req, "list")) { + gsweb_list(sock, p); + + } else if(!strcmp(req, "join")) { + gsweb_join(sock, p); + + } else if(!strcmp(req, "fav")) { + localip ? gsweb_fav(sock, p) : gsweb_kick(sock); + + } else if(!strcmp(req, "play")) { + gsweb_play(sock, p, localip); + + } else if(!strcmp(req, "filter")) { + localip ? gsweb_set_filter(sock, p) : gsweb_kick(sock); + + } else if(!strcmp(req, "config")) { + localip ? gsweb_config(sock, p) : gsweb_kick(sock); + + } else if(!strcmp(req, "add")) { + localip ? gsweb_add(sock, p, 0) : gsweb_kick(sock); + + } else if(!strcmp(req, "search")) { + gsweb_search(sock, p); + + } else if(!strcmp(req, "scan")) { + localip ? gsweb_scan_games(sock, p) : gsweb_kick(sock); + + } else if(!strcmp(req, "about")) { + gsweb_about(sock); + + } else if(!strcmp(req, "update")) { + localip ? gsweb_update(sock) : gsweb_kick(sock); + + } else if(!strcmp(req, "update_aluigi")) { + localip ? gsweb_update_aluigi(sock) : gsweb_kick(sock); + + } else if(!strcmp(req, "quit")) { + if(localip) { + GSWSEND(gsw_skin_end); + fputs("- quit Gslist\n", stderr); + shutdown(sock, 2); + close(sock); + if(gsw_data) free_gsw_data(gsw_data, gsw_tot); + if(gsw_fav_data) free_gsw_fav_data(gsw_fav_data, gsw_fav_tot); +#ifdef WINTRAY + Shell_NotifyIcon(NIM_DELETE, &gslist_tray); +#endif + exit(0); + } + gsweb_kick(sock); + + } else { + gsweb_index(sock); + } + + GSWSEND(gsw_skin_end); + +client_quit: + shutdown(sock, 2); + close(sock); + return(0); +} + + + +void gsweb(in_addr_t ip, u16 port) { + in_addr_t gsw_localip; + struct sockaddr_in peer; + int sdl, + sda, + //on = 1, + psz; + + sdl = tcpsocket(); +// if(setsockopt(sdl, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) +// < 0) std_err(); + while(port) { + peer.sin_addr.s_addr = ip; + peer.sin_port = htons(port); + peer.sin_family = AF_INET; + if(!bind(sdl, (struct sockaddr *)&peer, sizeof(struct sockaddr_in))) break; +#ifdef WINTRAY + MessageBox(0, "the port used by Gslistweb is already occupied\nprobably it's already running, check your system tray", "Gslist", MB_OK | MB_ICONERROR); +#else + fprintf(stderr, "\nError: the specified port is already occupied\n"); +#endif + exit(1); + port++; + } + if(!port) std_err(); + if(listen(sdl, SOMAXCONN) + < 0) std_err(); + + gsweb_initconf(); + gsweb_loadconf(); + + fprintf(stderr, "\n Gslist web interface ready\n"); + if(peer.sin_addr.s_addr == htonl(INADDR_ANY)) { + gsw_localip = resolv("127.0.0.1"); + fprintf(stderr, " Now connect to port %hu\n", port); + } else { + gsw_localip = ip; + fprintf(stderr, " Now connect to http://%s:%hu\n", + inet_ntoa(peer.sin_addr), port); + } + if(gsw_admin_IP) gsw_localip = resolv(gsw_admin_IP); + +#ifdef WINTRAY + quick_threadx(gsweb_tray, &peer); +#endif + + for(;;) { + psz = sizeof(struct sockaddr_in); + sda = accept(sdl, (struct sockaddr *)&peer, &psz); + if(sda < 0) std_err(); + + if(peer.sin_addr.s_addr == gsw_localip) { + sda = -sda; + } else if(!quiet) { + fprintf(stderr, " %s:%hu ", + inet_ntoa(peer.sin_addr), + ntohs(peer.sin_port)); + } + + quick_threadx(client, (void *)sda); + } + + close(sdl); +} + + diff --git a/gslist/src/gswskin.h b/gslist/src/gswskin.h new file mode 100644 index 0000000..7644fb6 --- /dev/null +++ b/gslist/src/gswskin.h @@ -0,0 +1,169 @@ +/* + +Built-in "puzzle" interface for gslist webgui + +*/ + +#define GSWKICK "This option can be used only by the local server interface (like 127.0.0.1)
" +#define GSWUPDATE1 "Update in progress, wait some seconds...
" +#define GSWUPDATE2 "Update complete!" +#define GSWUPDATE3 "Update FAILED! Anyway your game database has not been modified
" \ + "
" \ + "You can automatically get the latest pre-built files from aluigi.org" +#define GSWDEF(x,y) "" \ + "" \ + "" \ + y +#define GSWDEFAULT1 GSWDEF(" width=\"100%\"", "") +#define GSWDEFAULT2 GSWDEF("", "") +#define GSWDEFAULT3 GSWDEF(" width=\"100%\"", "" \ + "" \ + "" +#define GSWSEARCHT "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" + +#define GSWFONTCOL "000060" +#define GSWBORDER "d0d0d8" + +u8 gsw_skin_head[] = + "" + "" + "" + "Gslist" + "" + "" + "" + ""; + +u8 gsw_skin_window[] = + /* container */ + "
") +#define GSWDEFAULT4 GSWDEF(" width=\"100%\"", "") +#define GSWFILTER1 "" \ + "This template will help you to build a perfect temporary filter which will be lost when you will close Gslist
" \ + "Go in Config for using and storing this filter for a specific game, otherwise this filter will be NOT used
" \ + "
" \ + "Wildcard character: %
" \ + "
" +#define GSWFILTER2 "
" \ + "
" +#define GSWFILTER3 "" \ + "
" +#define GSWFILTER4 " <= click here for generating the filter with the parameters below:" \ + "" +#define GSWFILTERX "
" \ + "" \ + "
DescriptionGamenameGamekeyadd
game
list
only
ping
list
game
info
" + "" + "" + "" + "" + "" + "
" + /* menu */ + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
MainIPFavConfigDBScanFilterUpdateAboutQuit
"; + +u8 gsw_skin_games[] = + "
" + /* servers */ + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + +u8 gsw_skin_search[] = + "
favIPnamemaptypeplayersvermoddppbrmodecqpping
" + "
" + "" + "" + "" + ""; + +u8 gsw_skin_end[] = + "
" + "
" + "" + "" + "
" + "
" + "
" + "" + ""; + +u8 gsw_skin_about[] = + "
" + "Gslist "VER"
" + "by Luigi Auriemma
" + "e-mail: aluigi@autistici.org
" + "web: aluigi.org
" + "Homepage
" + "
" + "Configuration folder: %s
" + "
" + "Some tips
" + "press F11 for full screen visualization (it seems a game station!)
" + "press Ctrl and F for searching words in the screens
" + "browsers support bookmarks, use this feature when you need it
" + "enable the servers limitations in the Configuration screen: unpingable and max servers
" + "go on "Update" when you have a recent new game that is not included in your list
" + "use "Filter" for easily create a temporary filter
" + "the filter can be saved in the configuration file ONLY through the "Configure" menu
" + "go on "Scan" everytime you install a new game
" + "the Back and Forward button (arrows) of your browser exist to be used, remember it...
" + "same story for the Refresh button, it is very useful if you want to reping the servers
" + "press Ctrl and T for opening another browser tab, sometimes useful for multiple games
" + "

" + "Many thanx to all the people that have helped me to improve this tool and also
" + "to all the others that use it and find it useful or interesting.
" + "If you have ideas for improving Gslist you are welcome, send me a mail!
" + "

" + "the following is an explanation of the fields visualized by this webGUI and their colors:
"; + diff --git a/gslist/src/gswtray.h b/gslist/src/gswtray.h new file mode 100644 index 0000000..2d06229 --- /dev/null +++ b/gslist/src/gswtray.h @@ -0,0 +1,108 @@ +/* + +by Luigi Auriemma + +*/ + +HINSTANCE gslist_hInstance; +NOTIFYICONDATA gslist_tray; +u8 gslist_tray_run[64]; +#define gslist_tray_run_execute ShellExecute(NULL, "open", gslist_tray_run, NULL, NULL, SW_SHOW) + +#define ID_MYICON WM_USER + 1 +#define WM_TRAY_NOTIFY WM_USER + 2 +#define IDR_POPUP_MENU WM_USER + 3 +#define ID_APP_RELOAD WM_USER + 4 +#define ID_APP_ABOUT WM_USER + 5 +#define ID_APP_EXIT WM_USER + 6 + + + +int gslist_tray_menu(HWND hWnd) { + POINT pt; + HMENU hMenu, + hPopupMenu; + int ret; + + hMenu = LoadMenu(gslist_hInstance, MAKEINTRESOURCE(IDR_POPUP_MENU)); + hPopupMenu = GetSubMenu(hMenu, 0); + SetMenuDefaultItem(hPopupMenu, -1, TRUE); + + GetCursorPos(&pt); + SetForegroundWindow(hWnd); + ret = TrackPopupMenu(hPopupMenu, TPM_RETURNCMD | TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, NULL); + DestroyMenu(hPopupMenu); + DestroyMenu(hMenu); + return(ret); +} + + + +LRESULT CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { + switch(message) { + case WM_CREATE: + return(TRUE); + case WM_DESTROY: + gslist_tray.cbSize = sizeof(NOTIFYICONDATA); + gslist_tray.hWnd = hwnd; + Shell_NotifyIcon(NIM_DELETE, &gslist_tray); + PostQuitMessage(0); + exit(0); + return(TRUE); + case WM_TRAY_NOTIFY: + if((lParam == WM_RBUTTONUP) || (lParam == WM_LBUTTONUP)) { + switch(gslist_tray_menu(hwnd)) { + case ID_APP_RELOAD: gslist_tray_run_execute; break; + case ID_APP_ABOUT: MessageBox(0, GSTITLE, "Gslist", MB_OK | MB_ICONINFORMATION); break; + case ID_APP_EXIT: SendMessage(hwnd, WM_DESTROY, 0, 0); break; + default: break; + } + } + return(TRUE); + default: break; + } + return(DefWindowProc(hwnd, message, wParam, lParam)); +} + + + +quick_thread(gsweb_tray, struct sockaddr_in *peer) { + HWND hwnd; + WNDCLASSEX wc; + MSG msg; + char classname[] = "Gslist.NOTIFYICONDATA.hWnd"; + + memset(&wc, 0, sizeof(wc)); + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = wndProc; + wc.hInstance = gslist_hInstance; + wc.lpszClassName = classname; + wc.hIcon = LoadIcon(gslist_hInstance, MAKEINTRESOURCE(ID_MYICON)); + wc.hIconSm = LoadImage(gslist_hInstance, MAKEINTRESOURCE(ID_MYICON), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + RegisterClassEx(&wc); + + hwnd = CreateWindowEx(0, classname, classname, WS_POPUP, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, gslist_hInstance, NULL); + + memset(&gslist_tray, 0, sizeof(gslist_tray)); + gslist_tray.cbSize = sizeof(gslist_tray); + gslist_tray.hWnd = hwnd; + gslist_tray.uID = 1; + gslist_tray.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; + gslist_tray.uCallbackMessage = WM_TRAY_NOTIFY; + gslist_tray.hIcon = LoadImage(gslist_hInstance, MAKEINTRESOURCE(ID_MYICON), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + strcpy(gslist_tray.szTip, "Gslist "VER); + Shell_NotifyIcon(NIM_ADD, &gslist_tray); + + sprintf(gslist_tray_run, "http://%s:%hu", inet_ntoa(peer->sin_addr), ntohs(peer->sin_port)); + gslist_tray_run_execute; + + while(GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return(0); +} + + diff --git a/gslist/src/multi_query.h b/gslist/src/multi_query.h new file mode 100644 index 0000000..3cd3868 --- /dev/null +++ b/gslist/src/multi_query.h @@ -0,0 +1,977 @@ +/* + Copyright 2005-2011 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl-2.0.txt +*/ + +#define QUAKE3_QUERY "\xff\xff\xff\xff" "getstatus xxx\n" +#define QUAKE3_QUERY2 "\xff\xff\xff\xff" "getinfo xxx\n" +#define MOH_QUERY "\xff\xff\xff\xff" "\x02" "getstatus xxx\n" +#define QUAKE2_QUERY "\xff\xff\xff\xff" "status" +#define HALFLIFE_QUERY "\xff\xff\xff\xff" "infostring\n\0" +#define DPLAY8_QUERY "\x00\x02" /* info/join */ \ + "\xff\xff" /* ID tracker */ \ + "\x02" /* 01 = need GUID, 02 = no GUID */ +#define DOOM3_QUERY "\xff\xff" "getInfo\0" "\0\0\0\0" +#define ASE_QUERY "s" +#define GS1_QUERY "\\status\\" +#define GS1_QUERY2 "\\info\\" +#define GS2_QUERY "\xfe\xfd\x00" "\x00\x00\x00\x00" "\xff\x00\x00" "\x00" // 1 for split? +#define GS3_QUERY "\xfe\xfd\x09" "\x00\x00\x00\x00" +#define GS3_QUERYX "\xfe\xfd\x00" "\x00\x00\x00\x00" "\x00\x00\x00\x00" "\xff\x00\x00" "\x00" // 1 for split? +#define GS3_QUERYX_PL "\xfe\xfd\x00" "\x00\x00\x00\x00" "\x00\x00\x00\x00" "\x00\xff\xff" "\x00" // 1 for split? +#define SOURCE_QUERY "\xff\xff\xff\xff" "T" "Source Engine Query\0" +#define TRIBES2_QUERY "\x12\x02\x01\x02\x03\x04" + +#define GSLIST_QUERY_T(X) int (X)(u8 *data, int len, generic_query_data_t *gqd) +#define GSLIST_QUERY_PAR_T(X) int (X)(u8 *data, int skip) +#define GSLIST_QUERY_VAL_T(X) int (X)(u8 *data, int ipbit, ipdata_t *ipdata) + + + +/* +stdout for any information +fdout only for the megaquery scanning +*/ + + + + +void show_unicode(u8 *data, u32 size) { + u8 *limit = data + size; + + while(*data && (data < limit)) { + fputc(*data, stdout); + data += 2; + } + fputc('\n', stdout); +} + + + +GSLIST_QUERY_PAR_T(handle_query_par) { + u8 *p; + + p = strchr(data, '_'); + if(p) data = p + 1; /* "sv_" and "si_" */ + +#define K(x) !stricmp(data, x) + if(!(skip & 2) && ( + K("hostname") || K("name"))) { + return(1); + + } else if(!(skip & 4) && ( + K("mapname") || K("map"))) { + return(2); + + } else if(!(skip & 8) && ( + K("gametype") || K("gametypeLocal"))) { + return(3); + + } else if(!(skip & 16) && ( + K("numplayers") || K("clients") || K("privateClients"))) { + return(4); + + } else if(!(skip & 32) && ( + K("maxplayers") || K("maxclients"))) { + return(5); + + } else if(!(skip & 64) && ( + K("gamever") || K("version") || K("protocol"))) { + return(6); + + } else if(!(skip & 128) && ( + K("password") || K("needpass") || K("usepass") || K("passworden") || K("pwd") || K("pswrd") || K("pw"))) { + return(7); + + } else if(!(skip & 256) && ( + K("dedicated") || K("type") || K("ded") || K("dedic") || K("serverDedicated"))) { + return(8); + + } else if(!(skip & 512) && ( + K("hostport"))) { + return(9); + + } else if(!(skip & 1024) && ( + K("game") || K("gamedir") || K("modname") || K("mods") || K("mod"))) { + return(10); + + } else if(!(skip & 2048) && ( + K("pb") || K("punkbuster"))) { + return(11); + + } else if(!(skip & 4096) && ( + K("gamemode"))) { + return(12); + + } else if(!(skip & 8192) && ( + K("ranked") || K("rated"))) { + return(13); + } + +#undef K + return(0); +} + + + +GSLIST_QUERY_VAL_T(handle_query_val) { + int ret = 0; + + if(!*data) return(-1); + switch(ipbit) { + case 1: ipdata->name = mychrdup(data); break; + case 2: ipdata->map = mychrdup(data); break; + case 3: ipdata->type = mychrdup(data); break; + case 4: if(data[0]) ipdata->players = atoi(data); break; + case 5: if(data[0]) ipdata->max = atoi(data); break; + case 6: ipdata->ver = mychrdup(data); break; + case 7: if(data[0]) ipdata->pwd = *data; break; + case 8: if(data[0]) ipdata->ded = *data; break; + case 9: if(data[0]) ipdata->port = htons(atoi(data)); break; + case 10: ipdata->mod = mychrdup(data); break; + case 11: if(data[0]) ipdata->pb = *data; break; + case 12: ipdata->mode = mychrdup(data); break; + case 13: if(data[0]) ipdata->rank = *data; break; + default: ret = -1; break; + } + return(ret); +} + + + +GSLIST_QUERY_PAR_T(print_query_par) { + printf("%28s ", data); + return(0); +} + + + +GSLIST_QUERY_VAL_T(print_query_val) { + printf("%s\n", data); + return(0); +} + + + +GSLIST_QUERY_T(generic_query) { + GSLIST_QUERY_PAR_T(*par); /* the usage of function pointers */ + GSLIST_QUERY_VAL_T(*val); /* avoids to use many if() */ + static const u8 empty_par[] = ""; + ipdata_t *ipdata; + u32 chall; + int ipbit = 0, + skip = 0, + nt, + datalen = 0, + first = 0, + pars = 0, + parn; + u8 minime[32], + *next, + *tmp, + *extra_data, + *limit, + *next_backup, + *parz[8]; + + if((gqd->type == 11) && (len < 20)) { // Gamespy 3 + chall = atoi(data + 5); + memcpy(minime, GS3_QUERYX, sizeof(GS3_QUERYX) - 1); + minime[7] = chall >> 24; + minime[8] = chall >> 16; + minime[9] = chall >> 8; + minime[10] = chall; + sendto(gqd->sock, minime, sizeof(GS3_QUERYX) - 1, 0, (struct sockaddr *)gqd->peer, sizeof(struct sockaddr_in)); + return(-1); + } + if((gqd->type == 14) && (len < 20)) { // Gamespy 3 players + chall = atoi(data + 5); + memcpy(minime, GS3_QUERYX_PL, sizeof(GS3_QUERYX_PL) - 1); + minime[7] = chall >> 24; + minime[8] = chall >> 16; + minime[9] = chall >> 8; + minime[10] = chall; + sendto(gqd->sock, minime, sizeof(GS3_QUERYX_PL) - 1, 0, (struct sockaddr *)gqd->peer, sizeof(struct sockaddr_in)); + return(-1); + } + + limit = data + len - gqd->rear; + *limit = 0; + data += gqd->front; + nt = gqd->nt; + + if(gqd->ipdata) { + ipdata = &gqd->ipdata[gqd->pos]; + par = handle_query_par; + val = handle_query_val; + } else { + ipdata = NULL; + par = print_query_par; + val = print_query_val; + } + + if(gqd->scantype == QUERY_SCANTYPE_SINGLE) { // \par\value\par\value\... + datalen = strlen(gqd->data); + first = 1; // lame compatibility fix + } + + if(gqd->type == 14) { + for(next = data; (data < limit) && next; data = next + 1) { + next = strchr(data, gqd->chr); + if(next) *next = 0; + if(!data[0]) break; + if(pars < 8) parz[pars] = data; + pars++; + } + } + + parn = 0; + extra_data = NULL; + for(next = data; (data < limit) && next; data = next + 1, nt++) { + next_backup = next; + next = strchr(data, gqd->chr); + if(next) *next = 0; + + if(pars > 0) { + if(!(nt & 1)) { + if(parn >= pars) { + parn = 0; + if(gqd->scantype != QUERY_SCANTYPE_SINGLE) printf("\n"); //printf("%28s\n", "."); + } + // gqd->data is allocated with par&val, so we can't use it + data = empty_par; + if(gqd->scantype != QUERY_SCANTYPE_SINGLE) { + if(parn < 8) data = parz[parn]; + } + next = next_backup; + parn++; + } + } + + /* work-around for Quake games with players at end, only for -Q */ + if(gqd->ipdata) { + if(nt & 1) { + if(skip && (gqd->chr != '\n')) { + for(tmp = data; *tmp; tmp++) { + if(*tmp != '\n') continue; + *tmp = 0; + next = limit; + break; + } + } + } else { + skip |= (1 << ipbit); + } + } + + if(gqd->scantype == QUERY_SCANTYPE_SINGLE) { + if(first) { + first = 0; + if(strchr(data, '\n') || !*data) continue; + } + if(nt & 1) { + if(!extra_data) { + extra_data = strchr(data, '\n'); + if(extra_data) { + *extra_data++ = 0; + if(!*extra_data) extra_data = NULL; + } + } else { + tmp = strchr(data, '\n'); + if(tmp) *tmp = 0; // don't handle other '\n' + } + } else { + if((!*data && (data != empty_par)) || !strcmp(data, "queryid") || !strcmp(data, "final")) break; + } + enctypex_data_cleaner(data, data, -1); // format + datalen += sprintf(gqd->data + datalen, "\\%s", data); + if(extra_data) { + data = extra_data; + for(tmp = data; *tmp; tmp++) { + if(*tmp == '/') *tmp = '|'; // avoids the / delimiter used here to delimit players + if(*tmp == '\n') *tmp = '/'; + } + enctypex_data_cleaner(data, data, -1); // format + datalen += sprintf(gqd->data + datalen, "\\extra_data\\%s", data); + // leave extra_data declared to avoid overflows caused by multiple '\n' + } + continue; + } + + if(nt & 1) { + val(data, ipbit, ipdata); + ipbit = 0; + } else { + if((!*data && (data != empty_par)) || !strcmp(data, "queryid") || !strcmp(data, "final")) break; + ipbit = par(data, skip); + skip |= (1 << ipbit); + } + } + + return(0); +} + + + +GSLIST_QUERY_T(source_query) { + ipdata_t *ipdata; + + data += 5; + if(gqd->ipdata) { + ipdata = &gqd->ipdata[gqd->pos]; + + data += strlen(data) + 1; + ipdata->name = mychrdup(data); data += strlen(data) + 1; + ipdata->map = mychrdup(data); data += strlen(data) + 1; + ipdata->mod = mychrdup(data); data += strlen(data) + 1; + ipdata->type = mychrdup(data); data += strlen(data) + 1; + ipdata->players = *data++; + ipdata->max = *data++; + ipdata->ver = malloc(6); + sprintf(ipdata->ver, "%hu", *data++); + ipdata->ded = (*data++ == 'd') ? '1' : '0'; + data++; + ipdata->pwd = *data; + + } else { + printf("%28s %s\n", "Address", data); data += strlen(data) + 1; + printf("%28s %s\n", "Hostname", data); data += strlen(data) + 1; + printf("%28s %s\n", "Map", data); data += strlen(data) + 1; + printf("%28s %s\n", "Mod", data); data += strlen(data) + 1; + printf("%28s %s\n", "Description", data); data += strlen(data) + 1; + printf("%28s %u/%u\n", "Players", data[0], data[1]); data += 2; + printf("%28s %u\n", "Version", *data++); + printf("%28s %c\n", "Server type", *data++); + printf("%28s %c\n", "Server OS", *data++); + printf("%28s %u\n", "Password", *data++); + printf("%28s %u\n", "Secure server", *data); + } + return(0); +} + + + +GSLIST_QUERY_T(tribes2_query) { + ipdata_t *ipdata; + int sz, + tmp; + +#define TRIBES2STR(x) sz = *data++; \ + tmp = data[sz]; \ + data[sz] = 0; \ + x; \ + data += sz; \ + *data = tmp; + + data += 6; + if(gqd->ipdata) { + ipdata = &gqd->ipdata[gqd->pos]; + + TRIBES2STR((ipdata->mod = mychrdup(data))); + TRIBES2STR((ipdata->type = mychrdup(data))); + TRIBES2STR((ipdata->map = mychrdup(data))); + data++; + ipdata->players = *data++; + ipdata->max = *data++; + + } else { + TRIBES2STR(printf("%28s %s\n", "Mod", data)); + TRIBES2STR(printf("%28s %s\n", "Game type", data)); + TRIBES2STR(printf("%28s %s\n", "Map", data)); + data++; + printf("%28s %u/%u\n", "Players", data[0], data[1]); data += 2; + printf("%28s %u\n", "Bots", *data); data++; + printf("%28s %hu\n", "CPU", *(int16_t *)data); data += 2; + TRIBES2STR(printf("%28s %s\n", "Info", data)); + } +#undef TRIBES2STR + return(0); +} + + + +GSLIST_QUERY_T(dplay8info) { + struct myguid { + u32 g1; + u16 g2; + u16 g3; + u8 g4; + u8 g5; + u8 g6; + u8 g7; + u8 g8; + u8 g9; + u8 g10; + u8 g11; + }; + struct dplay8_t { + u32 pcklen; + u32 reservedlen; + u32 flags; + u32 session; + u32 max_players; + u32 players; + u32 fulldatalen; + u32 desclen; + u32 dunno1; + u32 dunno2; + u32 dunno3; + u32 dunno4; + u32 datalen; + u32 appreservedlen; + struct myguid instance_guid; + struct myguid application_guid; + } *dplay8; + + if(len < (sizeof(struct dplay8_t) + 4)) { + printf("\nError: the packet received seems invalid\n"); + return(0); + } + + dplay8 = (struct dplay8_t *)(data + 4); + + if(dplay8->session) { + fputs("\nSession options: ", stdout); + if(dplay8->session & 1) fputs("client-server ", stdout); + if(dplay8->session & 4) fputs("migrate_host ", stdout); + if(dplay8->session & 64) fputs("no_DPN_server ", stdout); + if(dplay8->session & 128) fputs("password ", stdout); + } + + printf("\n" + "Max players %u\n" + "Current players %u\n", + dplay8->max_players, + dplay8->players); + + printf("\n" + "Instance GUID %08x-%04hx-%04hx-%02x%02x-%02x%02x%02x%02x%02x%02x", + dplay8->instance_guid.g1, + dplay8->instance_guid.g2, + dplay8->instance_guid.g3, + dplay8->instance_guid.g4, + dplay8->instance_guid.g5, + dplay8->instance_guid.g6, + dplay8->instance_guid.g7, + dplay8->instance_guid.g8, + dplay8->instance_guid.g9, + dplay8->instance_guid.g10, + dplay8->instance_guid.g11); + + printf("\n" + "Application GUID %08x-%04hx-%04hx-%02x%02x-%02x%02x%02x%02x%02x%02x\n", + dplay8->application_guid.g1, + dplay8->application_guid.g2, + dplay8->application_guid.g3, + dplay8->application_guid.g4, + dplay8->application_guid.g5, + dplay8->application_guid.g6, + dplay8->application_guid.g7, + dplay8->application_guid.g8, + dplay8->application_guid.g9, + dplay8->application_guid.g10, + dplay8->application_guid.g11); + + // removed any security check here + + fputs("\nGame description/Session name:\n\n ", stdout); + show_unicode( + data + 4 + dplay8->fulldatalen, + len - dplay8->fulldatalen); + + if(dplay8->appreservedlen) { + printf("\nHexdump of the APPLICATION RESERVED data (%u) sent by the server:\n\n", + dplay8->appreservedlen); + show_dump( + data + 4 + dplay8->datalen, + dplay8->appreservedlen, + stdout); + } + + if(dplay8->reservedlen) { + printf("\nHexdump of the RESERVED data (%u) sent by the server:\n\n", + dplay8->reservedlen); + show_dump( + data + 4 + dplay8->fulldatalen + dplay8->desclen, + dplay8->reservedlen, + stdout); + } + + return(0); +} + + + +GSLIST_QUERY_T(ase_query) { + int num = 0; + u8 *limit; + + if(memcmp(data, "EYE1", 4)) { + fwrite(data + 1, len - 1, 1, stdout); + fputc('\n', stdout); + return(0); + } + limit = data + len; + data += 4; + + while((data < limit) && (*data > 1)) { + switch(num) { + case 0: printf("\n%28s ", "Running game"); break; + case 1: printf("\n%28s ", "Game port"); break; + case 2: printf("\n%28s ", "Server name"); break; + case 3: printf("\n%28s ", "Battle mode"); break; + case 4: printf("\n%28s ", "Map name"); break; + case 5: printf("\n%28s ", "Version"); break; + case 6: printf("\n%28s ", "Password"); break; + case 7: printf("\n%28s ", "Players"); break; + case 8: printf("\n%28s ", "Max players"); break; + default: { + if(num & 1) fputc('\n', stdout); + } break; + } + fwrite(data + 1, *data - 1, 1, stdout); + data += *data; + num++; + } + + fputc('\n', stdout); + num = limit - data; + if(num > 1) { + fputs("\nHex dump of the rest of the packet:\n", stdout); + show_dump(data, num, stdout); + } + return(0); +} + + + +u8 *switch_type_query(int type, int *querylen, generic_query_data_t *gqd, GSLIST_QUERY_T(**func), int info) { + u8 *query, + *msg; + + if(gqd) memset(gqd, 0, sizeof(generic_query_data_t)); + +#define ASSIGN(t,x,y,n,c,f,r,z) { \ + msg = x; \ + query = y; \ + if(querylen) *querylen = sizeof(y) - 1; \ + if(gqd) { \ + memset(gqd, 0, sizeof(generic_query_data_t)); \ + gqd->type = t; \ + gqd->nt = n; \ + gqd->chr = c; \ + gqd->front = f; \ + gqd->rear = r; \ + } \ + if(func) *func = z; \ + } + + switch(type) { + case 0: ASSIGN(type, "Gamespy \\status\\", GS1_QUERY, 1, '\\', 0, 0, generic_query) break; + case 1: ASSIGN(type, "Quake 3 engine", QUAKE3_QUERY, 1, '\\', 4, 0, generic_query) break; + case 2: ASSIGN(type, "Medal of Honor", MOH_QUERY, 1, '\\', 5, 0, generic_query) break; + case 3: ASSIGN(type, "Quake 2 engine", QUAKE2_QUERY, 1, '\\', 4, 0, generic_query) break; + case 4: ASSIGN(type, "Half-Life", HALFLIFE_QUERY, 1, '\\', 23, 0, generic_query) break; + case 5: ASSIGN(type, "DirectPlay 8", DPLAY8_QUERY, 0, '\0', 0, 0, dplay8info) break; + case 6: ASSIGN(type, "Doom 3 engine", DOOM3_QUERY, 0, '\0', 23, 8, generic_query) break; + case 7: ASSIGN(type, "All-seeing-eye", ASE_QUERY, 0, '\0', 0, 0, ase_query) break; + case 8: ASSIGN(type, "Gamespy 2", GS2_QUERY, 0, '\0', 5, 0, generic_query) break; + case 9: ASSIGN(type, "Source", SOURCE_QUERY, 0, '\0', 0, 0, source_query) break; + case 10: ASSIGN(type, "Tribes 2", TRIBES2_QUERY, 0, '\0', 0, 0, tribes2_query) break; + case 11: ASSIGN(type, "Gamespy 3", GS3_QUERY, 0, '\0', 5, 0, generic_query) break; + case 12: ASSIGN(type, "Gamespy \\info\\", GS1_QUERY2, 1, '\\', 0, 0, generic_query) break; + case 13: ASSIGN(type, "Quake 3 getinfo", QUAKE3_QUERY2, 1, '\\', 4, 0, generic_query) break; + case 14: ASSIGN(type, "Gamespy 3 players", GS3_QUERY, 0, '\0', 7, 3, generic_query) break; + case -1: // -1 or -2 in case I change the value of the custom query + case -2: { + ASSIGN(0, "custom binary", "", 1, '\\', 0, 0, generic_query) + query = multi_query_custom_binary; + if(querylen) *querylen = multi_query_custom_binary_size; + break; + } + default: return(NULL); break; + } +#undef ASSIGN + + switch(info) { + case 1: fprintf(stderr, "- query type: %s\n", msg); break; /* details */ + case 2: printf(" %2d %-22s", type, msg); break; /* list */ + case 3: return(msg); break; /* type */ + } + return(query); +} + + + + /* this is the code called when you use the -i, -I or -d option */ +void multi_query(u8 *gamestr, int type, in_addr_t ip, u16 port) { + GSLIST_QUERY_T(*func); + generic_query_data_t gqd; + struct sockaddr_in peer; + int sd, + len, + ret, + querylen; + u8 buff[QUERYSZ], + *query; + + query = switch_type_query(type, &querylen, &gqd, (void *)&func, 1); + if(!query) return; + + peer.sin_addr.s_addr = ip; + peer.sin_port = htons(port); + peer.sin_family = AF_INET; + + fprintf(stderr, "- target %s : %hu\n", + inet_ntoa(peer.sin_addr), ntohs(peer.sin_port)); + + sd = udpsocket(); + + gqd.sock = sd; + gqd.func = (void *)func; + gqd.peer = &peer; + + if(force_natneg) { + if(!gamestr) { + fputs("\n" + "Error: you must specify a gamename when you force the Gamespy NAT negotiation\n" + "\n", stderr); + exit(1); + } + printf("- activate Gamespy nat negotiation using gamename %s\n", gamestr); + if(gsnatneg(sd, gamestr, NULL, NULL, INADDR_NONE, 0, &peer, NULL) < 0) { + fputs("\n" + "Error: the game is not using the Gamespy NAT negotiation\n" + "\n", stderr); + exit(1); + } + } + sendto(sd, query, querylen, 0, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)); + if(timeout(sd, TIMEOUT) < 0) { + fputs("\n" + "Error: Socket timeout, no reply received\n" + "\n", stderr); + exit(1); + } + + do { + ret = -1; + len = recvfrom(sd, buff, sizeof(buff) - 1, 0, NULL, NULL); + if(len > 0) { + gqd.scantype = QUERY_SCANTYPE_GSWEB; + gqd.data = malloc(len + 1); + gqd.data[0] = 0; + gqd.ipdata = NULL; + ret = (*func)(buff, len, &gqd); + FREEX(gqd.data); + } + if((ret >= 0) && no_reping) break; + } while(!timeout(sd, TIMEOUT)); + close(sd); + if(force_natneg) gsnatneg(-1, NULL, NULL, NULL, 0, 0, NULL, NULL); +} + + + + /* receives all the replies from the servers */ +quick_thread(multi_scan_reply, generic_query_data_t *gqd) { + struct sockaddr_in peer; +#ifdef WIN32 +#else + struct timeb timex; +#endif + int sd, + i, + len, + psz; + u16 ping; + u8 buff[QUERYSZ], + *ipaddr, + *tmp; + + sd = gqd->sock; + psz = sizeof(struct sockaddr_in); + ipaddr = NULL; + + while(!timeout(sd, gqd->maxping)) { + len = recvfrom(sd, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&peer, &psz); + if(len < 0) continue; + + ping = -1; + if(gqd->scantype == QUERY_SCANTYPE_SINGLE) { + // country\IT\ping\65535 + gqd->data = malloc(25 + len + 1 + 100); // 15 IP + 1 + 5 port + 1 + len + added_data + ipaddr = inet_ntoa(peer.sin_addr); + sprintf(gqd->data, "%s:%hu ", ipaddr, ntohs(peer.sin_port)); + gqd->pos = 0; + + for(i = 0; i < gqd->iplen; i++) { + if((gqd->ipport[i].ip == peer.sin_addr.s_addr) && (gqd->ipport[i].port == peer.sin_port)) { + break; + } + } + if(i >= gqd->iplen) continue; + TEMPOZ1; + ping = TEMPOZ2 - gqd->ping[i]; + gqd->done[i] = 1; + + } else { + for(i = 0; gqd->ipdata[i].ip; i++) { + if((gqd->ipdata[i].ip == peer.sin_addr.s_addr) && (gqd->ipdata[i].port == peer.sin_port)) { + break; + } + } + if(!gqd->ipdata[i].ip) continue; + + if(gqd->ipdata[i].sort == IPDATA_SORT_CLEAR) { + TEMPOZ1; + ping = TEMPOZ2; + gqd->ipdata[i].ping = TEMPOZ2 - gqd->ipdata[i].ping; + gqd->ipdata[i].sort = IPDATA_SORT_REPLY; + ping = gqd->ipdata[i].ping; + } + + gqd->pos = i; + } + + gqd->sock = sd; + gqd->peer = &peer; + if((*gqd->func)(buff, len, gqd) < 0) continue; + + if(gqd->scantype == QUERY_SCANTYPE_SINGLE) { + + tmp = gqd->data + strlen(gqd->data); + sprintf( + tmp, + "%sping\\%u", + (tmp[-1] == '\\') ? "" : "\\", + ping); + + if(sql) { +#ifdef SQL + gssql(gqd->data); +#endif + } else { + // to console instead + printf("%s\n", gqd->data); + } + FREEX(gqd->data); + } + } + + if(gqd->scantype == QUERY_SCANTYPE_SINGLE) { + } else { + for(i = 0; gqd->ipdata[i].ip; i++) { + if(gqd->ipdata[i].sort != IPDATA_SORT_REPLY) gqd->ipdata[i].ping = -1; + } + } + + return(0); +} + + + + /* this function is used by gsweb consider it legacy! */ +int multi_scan(u8 *gamestr, int type, void *ipbuff, int iplen, ipdata_t *gip, u32 ping) { + GSLIST_QUERY_T(*func); + thread_id tid; + ipport_t *ipport; + generic_query_data_t gqd; + struct sockaddr_in peer; + int sd, + servers, + querylen; + u8 *query; + +#ifdef WIN32 +#else + struct timeb timex; +#endif + + ipport = (ipport_t *)ipbuff; + if(!ping) goto no_ping; + + memset(&gqd, 0, sizeof(generic_query_data_t)); + query = switch_type_query(type, &querylen, &gqd, (void *)&func, 0); + + sd = udpsocket(); + + gqd.sock = sd; + gqd.maxping = ping; + gqd.ipdata = gip; + gqd.func = (void *)func; + gqd.scantype = QUERY_SCANTYPE_GSWEB; + gqd.peer = &peer; + gqd.iplen = iplen; + peer.sin_family = AF_INET; + + tid = 0; + for(servers = 0; servers < iplen; servers++) { + peer.sin_addr.s_addr = gip[servers].ip = ipport[servers].ip; + peer.sin_port = gip[servers].port = gip[servers].qport = ipport[servers].port; + + TEMPOZ1; + gip[servers].ping = TEMPOZ2; + gip[servers].sort = IPDATA_SORT_CLEAR; + + if(!tid) tid = quick_threadx(multi_scan_reply, &gqd); + sendto(sd, query, querylen, 0, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)); + usleep(scandelay); + } + if(tid) quick_threadz(tid); + + tid = 0; + for(servers = 0; servers < iplen; servers++) { // REPING without gsnatneg + if(gip[servers].sort != IPDATA_SORT_CLEAR) continue; + peer.sin_addr.s_addr = gip[servers].ip = ipport[servers].ip; + peer.sin_port = gip[servers].port = gip[servers].qport = ipport[servers].port; + + TEMPOZ1; + gip[servers].ping = TEMPOZ2; + + if(!tid) tid = quick_threadx(multi_scan_reply, &gqd); + sendto(sd, query, querylen, 0, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)); + usleep(scandelay); + } + if(tid) quick_threadz(tid); + + if(force_natneg && ((type == 0) || (type == 8) || (type == 11))) { + tid = 0; + for(servers = 0; servers < iplen; servers++) { // REPING with gsnatneg + if(gip[servers].sort != IPDATA_SORT_CLEAR) continue; + peer.sin_addr.s_addr = gip[servers].ip = ipport[servers].ip; + peer.sin_port = gip[servers].port = gip[servers].qport = ipport[servers].port; + + TEMPOZ1; + gip[servers].ping = TEMPOZ2; + if(gsnatneg(sd, gamestr, NULL, NULL, INADDR_NONE, 0, &peer, NULL) < 0) continue; + + if(!tid) tid = quick_threadx(multi_scan_reply, &gqd); + sendto(sd, query, querylen, 0, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)); + usleep(scandelay); + } + if(tid) quick_threadz(tid); + } + + close(sd); + return(servers); + +no_ping: + for(servers = 0; servers < iplen; servers++) { + gip[servers].ip = ipport[servers].ip; + gip[servers].port = gip[servers].qport = ipport[servers].port; + gip[servers].sort = IPDATA_SORT_REPLY; + } + return(servers); +} + + + + /* sends query packets to all the hosts we have received (ipbuff) */ +int mega_query_scan(u8 *gamestr, int type, void *ipbuff, int iplen, u32 ping) { + GSLIST_QUERY_T(*func); + thread_id tid; + ipport_t *ipport; + generic_query_data_t gqd; + struct sockaddr_in peer; + int sd, + servers, + querylen; + u8 *query; + +#ifdef WIN32 +#else + struct timeb timex; +#endif + + if(!iplen) return(0); + ipport = (ipport_t *)ipbuff; + + memset(&gqd, 0, sizeof(generic_query_data_t)); + query = switch_type_query(type, &querylen, &gqd, (void *)&func, 0); + + sd = udpsocket(); + + gqd.sock = sd; + gqd.maxping = ping; + gqd.func = (void *)func; + gqd.scantype = QUERY_SCANTYPE_SINGLE; + gqd.peer = &peer; + gqd.ipport = ipport; + gqd.iplen = iplen; + gqd.ping = malloc(iplen * 4); + gqd.done = malloc(iplen); + peer.sin_family = AF_INET; + + tid = 0; + for(servers = 0; servers < iplen; servers++) { // FIRST PING + peer.sin_addr.s_addr = ipport[servers].ip; + peer.sin_port = ipport[servers].port; + + TEMPOZ1; + gqd.ping[servers] = TEMPOZ2; + gqd.done[servers] = 0; + + if(!tid) tid = quick_threadx(multi_scan_reply, &gqd); + sendto(sd, query, querylen, 0, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)); + usleep(scandelay); + } + if(tid) quick_threadz(tid); + + tid = 0; + for(servers = 0; servers < iplen; servers++) { // REPING without natneg + if(gqd.done[servers]) continue; + peer.sin_addr.s_addr = ipport[servers].ip; + peer.sin_port = ipport[servers].port; + + TEMPOZ1; + gqd.ping[servers] = TEMPOZ2; + + if(!tid) tid = quick_threadx(multi_scan_reply, &gqd); + sendto(sd, query, querylen, 0, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)); + usleep(scandelay); + } + if(tid) quick_threadz(tid); + + if(force_natneg && ((type == 0) || (type == 8) || (type == 11))) { + tid = 0; + for(servers = 0; servers < iplen; servers++) { // REPING with natneg + if(gqd.done[servers]) continue; + peer.sin_addr.s_addr = ipport[servers].ip; + peer.sin_port = ipport[servers].port; + + TEMPOZ1; + gqd.ping[servers] = TEMPOZ2; + + if(gsnatneg(sd, gamestr, NULL, NULL, INADDR_NONE, 0, &peer, NULL) < 0) continue; + + if(!tid) tid = quick_threadx(multi_scan_reply, &gqd); + sendto(sd, query, querylen, 0, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)); + usleep(scandelay); + } + if(tid) quick_threadz(tid); + } + + close(sd); + FREEX(gqd.ping); + FREEX(gqd.done); + if(force_natneg) gsnatneg(-1, NULL, NULL, NULL, 0, 0, NULL, NULL); + return(servers); +} + + diff --git a/gslist/src/mydownlib.c b/gslist/src/mydownlib.c new file mode 100644 index 0000000..9cd29a7 --- /dev/null +++ b/gslist/src/mydownlib.c @@ -0,0 +1,1660 @@ +/* +mydownlib +by Luigi Auriemma +e-mail: aluigi@autistici.org +web: aluigi.org + + Copyright 2006-2013 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl-2.0.txt +*/ + +// note that these functions contain some experimental code that +// I consider a "work-around" like SSL and global cookies support + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mydownlib.h" + +#ifdef WIN32 + #include + + #define close closesocket + #define in_addr_t u32 + #define TEMPOZ1 + #define TEMPOZ2 GetTickCount() + #define ONESEC 1000 +#else + #include + #include + #include + #include + #include + #include + #include + #include + + #define stricmp strcasecmp + #define strnicmp strncasecmp + #define TEMPOZ1 ftime(&timex) + #define TEMPOZ2 ((timex.time * 1000) + timex.millitm) + #define ONESEC 1 +#endif + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + + + +#define VISDELAY 500 +#define BUFFSZ 8192 +#define MAXTIMEOUT 60 // one minute +#define MAXARGS 16 // modify it if you need more args in mydown_scanhead +#define MAXDNS 32 +#define VERPRINTF if(verbose >= 0) fprintf(stderr, +#define VERPRINTF2 if(verbose > 0) fprintf(stderr, +#define TEMPOZ(x) TEMPOZ1; \ + x = TEMPOZ2 + +#ifdef MYDOWN_SSL + #include // link with libssl.a libcrypto.a -lgdi32 +#else // on linux: gcc -o code code.c -lssl -lcrypto -lpthread + #define SSL char + #define SSL_CTX char + #define SSL_read(A,B,C) 0 + #define SSL_write(A,B,C) 0 + #define SSL_shutdown(X) mydown_donothing() + #define SSL_free(X) mydown_donothing() + #define SSL_CTX_free(X) mydown_donothing() +#endif +#define SSL_COMPAT(X) SSL_CTX_set_cipher_list(X, "ALL"); \ + SSL_CTX_set_options(X, SSL_OP_ALL); + + + +typedef struct { // lame DNS caching implementation + u8 *host; + in_addr_t ip; +} dns_db_t; + +int dns_db_max = 0, + dns_db_add = 0; +dns_db_t dns_db[MAXDNS]; +u8 *global_cookie = NULL; // used only with -DMYDOWN_GLOBAL_COOKIE + + + +void mydown_donothing(void) {} +int mydown_get_host(u8 *url, u8 **hostx, u16 *portx, u8 **urix, u8 **userx, u8 **passx, int verbose); +u8 *mydown_uri2hex(u8 *uri); +u8 *mydown_hex2uri(u8 *uri); +void mydown_scanhead(u8 *data, int datalen, ...); +u8 *mydown_tmpnam(void); +void mydown_free_sock(SSL *ssl_sd, SSL_CTX *ctx_sd, int *sock); +u8 *mydown_http_skip(u8 *buff, int len, int *needed, int *remain); +void mydown_free(u8 **buff); +int mydown_chunked_skip(u8 *buff, int chunkedsize); +int mydown_unzip(z_stream z, u8 *in, int inlen, u8 **outx, int *outxlen); +int mydown_sscanf_hex(u8 *data, int datalen); +int mydown_timeout(int sock, int secs); +int mydown_block_recv(SSL *ssl_sd, int sd, u8 *data, int len, int timeout); +u8 *mydown_showhttp80(u16 port); +void mydown_showstatus(u32 fsize, u32 ret, u32 oldret, int timediff); +u8 *mydown_base64_encode(u8 *data, int *length); +in_addr_t mydown_resolv(char *host); +char *mydown_stristr(const char *String, const char *Pattern); + + + +int mydown_send(SSL *ssl_sd, int sd, u8 *data, int datasz) { + if(ssl_sd) return(SSL_write(ssl_sd, data, datasz)); + return(send(sd, data, datasz, 0)); +} + + + +int mydown_recv(SSL *ssl_sd, int sd, u8 *data, int datasz) { + int ret; + if(ssl_sd) ret = SSL_read(ssl_sd, data, datasz); + else ret = recv(sd, data, datasz, 0); + //if(ret > 0) show_dump(2, data, ret, stdout); + //if(ret > 0) fwrite(data, 1, ret, stdout); + return ret; +} + + + +#define mydown_opt_pop \ + if(opt) { \ + if(!from) from = opt->from; \ + if(!tot) tot = opt->tot; \ + if(!showhead) showhead = opt->showhead; \ + if(!resume) resume = opt->resume; \ + if(!onlyifdiff) onlyifdiff = opt->onlyifdiff; \ + if(!user) user = opt->user; \ + if(!pass) pass = opt->pass; \ + if(!referer) referer = opt->referer; \ + if(!useragent) useragent = opt->useragent; \ + if(!cookie) cookie = opt->cookie; \ + if(!more_http) more_http = opt->more_http; \ + if(!verbose) verbose = opt->verbose; \ + if(!filedata) filedata = opt->filedata; \ + if(!keep_alive) keep_alive = opt->keep_alive; \ + if(!timeout) timeout = opt->timeout; \ + if(!ret_code) ret_code = opt->ret_code; \ + if(!onflyunzip) onflyunzip = opt->onflyunzip; \ + if(!content) content = opt->content; \ + if(!contentsize)contentsize = opt->contentsize; \ + if(!get) get = opt->get; \ + if(!proxy) proxy = opt->proxy; \ + if(!proxy_port) proxy_port = opt->proxy_port; \ + } + +#define mydown_opt_push \ + if(opt) { \ + opt->from = from; \ + opt->tot = tot; \ + opt->showhead = showhead; \ + opt->resume = resume; \ + opt->onlyifdiff = onlyifdiff; \ + opt->user = user; \ + opt->pass = pass; \ + opt->referer = referer; \ + opt->useragent = useragent; \ + opt->cookie = cookie; \ + opt->more_http = more_http; \ + opt->verbose = verbose; \ + opt->filedata = filedata; \ + opt->keep_alive = keep_alive; \ + opt->timeout = timeout; \ + opt->ret_code = ret_code; \ + opt->onflyunzip = onflyunzip; \ + opt->content = content; \ + opt->contentsize = contentsize; \ + opt->get = get; \ + opt->proxy = proxy; \ + opt->proxy_port = proxy_port; \ + if(global_cookie) global_cookie = cookie; \ + } + + + +u32 mydown(u8 *myurl, u8 *filename, mydown_options *opt) { + FILE *fd = NULL; + u32 from = 0, + tot = 0, + filesize = MYDOWN_ERROR; + int showhead = 0, + resume = 0, + onlyifdiff = 0, + verbose = 0, + *keep_alive = 0, + *ret_code = 0, + timeout = 0, + onflyunzip = 0, + contentsize = 0, + dossl = 0; + u16 port = 0, + proxy_port = 0; + u8 *url = NULL, + *uri = NULL, + *host = NULL, + *user = NULL, + *pass = NULL, + *referer = NULL, + *useragent = NULL, + *cookie = NULL, + *more_http = NULL, + **filedata = NULL, + *content = NULL, + *get = NULL, + *proxy = NULL; + +#ifdef WIN32 + WSADATA wsadata; + WSAStartup(MAKEWORD(1,0), &wsadata); +#endif + + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + mydown_opt_pop + if(global_cookie) cookie = global_cookie; + if(user) { + VERPRINTF2 + " user %s\n" + " pass %s\n", + user, + pass); + } + + if(!myurl) return(MYDOWN_ERROR); + url = strdup(myurl); + if(!url) return(MYDOWN_ERROR); + dossl = mydown_get_host(url, &host, &port, &uri, user ? NULL : &user, pass ? NULL : &pass, verbose); + + VERPRINTF2" start download\n"); + + filesize = mydown_http2file( + keep_alive, // int *sock + timeout, // int timeout + host, // u8 *host + port, // u16 port + user, // u8 *user + pass, // u8 *pass + referer, // u8 *referer + useragent, // u8 *useragent + cookie, // u8 *cookie + more_http, // u8 *more_http + verbose, // int verbose + uri, // u8 *getstr + fd, // FILE *fd + filename, // u8 *filename + showhead, // int showhead + (dossl << 1) | onlyifdiff, // int onlyifdiff + resume, // int resume + from, // u32 from + tot, // u32 tot + NULL, // u32 *filesize + filedata, // u8 **filedata + ret_code, // int *ret_code + onflyunzip, // int onflyunzip + content, // u8 *content + contentsize, // int contentsize + get, // u8 *get + proxy, + proxy_port, + NULL + ); + + if(fd && (fd != stdout)) fclose(fd); + mydown_free(&url); + mydown_free(&uri); + return(filesize); +} + + + +int mydown_get_host(u8 *url, u8 **hostx, u16 *portx, u8 **urix, u8 **userx, u8 **passx, int verbose) { + int dossl = 0; + u16 port = 80; + u8 *host = NULL, + *uri = NULL, + *user = NULL, + *pass = NULL, + *p; + + if(!url) goto quit; + host = url; + + p = strstr(host, "://"); // handle http:// + if(!p) p = strstr(host, ":\\\\"); + if(p) { + if(!strnicmp(host, "https", 5)) { + dossl = 1; + port = 443; + } + for(p += 3; *p; p++) { // in case of http://// + if((*p != '/') && (*p != '\\')) break; + } + host = p; + } + + for(p = host; *p; p++) { // search the uri + if((*p == '/') || (*p == '\\')) { + uri = p; + break; + } + } + if(uri) { + *uri++ = 0; + uri = mydown_uri2hex(uri); + } + if(!uri) uri = strdup(""); // in case of no uri or mydown_uri2hex fails + + p = strrchr(host, '@'); + if(p) { + *p = 0; + + user = host; + + pass = strchr(host, ':'); + if(pass) { + *pass++ = 0; + } else { + pass = ""; + } + + host = p + 1; + } + + p = strchr(host, ':'); + if(p) { + *p = 0; + port = atoi(p + 1); + } + + VERPRINTF" %s\n", url); + VERPRINTF2 + " host %s : %u\n" + " uri %s\n", + host, port, + uri); + + if(user) { + VERPRINTF2 + " user %s\n" + " pass %s\n", + user, + pass); + } + +quit: + if(hostx) *hostx = host; + if(portx) *portx = port; + if(urix) *urix = uri; + if(userx) *userx = user; + if(passx) *passx = pass; + return(dossl); +} + + + +u8 *mydown_http_delimit(u8 *data, u8 *limit, int *mod) { + u8 *p; + + if(mod) *mod = -1; + if(!data || !data[0]) return(NULL); + + for(p = data;; p++) { + if(p >= limit) return(NULL); + if(!*p) return(NULL); + if((*p == '\r') || (*p == '\n')) break; + } + *p = 0; + if(mod) *mod = p - data; + for(p++;; p++) { + if(p >= limit) return(NULL); + if(!*p) return(NULL); + if((*p != '\r') && (*p != '\n')) break; + } + return(p); +} + + + +u8 *mydown_uri2hex(u8 *uri) { + static const u8 hex[] = "0123456789abcdef"; + u8 *ret, + *p, + c; + + if(!uri) return(NULL); + ret = calloc((strlen(uri) * 3) + 1, 1); + if(!ret) return(NULL); + + for(p = ret; *uri; uri++) { + c = *uri; + if(isprint(c) && !strchr(" \"<>#" /*"%\\"*/ "{}|^~[]`", c)) { // I have left the backslash and the percentage out + *p++ = c; + } else { + *p++ = '%'; + *p++ = hex[c >> 4]; + *p++ = hex[c & 15]; + } + } + *p = 0; + + return(ret); +} + + + +u8 *mydown_hex2uri(u8 *uri) { + int t, + n; + u8 *ret, + *p; + + if(!uri) return(NULL); + ret = strdup(uri); + if(!ret) return(NULL); + + for(p = ret; *uri; uri++, p++) { + t = *uri; + if((*uri == '%') && (*(uri + 1) == 'u')) { + if(sscanf(uri + 1, "u%04x", &t) == 1) uri += 5; + } else if(*uri == '%') { + if(sscanf(uri + 1, "%02x", &t) == 1) uri += 2; + } else if(*uri == '&') { + if(*(uri + 1) == '#') { + if((sscanf(uri + 1, "#%d;%n", &t, &n) == 1) && (n > 2)) { + uri += n; + } else { + t = *uri; + } + } else if(!strnicmp(uri, """, 6)) { + t = '\"'; + uri += 5; + } else if(!strnicmp(uri, "&", 5)) { + t = '&'; + uri += 4; + } else if(!strnicmp(uri, "⁄", 7)) { + t = '/'; + uri += 6; + } else if(!strnicmp(uri, "<", 4)) { + t = '<'; + uri += 3; + } else if(!strnicmp(uri, ">", 4)) { + t = '>'; + uri += 3; + } else if(!strnicmp(uri, " ", 6)) { + t = 160; + uri += 5; + } else if(!strnicmp(uri, "·", 8)) { + t = 183; + uri += 7; + } + } + *p = t; + } + *p = 0; + + return(ret); +} + + + +// can be used only one time because modifies the input! +void mydown_scanhead(u8 *data, int datalen, ...) { + va_list ap; + int i, + mod, + vals, + cookie_len = 0; + u8 *par[MAXARGS], + **val[MAXARGS], + *l, + *p, + *t, + *datal, + *limit; + + if(!data) return; + if(datalen <= 0) return; + + va_start(ap, datalen); + for(i = 0; i < MAXARGS; i++) { + par[i] = va_arg(ap, u8 *); + if(!par[i]) break; + val[i] = va_arg(ap, u8 **); + if(!val[i]) break; + *val[i] = NULL; + } + vals = i; + va_end(ap); + + for(limit = data + datalen; data; data = l) { // in case data becomes NULL + l = mydown_http_delimit(data, limit, &mod); + datal = strchr(data, ':'); + if(datal) { + *datal = 0; // restore later + for(i = 0; i < vals; i++) { + if(stricmp(data, par[i])) continue; + for(p = datal + 1; *p && ((*p == ' ') || (*p == '\t')); p++); + if(!stricmp(data, "Set-Cookie")) { + t = strchr(p, ';'); + if(t) *t = 0; // restore later + *val[i] = realloc(*val[i], cookie_len + 2 + strlen(p) + 1); + if(*val[i]) cookie_len += sprintf(*val[i] + cookie_len, "%s%s", cookie_len ? "; " : "", p); + if(t) *t = ';'; + } else { + *val[i] = p; + } + break; + } + *datal = ':'; + } + //if(mod >= 0) data[mod] = '\r'; // never enable this, the data must remain modified + if(!l) break; + } +} + + + +// fname is provided by the user or from the network, the cleaning here is intended only +// about the bad chars not supported by the file-system, it's not a security function +void mydown_filename_clean(u8 *fname) { + u8 *p; + + if(!fname) return; + p = strchr(fname, ':'); // the first ':' is allowed + if(p) fname = p + 1; + for(p = fname; *p && (*p != '\r') && (*p != '\n'); p++) { + if(strchr("?%*:|\"<>", *p)) { // invalid filename chars not supported by the most used file systems + *p = '_'; + } + } + if(*p) *p = 0; + for(p--; (p >= fname) && ((*p == ' ') || (*p == '.')); p--) *p = 0; // remove final spaces and dots +} + + + +u32 mydown_http2file(int *keep_alive, int timeout, u8 *host, u16 port, u8 *user, u8 *pass, u8 *referer, u8 *useragent, u8 *cookie, u8 *more_http, int verbose, u8 *getstr, FILE *fd, u8 *filename, int showhead, int onlyifdiff, int resume, u32 from, u32 tot, u32 *filesize, u8 **filedata, int *ret_code, int onflyunzip, u8 *content, int contentsize, u8 *get, u8 *proxy, u16 proxy_port, mydown_options *opt) { +#ifndef WIN32 + struct timeb timex; +#endif + static struct linger ling = {1,1}; + z_stream z; + FILE *oldfd = fd; + struct sockaddr_in peer; + struct stat xstat; + time_t oldtime = 0, + newtime; + u32 ret = 0, + httpret = 0, + vishttpret = 0, + fsize = 0; + int sd = 0, + t, + err, + len, + code = 0, + b64len, + filedatasz = 0, + httpcompress = 0, + httpgzip = 0, + httpdeflate = 0, + httpz = 0, + chunked = 0, + chunkedsize = 0, + chunkedlen = 0, + wbits, + zbufflen = 0, + httpskipbytes = 0; + u8 tmp[32], + *buff = NULL, + *query = NULL, + *data = NULL, + *header_limit = NULL, + *s = NULL, + *userpass = NULL, + *b64 = NULL, + *conttype = NULL, + *contlen = NULL, + *contdisp = NULL, + *icyname = NULL, + *transenc = NULL, + *contenc = NULL, + *connection = NULL, + *location = NULL, + *filedatatmp = NULL, + *zbuff = NULL, + *ztmp = NULL, + *chunkedbuff = NULL, + *chunkedtmp = NULL, + *filenamemalloc = NULL, + *filename_tmp1 = NULL, + *filename_tmp2 = NULL, + *remote_filename= NULL, + httpgzip_flags = 0; + + int dossl = 0; + SSL *ssl_sd = NULL; + SSL_CTX *ctx_sd = NULL; +#ifdef MYDOWN_SSL + static int ssl_loaded = 0; + SSL_METHOD *ssl_method = NULL; +#endif + +#define GOTOQUIT { ret = MYDOWN_ERROR; goto quit; } + + mydown_opt_pop + + if(onlyifdiff) { + // lame work-around to avoid to use additional fields + // ******** ******** ******** ***22221 + dossl = (onlyifdiff >> 1) & 15; // 4 bits + onlyifdiff = onlyifdiff & 1; // 1 bit + } + if(dossl) keep_alive = NULL; // ssl_sd and ctx_sd don't get saved at the moment + + if(!keep_alive || !*keep_alive) { + if(proxy) { + peer.sin_port = htons(proxy_port); + peer.sin_addr.s_addr = mydown_resolv(proxy); + } else { + peer.sin_port = htons(port); + peer.sin_addr.s_addr = mydown_resolv(host); + } + if(peer.sin_addr.s_addr == INADDR_NONE) GOTOQUIT; + peer.sin_family = AF_INET; + + sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(sd < 0) GOTOQUIT; + setsockopt(sd, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling)); + if(keep_alive) *keep_alive = sd; + + VERPRINTF2" connect to %s:%u...", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port)); + if(connect(sd, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)) < 0) { + fprintf(stderr, "\nError: connection refused\n"); + GOTOQUIT; + } + VERPRINTF2"done\n"); + + if(proxy && dossl) { // requires CONNECT + len = 64 + strlen(host) + 1 + 6 + 64 + 1; + query = malloc(len); + len = sprintf( + query, + "CONNECT %s:%u HTTP/1.0\r\n" + "\r\n", + host, port); + if(mydown_send(NULL, sd, query, len) != len) GOTOQUIT; + mydown_free(&query); + + len = 0; + while((t = mydown_block_recv(NULL, sd, tmp, 1, timeout)) > 0) { + if((tmp[0] == '\r') || (tmp[0] == '\n')) len++; + else len = 0; + if(len >= 4) break; + } + } + +#ifdef MYDOWN_SSL + if(dossl) { + if(!ssl_loaded) { + SSL_library_init(); + //SSL_load_error_strings(); + ssl_loaded = 1; + } + switch(dossl) { + case 1: ssl_method = (SSL_METHOD *)SSLv23_method(); break; + case 2: ssl_method = (SSL_METHOD *)SSLv2_method(); break; + case 3: ssl_method = (SSL_METHOD *)SSLv3_method(); break; + case 4: ssl_method = (SSL_METHOD *)DTLSv1_method(); break; + case 5: ssl_method = (SSL_METHOD *)TLSv1_method(); break; + default: ssl_method = NULL; break; + } + if(!ssl_method) GOTOQUIT + t = 0; + ctx_sd = SSL_CTX_new(ssl_method); + if(!ctx_sd) { + t = -1; + } else { + SSL_COMPAT(ctx_sd) + ssl_sd = SSL_new(ctx_sd); + SSL_set_fd(ssl_sd, sd); + if(SSL_connect(ssl_sd) < 0) t = -1; + } + if(t < 0) { + dossl++; + VERPRINTF2" try ssl method %u\n", dossl); + if(sd && !keep_alive) mydown_free_sock(ssl_sd, ctx_sd, &sd); + mydown_free_sock(ssl_sd, ctx_sd, keep_alive); + ret = mydown_http2file(keep_alive, timeout, host, port, user, pass, referer, useragent, cookie, more_http, verbose, getstr, fd, filename, showhead, (dossl << 1) | onlyifdiff, resume, from, tot, filesize, filedata, ret_code, onflyunzip, content, contentsize, get, proxy, proxy_port, opt); + goto quit; + } + } +#endif + } else { + sd = *keep_alive; + } + + if(user && pass) { + userpass = malloc(strlen(user) + 1 + strlen(pass) + 1); + if(!userpass) GOTOQUIT; + b64len = sprintf(userpass, "%s:%s", user, pass); + b64 = mydown_base64_encode(userpass, &b64len); + } + + if(!get) get = "GET"; + if(content && !contentsize) contentsize = strlen(content); + + len = + 400 + // my format strings and HTTP parameters + strlen(host) + // http proxy + 6 + + strlen(get) + + strlen(getstr) + + strlen(host) + + 6 + + ((from || tot) ? 20 + 20 : 0) + + (b64 ? strlen(b64) : 0) + + (referer ? strlen(referer) : 0) + + (useragent ? strlen(useragent) : 0) + + (cookie ? strlen(cookie) : 0) + + (more_http ? strlen(more_http) : 0) + + 1; + + query = calloc(len, 1); + if(!query) GOTOQUIT; + + if(port == 80) tmp[0] = 0; + else sprintf(tmp, ":%u", port); + +#define HTTP_APPEND len += sprintf(query + len, + + len = 0; + HTTP_APPEND "%s ", get); + if(proxy && !dossl) HTTP_APPEND "http://%s%s", host, tmp); + HTTP_APPEND "/%s HTTP/1.1\r\n", getstr); + HTTP_APPEND "Host: %s%s\r\n", host, tmp); + HTTP_APPEND "Connection: %s\r\n", keep_alive ? "keep-alive" : "close"); + + if(!onlyifdiff && (showhead != 2)) { // x-compress needs unlzw??? + HTTP_APPEND "Accept-Encoding: deflate,gzip,x-gzip,compress,x-compress\r\n"); + } + if(from || tot) { + HTTP_APPEND "Range: bytes="); + if(from != -1) HTTP_APPEND "%u", from); + HTTP_APPEND "-"); + if(tot >= 1) HTTP_APPEND "%u", (tot - 1) + ((from == -1) ? 0 : from)); + HTTP_APPEND "\r\n"); + } + if(b64) { + HTTP_APPEND "Authorization: Basic %s\r\n", b64); + } + if(referer) { + HTTP_APPEND "Referer: %s\r\n", referer); + } + if(useragent) { + HTTP_APPEND "User-Agent: %s\r\n", useragent); + } + if(cookie) { + HTTP_APPEND "Cookie: %s\r\n", cookie); + } + if(more_http) { + HTTP_APPEND "%s", more_http); + if(query[len - 1] == '\r') { + HTTP_APPEND "\n"); + } else if(query[len - 1] != '\n') { + HTTP_APPEND "\r\n"); + } + } + if(content) { + HTTP_APPEND "Content-length: %d\r\n", contentsize); + } + HTTP_APPEND "\r\n"); + +#undef HTTP_APPEND + + if(verbose >= 3) fputs(query, stdout); + if(mydown_send(ssl_sd, sd, query, len) != len) GOTOQUIT; + mydown_free(&query); + if(content) { + if(mydown_send(ssl_sd, sd, content, contentsize) != contentsize) GOTOQUIT; + } + + buff = calloc(BUFFSZ + 1, 1); + if(!buff) GOTOQUIT; + buff[0] = 0; + data = buff; + len = BUFFSZ; + while((t = mydown_block_recv(ssl_sd, sd, data, len, timeout)) > 0) { + data += t; + len -= t; + *data = 0; + + header_limit = strstr(buff, "\r\n\r\n"); + if(header_limit) { + *header_limit = 0; + header_limit += 4; + break; + } + header_limit = strstr(buff, "\n\n"); + if(header_limit) { + *header_limit = 0; + header_limit += 2; + break; + } + } + if(t < 0) GOTOQUIT; + + if(!header_limit) { // no header received + header_limit = buff; + } else { + if(showhead == 1) { + if(filedata) *filedata = strdup(buff); + VERPRINTF"\n%s", buff); + goto quit; + } + if(verbose == 2) fprintf(stderr, "\n%s", buff); + + mydown_scanhead(buff, header_limit - buff, + "content-length", &contlen, + "content-type", &conttype, + "content-disposition", &contdisp, + "icy-name", &icyname, + "transfer-encoding", &transenc, + "content-encoding", &contenc, + "Set-Cookie", &cookie, + "Connection", &connection, + "location", &location, + NULL, NULL); + +#ifdef MYDOWN_GLOBAL_COOKIE + if(cookie) global_cookie = cookie; +#endif + + s = strchr(buff, ' '); + if(s) { + code = atoi(s + 1); + + if((code / 100) == 3) { + if(!location) { + fprintf(stderr, "\nError: remote file is temporary unavailable (%d)\n", code); + GOTOQUIT; + } + //s = mydown_hex2uri(location); // not needed + //if(s) { + //free(location); + //location = s; + //} + // the problem is that the not all the webservers follow a standard + // so happens that you find something Location: example.com/path + // or Location: /path or Location: path and so on... + // that's why I need to guess it + VERPRINTF2"\n- redirect: %s\n", location); + t = 0; + if(!location[0]) goto quit; + else if(location[0] == '/') t = 0; + else if(strstr(location, "://")) t = 1; + else if(strstr(location, ":\\\\")) t = 1; + if(!t) { + if(!location[1]) goto quit; // "/" returns to the index + getstr = location; + getstr = mydown_uri2hex(getstr); + if(!getstr) getstr = strdup(""); + fprintf(stderr, "\n- redirect to URI on the same host: %s\n", location); // better to show it + } else { + dossl = mydown_get_host(location, &host, &port, &getstr, &user, &pass, verbose); + } + if(!host || !host[0] || !port) { + fprintf(stderr, "\nError: the Location field is invalid (error code %d)\n", code); + GOTOQUIT; + } + if(sd && !keep_alive) mydown_free_sock(ssl_sd, ctx_sd, &sd); + mydown_free_sock(ssl_sd, ctx_sd, keep_alive); + ret = mydown_http2file(keep_alive, timeout, host, port, user, pass, referer, useragent, cookie, more_http, verbose, getstr, fd, filename, showhead, (dossl << 1) | onlyifdiff, resume, from, tot, filesize, filedata, ret_code, onflyunzip, content, contentsize, get, proxy, proxy_port, opt); + goto quit; + } + + // if((code != 200) && (code != 206)) { + if((code / 100) != 2) { + fprintf(stderr, "\nError: remote file is temporary unavailable (%d)\n", code); + GOTOQUIT; + } + } + + if(connection) { + if(!stricmp(connection, "close") && keep_alive) *keep_alive = 0; + } + + if(contlen) { + s = strchr(contlen, '/'); + if(s) contlen = s + 1; + //if(_atoi64(contlen) > (u64)0x7fffffff) { + // fprintf(stderr, "\nError: large files are not yet supported by mydownlib\n"); + // GOTOQUIT; + //} + if(sscanf(contlen, "%u", &fsize) != 1) fsize = 0; //break; + } + + if(conttype && onflyunzip) { // needed or better + if(mydown_stristr(conttype, "compress")) httpcompress = 1; + if(mydown_stristr(conttype, "gzip")) httpgzip = 1; + if(!onflyunzip) if(mydown_stristr(conttype, "x-gzip")) httpgzip = 0; // work-around + if(mydown_stristr(conttype, "deflate")) httpdeflate = 1; + } + if(contenc) { + if(mydown_stristr(contenc, "compress")) httpcompress = 1; + if(mydown_stristr(contenc, "gzip")) httpgzip = 1; + if(!onflyunzip) if(mydown_stristr(contenc, "x-gzip")) httpgzip = 0; // work-around + if(mydown_stristr(contenc, "deflate")) httpdeflate = 1; + } + + if(!contdisp && icyname) contdisp = icyname; + + if(transenc && mydown_stristr(transenc, "chunked")) chunked = 1; + + if(filesize) *filesize = fsize; + } + + if(filename && ((resume == 2) || (resume == 3))) { + fd = (void *)filename; + if(!fd) { + fprintf(stderr, "\nError: no FILE used with resume %d\n", resume); + GOTOQUIT; + } + filename = "."; // this instruction is useless, I have added it only to skip the checks below + } + + if(contdisp) { + s = (u8 *)mydown_stristr(contdisp, "filename="); + if(!s) s = (u8 *)mydown_stristr(contdisp, "filename*="); + if(!s) s = (u8 *)mydown_stristr(contdisp, "file="); + if(!s) s = (u8 *)mydown_stristr(contdisp, "file*="); + if(s) { + s = strchr(s, '=') + 1; + } else { + s = contdisp; + } + while(*s && ((*s == '\"') || (*s == ' ') || (*s == '\t'))) s++; + remote_filename = mydown_hex2uri(s); + if(remote_filename) filenamemalloc = remote_filename; // needed for freeing it later! + if(remote_filename && remote_filename[0]) { + for(s = remote_filename; *s; s++) { // \r \n are don't exist, it's a line + if((*s == '\\') || (*s == '/') || (*s == ';') || (*s == ':') || (*s == '\"') || (*s == '&') || (*s == '?')) break; + } + for(s--; (s >= remote_filename) && *s && ((*s == ' ') || (*s == '\t')); s--); + *(s + 1) = 0; + } + } else { + remote_filename = mydown_hex2uri(getstr); + if(remote_filename) filenamemalloc = remote_filename; // needed for freeing it later! + if(remote_filename && remote_filename[0]) { + for( + s = remote_filename + strlen(remote_filename) - 1; + (s >= remote_filename) && (*s != '/') && (*s != '\\') && (*s != ':') && (*s != '&') && (*s != '?') && (*s != '='); + s--); + remote_filename = s + 1; + } + } + + if(remote_filename) { // last useless check to avoid directory traversal + s = strrchr(remote_filename, ':'); if(s) remote_filename = s + 1; + s = strrchr(remote_filename, '\\'); if(s) remote_filename = s + 1; + s = strrchr(remote_filename, '/'); if(s) remote_filename = s + 1; + } + + if(!filename || !filename[0]) filename = remote_filename; + + if(!filename || !filename[0]) { + filename = mydown_tmpnam(); + if(filename) filenamemalloc = filename; // needed for freeing it later! + //VERPRINTF"\nError: no filename retrieved, you must specify an output filename\n\n"); + //GOTOQUIT; + } + + // automatic gzip decompression on the fly + if(onflyunzip) { + s = remote_filename ? strrchr(remote_filename, '.') : NULL; + if(!(s && !stricmp(s, ".gz"))) s = filename ? strrchr(filename, '.') : NULL; + if(s && !stricmp(s, ".gz")) { + httpgzip = 1; + *s = 0; + } + } + + if(showhead == 2) { + if(filedata) *filedata = strdup(filename); + ret = fsize; + goto quit; + } + + if(!filedata && !fd) { + if(!strcmp(filename, "-")) { + fd = stdout; + VERPRINTF" file %s\n", "stdout"); + } else { + // lame but needed to avoid problems with mydown_filename_clean + filename_tmp1 = strdup(filename); + filename_tmp2 = filename; + filename = filename_tmp1; + + mydown_filename_clean(filename); + VERPRINTF" file %s\n", filename); + err = stat(filename, &xstat); + if(onlyifdiff && !err && (xstat.st_size == fsize)) { + VERPRINTF" the remote file has the same size of the local one, skip\n"); + GOTOQUIT; + } + /* can't be implemented or will make the library useless (think to updates) + fd = fopen(filename, "rb"); + if(fd) { + fclose(fd); + fprintf(stderr, + "- the output file (%s) already exists\n" + " do you want to overwrite it (y/N)? ", filename); + fgets(tmp, sizeof(tmp), stdin); + if(tolower(tmp[0]) != 'y') GOTOQUIT; + } + */ + if((err < 0) || !resume) { // file doesn't exist or must not resume + fd = fopen(filename, "wb"); + } else { + fd = fopen(filename, "ab"); + from = xstat.st_size; + VERPRINTF2" resume %u\n", from); + if(sd && !keep_alive) mydown_free_sock(ssl_sd, ctx_sd, &sd); + mydown_free_sock(ssl_sd, ctx_sd, keep_alive); + ret = mydown_http2file(keep_alive, timeout, host, port, user, pass, referer, useragent, cookie, more_http, verbose, getstr, fd, filename, showhead, (dossl << 1) | onlyifdiff, resume, from, tot, filesize, filedata, ret_code, onflyunzip, content, contentsize, get, proxy, proxy_port, opt); + mydown_free(&filename_tmp1); + filename = filename_tmp2; + goto quit; + } + mydown_free(&filename_tmp1); + filename = filename_tmp2; + if(!fd) GOTOQUIT; + } + } + + len = data - header_limit; + memmove(buff, header_limit, len); + + httpz = 1; + if(onflyunzip < 0) httpz = 0; // disabled forcely! + if(httpcompress) { + VERPRINTF2" compression: compress\n"); + wbits = 15; + } else if(httpgzip) { + VERPRINTF2" compression: gzip\n"); + wbits = -15; + } else if(httpdeflate) { + VERPRINTF2" compression: deflate\n"); + wbits = -15; + } else { + httpz = 0; + } + if(httpz) { + z.zalloc = (alloc_func)0; + z.zfree = (free_func)0; + z.opaque = (voidpf)0; + if(inflateInit2(&z, wbits)) GOTOQUIT; + + zbufflen = BUFFSZ * 4; + zbuff = calloc(zbufflen, 1); + if(!zbuff) GOTOQUIT; + } + + if(verbose > 0) { + fprintf(stderr, "\n"); + if(fsize) fprintf(stderr, " "); + fprintf(stderr, " | downloaded | kilobytes/second\n"); + if(fsize) fprintf(stderr, "----"); + fprintf(stderr, "-/--------------/-----------------\n"); + } + + if(verbose >= 0) { + TEMPOZ(oldtime); + oldtime -= VISDELAY; + } + + if(filedata) { + filedatasz = fsize; + if(!filedatasz == -1) GOTOQUIT; + filedatatmp = calloc(filedatasz + 1, 1); + if(!filedatatmp) GOTOQUIT; + filedatatmp[filedatasz] = 0; + } + + if(chunked) chunkedsize = len; + + do { +redo: + httpret += len; + + if(chunked) { + for(;;) { + chunkedsize = mydown_chunked_skip(buff, chunkedsize); + + err = mydown_sscanf_hex(buff, chunkedsize); + if(err > 0) break; + if(!err) { + chunkedsize = mydown_chunked_skip(buff, chunkedsize); + break; + } + + t = mydown_block_recv(ssl_sd, sd, buff + chunkedsize, BUFFSZ - chunkedsize, timeout); + if(t <= 0) GOTOQUIT; + chunkedsize += t; + if(chunkedsize > BUFFSZ) GOTOQUIT; + } + + chunkedlen = err; + if(!chunkedlen) break; + + //if(chunkedbuff) free(chunkedbuff); + //chunkedbuff = calloc(chunkedlen, 1); + chunkedbuff = realloc(chunkedbuff, chunkedlen); + if(!chunkedbuff) GOTOQUIT; + memset(chunkedbuff, 0, chunkedlen); + + s = (u8 *)strchr(buff, '\n'); + if(!s) GOTOQUIT; + err = (s + 1) - buff; + chunkedsize -= err; + memmove(buff, buff + err, chunkedsize); + + if(chunkedlen < chunkedsize) { // we have more data than how much we need + memcpy(chunkedbuff, buff, chunkedlen); + chunkedsize -= chunkedlen; + memmove(buff, buff + chunkedlen, chunkedsize); + } else { // we have only part of the needed data + memcpy(chunkedbuff, buff, chunkedsize); + for(len = chunkedsize; len < chunkedlen; len += t) { + t = mydown_block_recv(ssl_sd, sd, chunkedbuff + len, chunkedlen - len, timeout); + if(t <= 0) GOTOQUIT; + } + chunkedsize = 0; + } + + chunkedtmp = buff; + buff = chunkedbuff; + len = chunkedlen; + } + + /* DECOMPRESSION */ + + if(httpz) { + if(httpgzip && !ret) { // gzip is really bad to handle in my code... blah + if(len < 2) { + t = mydown_block_recv(ssl_sd, sd, buff + len, 2 - len, timeout); + if(t <= 0) GOTOQUIT; + len += t; + } + t = len; + s = buff; + if((httpgzip == 1) && !httpskipbytes && (len >= 2)) { + if((buff[0] == 0x1f) && (buff[1] == 0x8b)) { + httpskipbytes = 3; // CM is usually 8, no need to check it + } else { // in this case it's a raw deflate stream + httpgzip = -1; // -1 means that it's no longer needed to check the header + t = 0; // handle all the buffer + } + } + + if(httpgzip > 0) { + for(;;) { + s = mydown_http_skip(s, t, &httpskipbytes, &t); + if(!s) { + t = len; + break; + } + if(httpgzip == 1) { + httpgzip_flags = *s++; // flags + httpskipbytes = 4 + 1 + 1; // mtime + xfl + os + httpgzip++; + } else if(httpgzip == 2) { // xfl flag + httpskipbytes = 0; + if(httpgzip_flags & 4) { + if(t >= 2) { // uff boring, not 100% correct + httpskipbytes = s[0] | (s[1] << 8); + s += 2; + } + } + httpgzip++; + } else if(httpgzip == 3) { + httpskipbytes = 0; + if(httpgzip_flags & 8) { // name + while((s - buff) < len) { + if(!*s++) { + httpgzip++; + break; + } + } + } else { + httpgzip++; + } + } else if(httpgzip == 4) { + httpskipbytes = 0; + if(httpgzip_flags & 16) { // comment + while((s - buff) < len) { + if(!*s++) { + httpgzip++; + break; + } + } + } else { + httpgzip++; + } + } else if(httpgzip == 5) { + httpskipbytes = 0; + if(httpgzip_flags & 2) { // crc + httpskipbytes = 2; + } + httpgzip++; + } else { + t = s - buff; + break; + } + } + } + len = mydown_unzip(z, buff + t, len - t, &zbuff, &zbufflen); + } else { + len = mydown_unzip(z, buff, len, &zbuff, &zbufflen); + } + if(len < 0) GOTOQUIT; + ztmp = buff; + buff = zbuff; + } + + /* UPDATE THE AMOUNT OF UNCOMPRESSED BYTES DOWNLOADED */ + // ret is the total size of the data we have downloaded (uncompressed) + // httpret is the total size of the data we have downloaded from the server + // len is the size of the current block of data we have downloaded (uncompressed) + + ret += len; + + /* WRITE THE DATA INTO FILE OR MEMORY */ + + if(tot && (ret > tot)) { + len = tot - (ret - len); + ret = tot; + } + + if(filedata) { + if(filedatasz < ret) { + filedatasz = ret; + if(filedatasz == -1) GOTOQUIT; + filedatatmp = realloc(filedatatmp, filedatasz); + if(!filedatatmp) GOTOQUIT; + filedatatmp[filedatasz] = 0; + } + memcpy(filedatatmp + ret - len, buff, len); + } else if(fd) { + if(fwrite(buff, 1, len, fd) != len) { + fprintf(stderr, "\nError: I/O error. Probably your disk is full or the file is write protected\n"); + GOTOQUIT; + } + //fflush(fd); + } + if(ret_code && (resume == 3)) *ret_code = ret; + + /* VISUALIZATION */ + + if(verbose >= 0) { + TEMPOZ(newtime); + if((newtime - oldtime) >= VISDELAY) { + mydown_showstatus(fsize, httpret, vishttpret, (int)(newtime - oldtime)); + oldtime = newtime; + vishttpret = httpret; + } + } + + /* FREE, EXCHANGE OR OTHER STUFF */ + + if(httpz) { + zbuff = buff; + buff = ztmp; + } + if(chunked) { + chunkedbuff = buff; + buff = chunkedtmp; + httpret += len; // lame work-around, sorry + len = 0; + goto redo; + } + + /* FSIZE CHECK */ + + if(tot && (ret == tot)) break; + if(fsize) { + if(httpret >= fsize) break; + } + + /* READ NEW DATA FROM THE STREAM */ + + } while((len = mydown_block_recv(ssl_sd, sd, buff, BUFFSZ, timeout)) > 0); + + if(verbose >= 0) { + TEMPOZ(newtime); + mydown_showstatus(fsize, httpret, vishttpret, (int)(newtime - oldtime)); + } + + if(fsize && (len < 0)) GOTOQUIT; + + if(filedata) { + *filedata = filedatatmp; + } + +quit: + if(httpz) { + if(zbuff) inflateEnd(&z); + if(zbuff != buff) mydown_free(&zbuff); + } + if(chunkedbuff != buff) mydown_free(&chunkedbuff); + mydown_free(&userpass); + mydown_free(&b64); + mydown_free(&buff); + mydown_free(&filenamemalloc); + mydown_free(&query); + if(ret_code) { + *ret_code = code; + if(resume == 3) *ret_code = ret; + } + if(sd && !keep_alive) mydown_free_sock(ssl_sd, ctx_sd, &sd); + VERPRINTF"\n"); + if(ret == MYDOWN_ERROR) mydown_free_sock(ssl_sd, ctx_sd, keep_alive); + if(filename && ((resume == 2) || (resume == 3))) { + // do nothing + } else { + if(!oldfd && fd && (fd != stdout)) fclose(fd); + } + mydown_opt_push + return(ret); + +#undef GOTOQUIT +} + + + +u8 *mydown_tmpnam(void) { + FILE *fd; + int i; + u8 *ret; + + ret = malloc(32); + for(i = 1; ; i++) { + sprintf(ret, "%u.myd", i); // enough small for any OS (8.3 too) + fd = fopen(ret, "rb"); // check real existence of the file + if(!fd) break; + fclose(fd); + } + return(ret); +} + + + +void mydown_free_sock(SSL *ssl_sd, SSL_CTX *ctx_sd, int *sock) { + if(sock && *sock) { + // note that ssl_sd and ctx_sd exist only in http2file so if the socket is saved/reused + // they get lost... yeah it's lame but it's only a work-around + if(ssl_sd) { + SSL_shutdown(ssl_sd); + SSL_free(ssl_sd); + } + if(ctx_sd) SSL_CTX_free(ctx_sd); + close(*sock); + *sock = 0; + } +} + + + +u8 *mydown_http_skip(u8 *buff, int len, int *needed, int *remain) { + int rest; + + if(!buff) return(NULL); + rest = *needed; + if(len < rest) { + *needed = rest - len; + *remain = 0; + return(NULL); + } + *needed = 0; + *remain = len - rest; + return(buff + rest); +} + + + +void mydown_free(u8 **buff) { + if(!buff || !*buff) return; + free(*buff); + *buff = NULL; +} + + + +int mydown_chunked_skip(u8 *buff, int chunkedsize) { + int t; + + if(!buff) return(0); + for(t = 0; t < chunkedsize; t++) { + if((buff[t] != '\r') && (buff[t] != '\n')) break; + } + if(t) { + chunkedsize -= t; + memmove(buff, buff + t, chunkedsize); + } + return(chunkedsize); +} + + + +int mydown_unzip(z_stream z, u8 *in, int inlen, u8 **outx, int *outxlen) { + int zerr, + outsz; + u8 *out; + + if(!in) return(0); + if(inlen <= 0) return(0); + + out = *outx; + outsz = *outxlen; + + z.next_in = in; + z.avail_in = inlen; + + for(;;) { + z.next_out = out + z.total_out; + z.avail_out = outsz - z.total_out; + + zerr = inflate(&z, Z_NO_FLUSH); + + if(zerr == Z_STREAM_END) break; + if((zerr != Z_OK) && (zerr != Z_BUF_ERROR)) { + fprintf(stderr, "\nError: zlib error %d\n", zerr); + z.total_out = MYDOWN_ERROR; + break; + } + + if(!z.avail_in) break; + + outsz += (inlen << 1); // inlen * 2 should be enough each time + out = realloc(out, outsz); + if(!out) { + outsz = 0; + z.total_out = MYDOWN_ERROR; + break; + } + } + + *outx = out; + *outxlen = outsz; + return(z.total_out); +} + + + +int mydown_sscanf_hex(u8 *data, int datalen) { + int i, + ret; + + if(!data) return(MYDOWN_ERROR); + for(i = 0; i < datalen; i++) { + if((data[i] == '\r') || (data[i] == '\n')) break; + } + if(i == datalen) return(MYDOWN_ERROR); + + if(sscanf(data, "%x", &ret) != 1) return(MYDOWN_ERROR); + return(ret); +} + + + +int mydown_timeout(int sock, int secs) { + struct timeval tout; + fd_set fdr; + int err; + + tout.tv_sec = secs; + tout.tv_usec = 0; + FD_ZERO(&fdr); + FD_SET(sock, &fdr); + err = select(sock + 1, &fdr, NULL, NULL, &tout); + if(err < 0) return(MYDOWN_ERROR); //std_err(); + if(!err) return(MYDOWN_ERROR); + return(0); +} + + + +int mydown_block_recv(SSL *ssl_sd, int sd, u8 *data, int len, int timeout) { + if(!timeout) timeout = MAXTIMEOUT; + if(mydown_timeout(sd, timeout) < 0) return(MYDOWN_ERROR); + return(mydown_recv(ssl_sd, sd, data, len)); +} + + + +u8 *mydown_showhttp80(u16 port) { + static u8 mini[16]; // static but used only for some milliseconds + + if(port == 80) return(""); + sprintf(mini, ":%u", port); + return(mini); +} + + + +void mydown_showstatus(u32 fsize, u32 ret, u32 oldret, int timediff) { + u32 vis; + + if(fsize) { + vis = ((u64)ret * (u64)100) / (u64)fsize; + fprintf(stderr, "%3u%%", (vis < 100) ? vis : 100); + } + fprintf(stderr, " %12u", ret); + if(ret > 0) { + if(timediff) fprintf(stderr, " %-10u", (u32)(((((u64)ret - (u64)oldret) * (u64)1000) / (u64)timediff) / 1024)); + } + fprintf(stderr, "\r"); +} + + + +u8 *mydown_base64_encode(u8 *data, int *size) { + int len; + u8 *buff, + *p, + a, + b, + c; + static const u8 base[64] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' + }; + + if(!data) return(NULL); + if(!size || (*size < 0)) { // use size -1 for auto text size! + len = strlen(data); + } else { + len = *size; + } + buff = calloc(((len / 3) << 2) + 6, 1); + if(!buff) return(NULL); + + p = buff; + do { + a = data[0]; + b = data[1]; + c = data[2]; + *p++ = base[(a >> 2) & 63]; + *p++ = base[(((a & 3) << 4) | ((b >> 4) & 15)) & 63]; + *p++ = base[(((b & 15) << 2) | ((c >> 6) & 3)) & 63]; + *p++ = base[c & 63]; + data += 3; + len -= 3; + } while(len > 0); + *p = 0; + + for(; len < 0; len++) *(p + len) = '='; + + if(size) *size = p - buff; + return(buff); +} + + + +in_addr_t mydown_resolv(char *host) { + struct hostent *hp; + in_addr_t host_ip; + int i; + dns_db_t *dns; + + host_ip = inet_addr(host); + if(host_ip == htonl(INADDR_NONE)) { + + for(i = 0; i < dns_db_max; i++) { // search + if(!stricmp(host, dns_db[i].host)) return(dns_db[i].ip); + } + + hp = gethostbyname(host); + if(!hp) { + fprintf(stderr, "\nError: Unable to resolve hostname (%s)\n\n", host); + return(INADDR_NONE); + } + host_ip = *(in_addr_t *)(hp->h_addr); + + if(!dns_db_max) memset(&dns_db, 0, sizeof(dns_db)); + if(dns_db_add == MAXDNS) dns_db_add = 0; // add + dns = &dns_db[dns_db_add]; + mydown_free(&dns->host); + dns->host = strdup(host); + dns->ip = host_ip; + dns_db_add++; + if(dns_db_max < MAXDNS) dns_db_max++; + } + return(host_ip); +} + + + +char *mydown_stristr(const char *String, const char *Pattern) +{ + char *pptr, *sptr, *start; + + for (start = (char *)String; *start; start++) + { + /* find start of pattern in string */ + for ( ; (*start && (toupper(*start) != toupper(*Pattern))); start++) + ; + if (!*start) + return 0; + + pptr = (char *)Pattern; + sptr = (char *)start; + + while (toupper(*sptr) == toupper(*pptr)) + { + sptr++; + pptr++; + + /* if end of pattern then pattern was found */ + + if (!*pptr) + return (start); + } + } + return 0; +} + diff --git a/gslist/src/mydownlib.h b/gslist/src/mydownlib.h new file mode 100644 index 0000000..6653472 --- /dev/null +++ b/gslist/src/mydownlib.h @@ -0,0 +1,108 @@ +/* +mydownlib +by Luigi Auriemma +e-mail: aluigi@autistici.org +web: aluigi.org + +Note that this library has been written for being used in my tools, so there is no manual except some comments in the code + + Copyright 2006-2013 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl-2.0.txt +*/ + +#include +#include +#include + + + +#define MYDOWN_VER "0.2.7a" +#define MYDOWN_ERROR (-1) + + + +typedef struct { + uint32_t from; // download from byte, use -1 for the latest tot bytes + uint32_t tot; // download tot bytes + int showhead; // show the http header and stop (1 = yes, 0 = no, 2 get all the remote file info (solution for backward compatibility!)) + int resume; // resume a download (1 = yes, 0 = no, 2 or 3 considers the filename as FILE * (solution for backward compatibility!)) + int onlyifdiff; // download only if differs from local file (1 = yes, 0 = no) + uint8_t *user; // username for authentication + uint8_t *pass; // password for authentication + uint8_t *referer; // referer string + uint8_t *useragent; // user-agent string + uint8_t *cookie; // cookie string + uint8_t *more_http; // additional http parameters + int verbose; // verbosity (-1 = quiet, 0 = normal, 1 = verbose) + uint8_t **filedata; // use it if you want to store the downloaded file in memory (if showhead is 2 then filedata will contain the name ofthe remote file) + int *keep_alive; // keep-alive socket + int timeout; // seconds of timeout + int *ret_code; // HTTP code from the server (if resume is equal to 3 ret_code returns the number of bytes received (solution for backward compatibility!)) + int onflyunzip; // unpack the file on the fly if possible (usually gzipped) + uint8_t *content; // data to post + int contentsize; // optional size of content (default is auto) + uint8_t *get; // the type of request, like GET or POST or HEAD or anything else + uint8_t *proxy; // proxy (HTTP and CONNECT method) + uint16_t proxy_port; // proxy port +} mydown_options; + + + +uint32_t mydown( // ret: file size + uint8_t *myurl, // the URL + // can be like http://aluigi.org/mytoolz/mydown.zip + // or http://user:pass@host:port/blabla/blabla.php?file=1 + uint8_t *filename, // NULL for automatic filename or forced like "test.txt" (if showhead is 2 filename will be considered a uint8_t ** containing the + mydown_options *opt // the above structure for your options +); + + + +uint32_t mydown_http2file( // ret: file size + int *sock, // socket for keep-alive + int timeout, // seconds of timeout + uint8_t *host, // hostname or IP + uint16_t port, // port + uint8_t *user, // username + uint8_t *pass, // password + uint8_t *referer, // Referer + uint8_t *useragent, // User-Agent + uint8_t *cookie, // Cookie + uint8_t *more_http, // additional http parameters (ex: mycookie: blabla\r\npar: val\r\n) + int verbose, // verbose + uint8_t *getstr, // URI + FILE *fd, // file descriptor + uint8_t *filename, // force filename + int showhead, // show headers + int onlyifdiff, // download only if differs from local file + int resume, // resume + uint32_t from, // download from byte + uint32_t tot, // download tot bytes + uint32_t *filesize, // for storing file size + uint8_t **filedata, // use it if you want to store the downloaded file in memory + int *ret_code, // HTTP code from the server + int onflyunzip, // on fly decompression + uint8_t *content, // data to post + int contentsize, // optional size of content (default is auto) + uint8_t *get, // request + uint8_t *proxy, + uint16_t proxy_port, + mydown_options *opt // set it to NULL, it's an experimental idea +); + + diff --git a/gslist/src/show_dump.h b/gslist/src/show_dump.h new file mode 100644 index 0000000..7865534 --- /dev/null +++ b/gslist/src/show_dump.h @@ -0,0 +1,67 @@ +/* +Show_dump 0.1.1a + + Copyright 2004,2005,2006 Luigi Auriemma + + 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 + + http://www.gnu.org/licenses/gpl.txt + +This function, optimized for performace, shows the hex dump of a buffer and +places it in a stream + +Usage: + show_dump(buffer, buffer_length, stdout); + show_dump(buffer, buffer_length, fd); +*/ + +#include + + + +void show_dump(unsigned char *data, unsigned int len, FILE *stream) { + static const char hex[] = "0123456789abcdef"; + static unsigned char buff[67]; /* HEX CHAR\n */ + unsigned char chr, + *bytes, + *p, + *limit, + *glimit = data + len; + + memset(buff + 2, ' ', 48); + + while(data < glimit) { + limit = data + 16; + if(limit > glimit) { + limit = glimit; + memset(buff, ' ', 48); + } + + p = buff; + bytes = p + 50; + while(data < limit) { + chr = *data; + *p++ = hex[chr >> 4]; + *p++ = hex[chr & 15]; + p++; + *bytes++ = ((chr < ' ') || (chr >= 0x7f)) ? '.' : chr; + data++; + } + *bytes++ = '\n'; + + fwrite(buff, bytes - buff, 1, stream); + } +} + diff --git a/gslist/src/stristr.c b/gslist/src/stristr.c new file mode 100644 index 0000000..e6edd64 --- /dev/null +++ b/gslist/src/stristr.c @@ -0,0 +1,32 @@ +// from http://c.snippets.org/code/stristr.c without the usage of NUL and NULL + +#include + +char *stristr(const char *String, const char *Pattern) +{ + char *pptr, *sptr, *start; + + for (start = (char *)String; *start; start++) + { + /* find start of pattern in string */ + for ( ; (*start && (toupper(*start) != toupper(*Pattern))); start++) + ; + if (!*start) + return 0; + + pptr = (char *)Pattern; + sptr = (char *)start; + + while (toupper(*sptr) == toupper(*pptr)) + { + sptr++; + pptr++; + + /* if end of pattern then pattern was found */ + + if (!*pptr) + return (start); + } + } + return 0; +} diff --git a/gslist/src/winerr.h b/gslist/src/winerr.h new file mode 100644 index 0000000..3f445ee --- /dev/null +++ b/gslist/src/winerr.h @@ -0,0 +1,74 @@ +/* + Header file used for manage errors in Windows + It support socket and errno too + (this header replace the previous sock_errX.h) +*/ + +#include +#include + + + +void std_err(void) { + char *error; + + switch(WSAGetLastError()) { + case 10004: error = "Interrupted system call"; break; + case 10009: error = "Bad file number"; break; + case 10013: error = "Permission denied"; break; + case 10014: error = "Bad address"; break; + case 10022: error = "Invalid argument (not bind)"; break; + case 10024: error = "Too many open files"; break; + case 10035: error = "Operation would block"; break; + case 10036: error = "Operation now in progress"; break; + case 10037: error = "Operation already in progress"; break; + case 10038: error = "Socket operation on non-socket"; break; + case 10039: error = "Destination address required"; break; + case 10040: error = "Message too long"; break; + case 10041: error = "Protocol wrong type for socket"; break; + case 10042: error = "Bad protocol option"; break; + case 10043: error = "Protocol not supported"; break; + case 10044: error = "Socket type not supported"; break; + case 10045: error = "Operation not supported on socket"; break; + case 10046: error = "Protocol family not supported"; break; + case 10047: error = "Address family not supported by protocol family"; break; + case 10048: error = "Address already in use"; break; + case 10049: error = "Can't assign requested address"; break; + case 10050: error = "Network is down"; break; + case 10051: error = "Network is unreachable"; break; + case 10052: error = "Net dropped connection or reset"; break; + case 10053: error = "Software caused connection abort"; break; + case 10054: error = "Connection reset by peer"; break; + case 10055: error = "No buffer space available"; break; + case 10056: error = "Socket is already connected"; break; + case 10057: error = "Socket is not connected"; break; + case 10058: error = "Can't send after socket shutdown"; break; + case 10059: error = "Too many references, can't splice"; break; + case 10060: error = "Connection timed out"; break; + case 10061: error = "Connection refused"; break; + case 10062: error = "Too many levels of symbolic links"; break; + case 10063: error = "File name too long"; break; + case 10064: error = "Host is down"; break; + case 10065: error = "No Route to Host"; break; + case 10066: error = "Directory not empty"; break; + case 10067: error = "Too many processes"; break; + case 10068: error = "Too many users"; break; + case 10069: error = "Disc Quota Exceeded"; break; + case 10070: error = "Stale NFS file handle"; break; + case 10091: error = "Network SubSystem is unavailable"; break; + case 10092: error = "WINSOCK DLL Version out of range"; break; + case 10093: error = "Successful WSASTARTUP not yet performed"; break; + case 10071: error = "Too many levels of remote in path"; break; + case 11001: error = "Host not found"; break; + case 11002: error = "Non-Authoritative Host not found"; break; + case 11003: error = "Non-Recoverable errors: FORMERR, REFUSED, NOTIMP"; break; + case 11004: error = "Valid name, no data record of requested type"; break; + default: error = strerror(errno); break; + } + fprintf(stderr, "\nError: %s\n", error); +#ifdef WINTRAY + MessageBox(0, error, "Gslist", MB_OK | MB_ICONERROR | MB_TASKMODAL); +#endif + exit(1); +} + diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 5e6049e..0000000 --- a/poetry.lock +++ /dev/null @@ -1,422 +0,0 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. - -[[package]] -name = "aiofiles" -version = "23.2.1" -description = "File support for asyncio." -optional = false -python-versions = ">=3.7" -files = [ - {file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"}, - {file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"}, -] - -[[package]] -name = "blinker" -version = "1.7.0" -description = "Fast, simple object-to-object and broadcast signaling" -optional = false -python-versions = ">=3.8" -files = [ - {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, - {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "dnspython" -version = "2.4.2" -description = "DNS toolkit" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, - {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, -] - -[package.extras] -dnssec = ["cryptography (>=2.6,<42.0)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] -doq = ["aioquic (>=0.9.20)"] -idna = ["idna (>=2.1,<4.0)"] -trio = ["trio (>=0.14,<0.23)"] -wmi = ["wmi (>=1.5.1,<2.0.0)"] - -[[package]] -name = "flask" -version = "3.0.0" -description = "A simple framework for building complex web applications." -optional = false -python-versions = ">=3.8" -files = [ - {file = "flask-3.0.0-py3-none-any.whl", hash = "sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638"}, - {file = "flask-3.0.0.tar.gz", hash = "sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58"}, -] - -[package.dependencies] -blinker = ">=1.6.2" -click = ">=8.1.3" -itsdangerous = ">=2.1.2" -Jinja2 = ">=3.1.2" -Werkzeug = ">=3.0.0" - -[package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "h2" -version = "4.1.0" -description = "HTTP/2 State-Machine based protocol implementation" -optional = false -python-versions = ">=3.6.1" -files = [ - {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, - {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, -] - -[package.dependencies] -hpack = ">=4.0,<5" -hyperframe = ">=6.0,<7" - -[[package]] -name = "hpack" -version = "4.0.0" -description = "Pure-Python HPACK header compression" -optional = false -python-versions = ">=3.6.1" -files = [ - {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, - {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, -] - -[[package]] -name = "hypercorn" -version = "0.15.0" -description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn" -optional = false -python-versions = ">=3.7" -files = [ - {file = "hypercorn-0.15.0-py3-none-any.whl", hash = "sha256:5008944999612fd188d7a1ca02e89d20065642b89503020ac392dfed11840730"}, - {file = "hypercorn-0.15.0.tar.gz", hash = "sha256:d517f68d5dc7afa9a9d50ecefb0f769f466ebe8c1c18d2c2f447a24e763c9a63"}, -] - -[package.dependencies] -h11 = "*" -h2 = ">=3.1.0" -priority = "*" -wsproto = ">=0.14.0" - -[package.extras] -docs = ["pydata_sphinx_theme", "sphinxcontrib_mermaid"] -h3 = ["aioquic (>=0.9.0,<1.0)"] -trio = ["exceptiongroup (>=1.1.0)", "trio (>=0.22.0)"] -uvloop = ["uvloop"] - -[[package]] -name = "hyperframe" -version = "6.0.1" -description = "HTTP/2 framing layer for Python" -optional = false -python-versions = ">=3.6.1" -files = [ - {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, - {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, -] - -[[package]] -name = "itsdangerous" -version = "2.1.2" -description = "Safely pass data to untrusted environments and back." -optional = false -python-versions = ">=3.7" -files = [ - {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, - {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, -] - -[[package]] -name = "jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markupsafe" -version = "2.1.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] - -[[package]] -name = "priority" -version = "2.0.0" -description = "A pure-Python implementation of the HTTP/2 priority tree" -optional = false -python-versions = ">=3.6.1" -files = [ - {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"}, - {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"}, -] - -[[package]] -name = "pymongo" -version = "4.6.0" -description = "Python driver for MongoDB " -optional = false -python-versions = ">=3.7" -files = [ - {file = "pymongo-4.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c011bd5ad03cc096f99ffcfdd18a1817354132c1331bed7a837a25226659845f"}, - {file = "pymongo-4.6.0-cp310-cp310-manylinux1_i686.whl", hash = "sha256:5e63146dbdb1eac207464f6e0cfcdb640c9c5ff0f57b754fa96fe252314a1dc6"}, - {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:2972dd1f1285866aba027eff2f4a2bbf8aa98563c2ced14cb34ee5602b36afdf"}, - {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:a0be99b599da95b7a90a918dd927b20c434bea5e1c9b3efc6a3c6cd67c23f813"}, - {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:9b0f98481ad5dc4cb430a60bbb8869f05505283b9ae1c62bdb65eb5e020ee8e3"}, - {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:256c503a75bd71cf7fb9ebf889e7e222d49c6036a48aad5a619f98a0adf0e0d7"}, - {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:b4ad70d7cac4ca0c7b31444a0148bd3af01a2662fa12b1ad6f57cd4a04e21766"}, - {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5717a308a703dda2886a5796a07489c698b442f5e409cf7dc2ac93de8d61d764"}, - {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f7f9feecae53fa18d6a3ea7c75f9e9a1d4d20e5c3f9ce3fba83f07bcc4eee2"}, - {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128b1485753106c54af481789cdfea12b90a228afca0b11fb3828309a907e10e"}, - {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3077a31633beef77d057c6523f5de7271ddef7bde5e019285b00c0cc9cac1e3"}, - {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebf02c32afa6b67e5861a27183dd98ed88419a94a2ab843cc145fb0bafcc5b28"}, - {file = "pymongo-4.6.0-cp310-cp310-win32.whl", hash = "sha256:b14dd73f595199f4275bed4fb509277470d9b9059310537e3b3daba12b30c157"}, - {file = "pymongo-4.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:8adf014f2779992eba3b513e060d06f075f0ab2fb3ad956f413a102312f65cdf"}, - {file = "pymongo-4.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba51129fcc510824b6ca6e2ce1c27e3e4d048b6e35d3ae6f7e517bed1b8b25ce"}, - {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2973f113e079fb98515722cd728e1820282721ec9fd52830e4b73cabdbf1eb28"}, - {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af425f323fce1b07755edd783581e7283557296946212f5b1a934441718e7528"}, - {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ec71ac633b126c0775ed4604ca8f56c3540f5c21a1220639f299e7a544b55f9"}, - {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ec6c20385c5a58e16b1ea60c5e4993ea060540671d7d12664f385f2fb32fe79"}, - {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85f2cdc400ee87f5952ebf2a117488f2525a3fb2e23863a8efe3e4ee9e54e4d1"}, - {file = "pymongo-4.6.0-cp311-cp311-win32.whl", hash = "sha256:7fc2bb8a74dcfcdd32f89528e38dcbf70a3a6594963d60dc9595e3b35b66e414"}, - {file = "pymongo-4.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6695d7136a435c1305b261a9ddb9b3ecec9863e05aab3935b96038145fd3a977"}, - {file = "pymongo-4.6.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d603edea1ff7408638b2504905c032193b7dcee7af269802dbb35bc8c3310ed5"}, - {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79f41576b3022c2fe9780ae3e44202b2438128a25284a8ddfa038f0785d87019"}, - {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f2af6cf82509b15093ce3569229e0d53c90ad8ae2eef940652d4cf1f81e045"}, - {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecd9e1fa97aa11bf67472220285775fa15e896da108f425e55d23d7540a712ce"}, - {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d2be5c9c3488fa8a70f83ed925940f488eac2837a996708d98a0e54a861f212"}, - {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab6bcc8e424e07c1d4ba6df96f7fb963bcb48f590b9456de9ebd03b88084fe8"}, - {file = "pymongo-4.6.0-cp312-cp312-win32.whl", hash = "sha256:47aa128be2e66abd9d1a9b0437c62499d812d291f17b55185cb4aa33a5f710a4"}, - {file = "pymongo-4.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:014e7049dd019a6663747ca7dae328943e14f7261f7c1381045dfc26a04fa330"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:288c21ab9531b037f7efa4e467b33176bc73a0c27223c141b822ab4a0e66ff2a"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:747c84f4e690fbe6999c90ac97246c95d31460d890510e4a3fa61b7d2b87aa34"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:055f5c266e2767a88bb585d01137d9c7f778b0195d3dbf4a487ef0638be9b651"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:82e620842e12e8cb4050d2643a81c8149361cd82c0a920fa5a15dc4ca8a4000f"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:6b18276f14b4b6d92e707ab6db19b938e112bd2f1dc3f9f1a628df58e4fd3f0d"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:680fa0fc719e1a3dcb81130858368f51d83667d431924d0bcf249644bce8f303"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:3919708594b86d0f5cdc713eb6fccd3f9b9532af09ea7a5d843c933825ef56c4"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db082f728160369d9a6ed2e722438291558fc15ce06d0a7d696a8dad735c236b"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e4ed21029d80c4f62605ab16398fe1ce093fff4b5f22d114055e7d9fbc4adb0"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bea9138b0fc6e2218147e9c6ce1ff76ff8e29dc00bb1b64842bd1ca107aee9f"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a0269811661ba93c472c8a60ea82640e838c2eb148d252720a09b5123f2c2fe"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d6a1b1361f118e7fefa17ae3114e77f10ee1b228b20d50c47c9f351346180c8"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e3b0127b260d4abae7b62203c4c7ef0874c901b55155692353db19de4b18bc4"}, - {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a49aca4d961823b2846b739380c847e8964ff7ae0f0a683992b9d926054f0d6d"}, - {file = "pymongo-4.6.0-cp37-cp37m-win32.whl", hash = "sha256:09c7de516b08c57647176b9fc21d929d628e35bcebc7422220c89ae40b62126a"}, - {file = "pymongo-4.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:81dd1308bd5630d2bb5980f00aa163b986b133f1e9ed66c66ce2a5bc3572e891"}, - {file = "pymongo-4.6.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2f8c04277d879146eacda920476e93d520eff8bec6c022ac108cfa6280d84348"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5802acc012bbb4bce4dff92973dff76482f30ef35dd4cb8ab5b0e06aa8f08c80"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ccd785fafa1c931deff6a7116e9a0d402d59fabe51644b0d0c268295ff847b25"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fe03bf25fae4b95d8afe40004a321df644400fdcba4c8e5e1a19c1085b740888"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:2ca0ba501898b2ec31e6c3acf90c31910944f01d454ad8e489213a156ccf1bda"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:10a379fb60f1b2406ae57b8899bacfe20567918c8e9d2d545e1b93628fcf2050"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a4dc1319d0c162919ee7f4ee6face076becae2abbd351cc14f1fe70af5fb20d9"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ddef295aaf80cefb0c1606f1995899efcb17edc6b327eb6589e234e614b87756"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:518c90bdd6e842c446d01a766b9136fec5ec6cc94f3b8c3f8b4a332786ee6b64"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b80a4ee19b3442c57c38afa978adca546521a8822d663310b63ae2a7d7b13f3a"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb438a8bf6b695bf50d57e6a059ff09652a07968b2041178b3744ea785fcef9b"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3db7d833a7c38c317dc95b54e27f1d27012e031b45a7c24e360b53197d5f6e7"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3729b8db02063da50eeb3db88a27670d85953afb9a7f14c213ac9e3dca93034b"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:39a1cd5d383b37285641d5a7a86be85274466ae336a61b51117155936529f9b3"}, - {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7b0e6361754ac596cd16bfc6ed49f69ffcd9b60b7bc4bcd3ea65c6a83475e4ff"}, - {file = "pymongo-4.6.0-cp38-cp38-win32.whl", hash = "sha256:806e094e9e85d8badc978af8c95b69c556077f11844655cb8cd2d1758769e521"}, - {file = "pymongo-4.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1394c4737b325166a65ae7c145af1ebdb9fb153ebedd37cf91d676313e4a67b8"}, - {file = "pymongo-4.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a8273e1abbcff1d7d29cbbb1ea7e57d38be72f1af3c597c854168508b91516c2"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:e16ade71c93f6814d095d25cd6d28a90d63511ea396bd96e9ffcb886b278baaa"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:325701ae7b56daa5b0692305b7cb505ca50f80a1288abb32ff420a8a209b01ca"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:cc94f9fea17a5af8cf1a343597711a26b0117c0b812550d99934acb89d526ed2"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:21812453354b151200034750cd30b0140e82ec2a01fd4357390f67714a1bfbde"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:0634994b026336195778e5693583c060418d4ab453eff21530422690a97e1ee8"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ad4f66fbb893b55f96f03020e67dcab49ffde0177c6565ccf9dec4fdf974eb61"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:2703a9f8f5767986b4f51c259ff452cc837c5a83c8ed5f5361f6e49933743b2f"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bafea6061d63059d8bc2ffc545e2f049221c8a4457d236c5cd6a66678673eab"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f28ae33dc5a0b9cee06e95fd420e42155d83271ab75964baf747ce959cac5f52"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16a534da0e39785687b7295e2fcf9a339f4a20689024983d11afaa4657f8507"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef67fedd863ffffd4adfd46d9d992b0f929c7f61a8307366d664d93517f2c78e"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05c30fd35cc97f14f354916b45feea535d59060ef867446b5c3c7f9b609dd5dc"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c63e3a2e8fb815c4b1f738c284a4579897e37c3cfd95fdb199229a1ccfb638a"}, - {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e5e193f89f4f8c1fe273f9a6e6df915092c9f2af6db2d1afb8bd53855025c11f"}, - {file = "pymongo-4.6.0-cp39-cp39-win32.whl", hash = "sha256:a09bfb51953930e7e838972ddf646c5d5f984992a66d79da6ba7f6a8d8a890cd"}, - {file = "pymongo-4.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:107a234dc55affc5802acb3b6d83cbb8c87355b38a9457fcd8806bdeb8bce161"}, - {file = "pymongo-4.6.0.tar.gz", hash = "sha256:fb1c56d891f9e34303c451998ef62ba52659648bb0d75b03c5e4ac223a3342c2"}, -] - -[package.dependencies] -dnspython = ">=1.16.0,<3.0.0" - -[package.extras] -aws = ["pymongo-auth-aws (<2.0.0)"] -encryption = ["certifi", "pymongo[aws]", "pymongocrypt (>=1.6.0,<2.0.0)"] -gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] -ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] -snappy = ["python-snappy"] -test = ["pytest (>=7)"] -zstd = ["zstandard"] - -[[package]] -name = "quart" -version = "0.19.3" -description = "A Python ASGI web microframework with the same API as Flask" -optional = false -python-versions = ">=3.8" -files = [ - {file = "quart-0.19.3-py3-none-any.whl", hash = "sha256:3bfb433bb4edfcb13eb908096e579cb99eaae0b57ef05a3edfde797a2b12518a"}, - {file = "quart-0.19.3.tar.gz", hash = "sha256:530ab6cc9b9e694ba48c00085a7bf881e2f7f23bdc51e27281e6739eee36c13f"}, -] - -[package.dependencies] -aiofiles = "*" -blinker = ">=1.6" -click = ">=8.0.0" -flask = ">=3.0.0" -hypercorn = ">=0.11.2" -itsdangerous = "*" -jinja2 = "*" -markupsafe = "*" -werkzeug = ">=3.0.0" - -[package.extras] -docs = ["pydata_sphinx_theme"] -dotenv = ["python-dotenv"] - -[[package]] -name = "werkzeug" -version = "3.0.1" -description = "The comprehensive WSGI web application library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, - {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, -] - -[package.dependencies] -MarkupSafe = ">=2.1.1" - -[package.extras] -watchdog = ["watchdog (>=2.3)"] - -[[package]] -name = "wsproto" -version = "1.2.0" -description = "WebSockets state-machine based protocol implementation" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, - {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, -] - -[package.dependencies] -h11 = ">=0.9.0,<1" - -[metadata] -lock-version = "2.0" -python-versions = "^3.11" -content-hash = "b30a0fc7ca00bc90da8a9e05c6126c1731d3e1619e87c3221f4ea18b56e049d7"