diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
new file mode 100644
index 0000000..9a39d91
--- /dev/null
+++ b/.github/workflows/pr.yml
@@ -0,0 +1,44 @@
+name: Build to test PR
+
+on:
+ pull_request:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ container: archlinux:latest
+ steps:
+ - name: Install system dependencies
+ run: pacman -Syu --noconfirm base-devel vulkan-headers vulkan-icd-loader libx11 libgl git
+ - uses: actions/checkout@v5
+ - name: Build binary
+ run: make release
+ - name: Create tarball
+ run: |
+ ARCH=$(uname -m)
+ if [ "$ARCH" = "x86_64" ]; then
+ ARCH_NAME="x86_64"
+ elif [ "$ARCH" = "aarch64" ]; then
+ ARCH_NAME="aarch64"
+ fi
+ mkdir -p krosshair-linux-${ARCH_NAME}
+ cp lib/krosshair.so krosshair-linux-${ARCH_NAME}/
+ cp krosshair.json krosshair-linux-${ARCH_NAME}/
+ cp LICENSE krosshair-linux-${ARCH_NAME}/ 2>/dev/null || true
+ tar -czf krosshair-linux-${ARCH_NAME}.tar.gz -C krosshair-linux-${ARCH_NAME} .
+ - name: Generate checksum
+ run: |
+ ARCH=$(uname -m)
+ if [ "$ARCH" = "x86_64" ]; then
+ ARCH_NAME="x86_64"
+ elif [ "$ARCH" = "aarch64" ]; then
+ ARCH_NAME="aarch64"
+ fi
+ sha256sum krosshair-linux-${ARCH_NAME}.tar.gz > krosshair-linux-${ARCH_NAME}.sha256
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v6
+ with:
+ name: krosshair-linux
+ path: |
+ krosshair-linux-*.tar.gz
+ krosshair-linux-*.sha256
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..74677fd
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,225 @@
+name: Semantic Versioning and Release
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ check-skip:
+ runs-on: ubuntu-latest
+ container: archlinux:latest
+ outputs:
+ should_skip: ${{ steps.check.outputs.skip }}
+ steps:
+ - name: Check if this is a version bump commit
+ id: check
+ run: |
+ MSG="${{ github.event.head_commit.message }}"
+ if echo "$MSG" | grep -q "^chore: bump version"; then
+ echo "skip=true" >> $GITHUB_OUTPUT
+ else
+ echo "skip=false" >> $GITHUB_OUTPUT
+ fi
+
+ version:
+ needs: [check-skip]
+ if: needs.check-skip.outputs.should_skip == 'false'
+ runs-on: ubuntu-latest
+ container: archlinux:latest
+ outputs:
+ new_version: ${{ steps.bump.outputs.new_version }}
+ old_version: ${{ steps.bump.outputs.old_version }}
+ steps:
+ - name: Install dependencies
+ run: pacman -Syu --noconfirm git
+ - uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+ - name: Determine version bump
+ id: bump
+ run: |
+ git config --global --add safe.directory "$GITHUB_WORKSPACE"
+ LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
+ OLD_VERSION=${LAST_TAG#v}
+ IFS='.' read -r MAJOR MINOR PATCH <<< "$OLD_VERSION"
+
+ COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=%s 2>/dev/null || git log HEAD --pretty=%s)
+
+ if echo "$COMMITS" | grep -q "^feat:"; then
+ MINOR=$((MINOR + 1))
+ PATCH=0
+ elif echo "$COMMITS" | grep -q "^chore:\|^fix:\|^docs:"; then
+ PATCH=$((PATCH + 1))
+ fi
+
+ if echo "$COMMITS" | grep -q "^BREAKING CHANGE:"; then
+ MAJOR=$((MAJOR + 1))
+ MINOR=0
+ PATCH=0
+ fi
+
+ NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
+ echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
+ echo "old_version=$OLD_VERSION" >> $GITHUB_OUTPUT
+ echo "Bumping $OLD_VERSION -> $NEW_VERSION"
+
+ build:
+ needs: [version]
+ runs-on: ubuntu-latest
+ container: archlinux:latest
+ steps:
+ - name: Install system dependencies
+ run: pacman -Syu --noconfirm base-devel vulkan-headers vulkan-icd-loader libx11 libgl git
+ - uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+ - name: Build binary
+ run: make release
+ - name: Determine architecture
+ id: arch
+ run: |
+ ARCH=$(uname -m)
+ echo "arch=$ARCH" >> $GITHUB_OUTPUT
+ if [ "$ARCH" = "x86_64" ]; then
+ echo "arch_name=x86_64" >> $GITHUB_OUTPUT
+ elif [ "$ARCH" = "aarch64" ]; then
+ echo "arch_name=aarch64" >> $GITHUB_OUTPUT
+ fi
+ - name: Update PKGBUILD versions
+ run: |
+ VERSION=${{ needs.version.outputs.new_version }}
+ sed -i "s/pkgver=.*/pkgver=$VERSION/" PKGBUILD
+ sed -i "s/pkgver=.*/pkgver=$VERSION/" aur/PKGBUILD
+ - name: Create tarball
+ run: |
+ ARCH_NAME=${{ steps.arch.outputs.arch_name }}
+ VERSION=${{ needs.version.outputs.new_version }}
+ mkdir -p krosshair-linux-${ARCH_NAME}/crosshairs
+ cp lib/krosshair.so krosshair-linux-${ARCH_NAME}/
+ cp krosshair.json krosshair-linux-${ARCH_NAME}/
+ cp LICENSE krosshair-linux-${ARCH_NAME}/ 2>/dev/null || true
+ cp -r crosshairs/* krosshair-linux-${ARCH_NAME}/crosshairs/ 2>/dev/null || true
+ tar -czf krosshair-linux-${ARCH_NAME}.tar.gz -C krosshair-linux-${ARCH_NAME} .
+ sha256sum krosshair-linux-${ARCH_NAME}.tar.gz > krosshair-linux-${ARCH_NAME}.sha256
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v6
+ with:
+ name: build-output
+ path: |
+ PKGBUILD
+ aur/
+ krosshair-linux-*.tar.gz
+ krosshair-linux-*.sha256
+
+ release:
+ needs: [version, build]
+ runs-on: ubuntu-latest
+ container: archlinux:latest
+ permissions:
+ contents: write
+ steps:
+ - name: Install dependencies
+ run: pacman -Syu --noconfirm git github-cli
+ - uses: actions/checkout@v5
+ with:
+ ref: main
+ fetch-depth: 0
+ - uses: actions/download-artifact@v7
+ with:
+ name: build-output
+ path: .
+ - name: Configure git
+ run: |
+ git config --global --add safe.directory "$GITHUB_WORKSPACE"
+ git config user.name "GitHub Action"
+ git config user.email "action@github.com"
+ gh auth setup-git
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Commit version bump and tag
+ run: |
+ VERSION=${{ needs.version.outputs.new_version }}
+ git add PKGBUILD aur/
+ git commit -m "chore: bump version to v${VERSION}" || echo "No changes to commit"
+ git tag "v${VERSION}" -f
+ git push origin main
+ git push origin "v${VERSION}" -f
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Create GitHub release
+ run: |
+ VERSION=${{ needs.version.outputs.new_version }}
+ ARCH_NAME=$(uname -m)
+ if [ "$ARCH_NAME" = "x86_64" ]; then
+ ARCH_NAME="x86_64"
+ elif [ "$ARCH_NAME" = "aarch64" ]; then
+ ARCH_NAME="aarch64"
+ fi
+ gh release delete "v${VERSION}" --yes 2>/dev/null || true
+ gh release create "v${VERSION}" \
+ --title "v${VERSION}" \
+ --notes "Release v${VERSION}" \
+ krosshair-linux-${ARCH_NAME}.tar.gz \
+ krosshair-linux-${ARCH_NAME}.sha256
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ aur:
+ needs: [version, build, release]
+ runs-on: ubuntu-latest
+ container: archlinux:latest
+ steps:
+ - uses: actions/download-artifact@v7
+ with:
+ name: build-output
+ path: .
+ - name: Push to AUR
+ run: |
+ VERSION=${{ needs.version.outputs.new_version }}
+ pacman -Syu --noconfirm openssh git sudo base-devel
+
+ useradd -m builduser
+ echo "builduser ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
+
+ mkdir -p /home/builduser/.ssh
+ echo "$AUR_SSH_KEY" > /home/builduser/.ssh/aur
+ chmod 600 /home/builduser/.ssh/aur
+ chmod 700 /home/builduser/.ssh
+ chown -R builduser:builduser /home/builduser/.ssh
+ ssh-keyscan aur.archlinux.org >> /home/builduser/.ssh/known_hosts
+
+ cp -r aur /home/builduser/aur
+ chown -R builduser:builduser /home/builduser/aur
+
+ sudo -u builduser VERSION="$VERSION" GIT_SSH_COMMAND="ssh -i /home/builduser/.ssh/aur -o StrictHostKeyChecking=no" bash -c '
+ cd /home/builduser
+ git clone ssh://aur@aur.archlinux.org/krosshair.git aur-push 2>&1 || true
+ if [ -d "aur-push/.git" ]; then
+ echo "Cloned existing AUR repo"
+ cd aur-push
+ git fetch origin
+ git reset --hard origin/master
+ cd ..
+ else
+ echo "Creating new AUR repo (clone failed)"
+ mkdir -p aur-push
+ cd aur-push
+ git init
+ git remote add origin ssh://aur@aur.archlinux.org/krosshair.git
+ cd ..
+ fi
+
+ rm -rf aur-push/*
+ cp aur/PKGBUILD aur-push/
+ cp aur/krosshair.install aur-push/
+
+ cd aur-push
+ makepkg --printsrcinfo > .SRCINFO
+ git config user.name "GitHub Action"
+ git config user.email "action@github.com"
+ git add PKGBUILD .SRCINFO krosshair.install
+ git commit -m "Update to v${VERSION}" || echo "No changes to commit"
+ git push origin master
+ '
+ env:
+ AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/Makefile b/Makefile
index 9f5f80d..e1acd25 100644
--- a/Makefile
+++ b/Makefile
@@ -1,15 +1,25 @@
LIBRARY = lib/krosshair.so
-CCFLAGS = -Wall -std=c99 -ggdb -fPIC -shared -I./include/
+CCFLAGS = -Wall -std=c99 -fPIC -shared -I./include/
LDFLAGS = -ldl -lm
+# Debug build by default; use `make release` or `make RELEASE=1` for release
+ifdef RELEASE
+CCFLAGS += -O2 -DNDEBUG
+else
+CCFLAGS += -ggdb
+endif
+
SOURCES = $(shell find src -type f -name "*.c")
-.PHONY: all clean install
+.PHONY: all release clean install
all:
mkdir -p lib
- $(CC) $(LDFLAGS) $(CCFLAGS) $(SOURCES) -o $(LIBRARY)
+ $(CC) $(CCFLAGS) $(SOURCES) $(LDFLAGS) -o $(LIBRARY)
+
+release:
+ $(MAKE) RELEASE=1 all
install:
sudo mkdir -p /usr/lib/krosshair
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 0000000..54ddf0b
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,28 @@
+# Maintainer: fibsussy
+pkgname=krosshair
+pkgver=0.2.5
+pkgrel=1
+pkgdesc="Crosshair overlay for games on linux using Vulkan"
+arch=('x86_64' 'aarch64')
+url="https://github.com/fibsussy/krosshair"
+license=('GPL3')
+depends=('vulkan-icd-loader' 'libgl' 'libx11')
+makedepends=('gcc' 'make' 'vulkan-headers')
+install=krosshair.install
+options=('!debug')
+
+build() {
+ cd "$startdir"
+ make release
+}
+
+package() {
+ cd "$startdir"
+
+ install -Dm755 lib/krosshair.so "$pkgdir/usr/lib/krosshair/krosshair.so"
+ install -Dm644 krosshair.json "$pkgdir/usr/share/vulkan/implicit_layer.d/krosshair.json"
+ install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
+
+ install -dm755 "$pkgdir/usr/share/krosshair/crosshairs"
+ cp -r crosshairs/* "$pkgdir/usr/share/krosshair/crosshairs/" || true
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 04ecd04..eb2dd17 100644
--- a/README.md
+++ b/README.md
@@ -5,23 +5,47 @@ A crosshair overlay for games on linux using vulkan.

## Installation
-`make && make install`
-On nixos-unstable: Add `krosshair` (or clone and add `(callPackage /derivation.nix { })`) to packages and `nixos-rebuild switch`.
+### AUR
+
+```bash
+yay -S krosshair
+```
+
+### From source
+
+```bash
+git clone https://github.com/fibsussy/krosshair.git
+cd krosshair
+make
+```
## Usage
-Simply start the your game with `KROSSHAIR=1` set. When using steam, put `KROSSHAIR=1 %command%` into the launch options of your game. Additionally you can use the following variables to customize your crosshair.
-- `KROSSHAIR_IMG=/path/to/crosshair-image.png` (i've provided some crosshairs in [here](https://github.com/krob64/krosshair/tree/main/crosshairs), copy them wherever you like)
- - alternatively you can use a [crosshair generator site](https://crosshair.themeta.gg/) (or [this one](https://guywiddacaptain.github.io/aimX/))
-- `KROSSHAIR_SCALE=1.0` (scaling the scrosshair may make it look blurry, i'd suggest to just create a bigger/smaller crosshair)
+
+```bash
+export KROSSHAIR=1
+your-game
+```
+
+For Steam games, add `KROSSHAIR=1 %command%` to launch options.
+
+## crosshair-maker integration
+
+krosshair works out of the box with [crosshair-maker](https://github.com/fibsussy/crosshair-maker), a crosshair overlay creator with SVG rendering and preview. The currently selected crosshair is automatically exported to `~/.config/crosshair-maker/projects/current.png`, which krosshair picks up as its default — just launch your game with `KROSSHAIR=1` and go.
+
+You can override the image with `export KROSSHAIR_IMG=/path/to/crosshair.png` if you want to use a custom file.
+
+To test the full setup together with `vkcube`:
+
+```sh
+$ yay -Sy krosshair crosshair-maker vulkan-tools
+$ KROSSHAIR=1 vkcube &
+$ crosshair-maker &
+```
## Can i get banned for this?
I don't know, use at your own risk. I've only used it in Quake Champions and STRAFTAT, both of which don't really have an anticheat.
-## Setting up requirements
-Needs Vulkan libs to build.
-All requirements can be set up with `nix-shell' when nix is installed.
-
## Issues
As of now, the overlay leaks a bit of memory everytime you alt-tab out of/into the game, as well as everytime the window is being resized and upon resolution changes. It's not a big leak and shouldn't cause any problems, but it's still worth noting.
I've tried fixing it multiple times but have always hit a dead-end. If someone more experienced with vulkan wants to help, take a look at [this issue](https://github.com/krob64/krosshair/issues/1). I know it's a bit of a mess, this whole project is based on a morally questionable apex legends project which i'm unsure if i should link to it here, coupled with me jumping into it right after completing the vulkan tutorial.
diff --git a/aur/.SRCINFO b/aur/.SRCINFO
new file mode 100644
index 0000000..106faa8
--- /dev/null
+++ b/aur/.SRCINFO
@@ -0,0 +1,13 @@
+pkgbase = krosshair
+pkgdesc = Crosshair overlay for games on linux using Vulkan
+pkgver = 0.1.0
+pkgrel = 1
+url = https://github.com/fibsussy/krosshair
+arch = x86_64
+arch = aarch64
+license = GPL3
+depends = libvulkan-driver
+depends = libgl
+depends = libx11
+
+pkgname = krosshair
\ No newline at end of file
diff --git a/aur/PKGBUILD b/aur/PKGBUILD
new file mode 100644
index 0000000..2a83a81
--- /dev/null
+++ b/aur/PKGBUILD
@@ -0,0 +1,34 @@
+# Maintainer: fibsussy
+pkgname=krosshair
+pkgver=0.2.5
+pkgrel=1
+pkgdesc="Crosshair overlay for games on linux using Vulkan"
+arch=('x86_64' 'aarch64')
+url="https://github.com/fibsussy/krosshair"
+license=('GPL3')
+depends=('vulkan-icd-loader' 'libgl' 'libx11')
+makedepends=()
+install=krosshair.install
+options=('!debug')
+
+_arch="$CARCH"
+if [ "$_arch" = "x86_64" ]; then
+ _arch="x86_64"
+elif [ "$_arch" = "aarch64" ]; then
+ _arch="aarch64"
+fi
+
+source=(
+ "krosshair-${pkgver}-linux-${_arch}.tar.gz::https://github.com/fibsussy/krosshair/releases/download/v${pkgver}/krosshair-linux-${_arch}.tar.gz"
+ "LICENSE-${pkgver}::https://raw.githubusercontent.com/fibsussy/krosshair/v${pkgver}/LICENSE"
+)
+sha256sums=('SKIP' 'SKIP')
+
+package() {
+ install -Dm755 "$srcdir/krosshair.so" "$pkgdir/usr/lib/krosshair/krosshair.so"
+ install -Dm644 "$srcdir/krosshair.json" "$pkgdir/usr/share/vulkan/implicit_layer.d/krosshair.json"
+ install -Dm644 "$srcdir/LICENSE-${pkgver}" "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
+
+ install -dm755 "$pkgdir/usr/share/krosshair/crosshairs"
+ cp -r "$srcdir/crosshairs/"* "$pkgdir/usr/share/krosshair/crosshairs/" 2>/dev/null || true
+}
diff --git a/aur/krosshair.install b/aur/krosshair.install
new file mode 100644
index 0000000..de5c536
--- /dev/null
+++ b/aur/krosshair.install
@@ -0,0 +1,25 @@
+post_install() {
+ echo ""
+ echo "==> krosshair installed successfully!"
+ echo ""
+ echo "==> Getting Started:"
+ echo ""
+ echo " KROSSHAIR=1 your-game"
+ echo ""
+ echo " For Steam games, add to launch options:"
+ echo " KROSSHAIR=1 %command%"
+ echo ""
+ echo "==> crosshair-maker integration:"
+ echo " If crosshair-maker is installed, your currently selected"
+ echo " crosshair is used automatically. Override with:"
+ echo " export KROSSHAIR_IMG=/path/to/crosshair.png"
+ echo ""
+ echo "==> For more info: https://github.com/krob64/krosshair"
+ echo ""
+}
+
+post_upgrade() {
+ echo ""
+ echo "==> krosshair upgraded successfully!"
+ echo ""
+}
\ No newline at end of file
diff --git a/krosshair.install b/krosshair.install
new file mode 100644
index 0000000..de5c536
--- /dev/null
+++ b/krosshair.install
@@ -0,0 +1,25 @@
+post_install() {
+ echo ""
+ echo "==> krosshair installed successfully!"
+ echo ""
+ echo "==> Getting Started:"
+ echo ""
+ echo " KROSSHAIR=1 your-game"
+ echo ""
+ echo " For Steam games, add to launch options:"
+ echo " KROSSHAIR=1 %command%"
+ echo ""
+ echo "==> crosshair-maker integration:"
+ echo " If crosshair-maker is installed, your currently selected"
+ echo " crosshair is used automatically. Override with:"
+ echo " export KROSSHAIR_IMG=/path/to/crosshair.png"
+ echo ""
+ echo "==> For more info: https://github.com/krob64/krosshair"
+ echo ""
+}
+
+post_upgrade() {
+ echo ""
+ echo "==> krosshair upgraded successfully!"
+ echo ""
+}
\ No newline at end of file
diff --git a/shaders/dynamic.frag b/shaders/dynamic.frag
new file mode 100644
index 0000000..e3e9a88
--- /dev/null
+++ b/shaders/dynamic.frag
@@ -0,0 +1,141 @@
+#version 450
+
+layout(location = 0) in vec2 frag_tex_coord;
+layout(location = 0) out vec4 out_color;
+
+layout(binding = 0) uniform sampler2D mask_sampler;
+layout(binding = 1) uniform sampler2D game_fb_sampler;
+
+// Push constants: 76 bytes total. Fixed processing order.
+layout(push_constant) uniform PushConstants {
+ vec2 quad_ndc_min; // NDC min corner of the mask quad
+ vec2 quad_ndc_size; // NDC size of the mask quad
+ float invert_str; // Invert strength (0 = disabled)
+ float dodge_str; // Dodge strength
+ float dodge_r; // Dodge tint R
+ float dodge_g; // Dodge tint G
+ float dodge_b; // Dodge tint B
+ float burn_str; // Burn strength
+ float burn_r; // Burn tint R
+ float burn_g; // Burn tint G
+ float burn_b; // Burn tint B
+ float complement_str; // Complement strength
+ float lumainvert_str; // Luma Invert strength
+ float huerotate_str; // Hue Rotate strength
+ float huerotate_angle; // Hue Rotate angle (degrees)
+ float saturate_str; // Saturate strength
+ float saturate_amount; // Saturate amount (0-1)
+ float opacity; // Overall effect opacity (0-1)
+} pc;
+
+// ── RGB <-> HSL conversions ──────────────────────────────────────
+
+vec3 rgb_to_hsl(vec3 c) {
+ float mx = max(c.r, max(c.g, c.b));
+ float mn = min(c.r, min(c.g, c.b));
+ float l = (mx + mn) * 0.5;
+ float d = mx - mn;
+
+ if (d < 0.00001) return vec3(0.0, 0.0, l);
+
+ float s = (l > 0.5) ? d / (2.0 - mx - mn) : d / (mx + mn);
+ float h;
+
+ if (mx == c.r)
+ h = mod((c.g - c.b) / d, 6.0);
+ else if (mx == c.g)
+ h = (c.b - c.r) / d + 2.0;
+ else
+ h = (c.r - c.g) / d + 4.0;
+
+ h /= 6.0;
+ if (h < 0.0) h += 1.0;
+
+ return vec3(h, s, l);
+}
+
+float hue_to_rgb(float p, float q, float t) {
+ if (t < 0.0) t += 1.0;
+ if (t > 1.0) t -= 1.0;
+ if (t < 1.0/6.0) return p + (q - p) * 6.0 * t;
+ if (t < 1.0/2.0) return q;
+ if (t < 2.0/3.0) return p + (q - p) * (2.0/3.0 - t) * 6.0;
+ return p;
+}
+
+vec3 hsl_to_rgb(vec3 hsl) {
+ float h = hsl.x, s = hsl.y, l = hsl.z;
+ if (s < 0.00001) return vec3(l);
+ float q = (l < 0.5) ? l * (1.0 + s) : l + s - l * s;
+ float p = 2.0 * l - q;
+ return vec3(
+ hue_to_rgb(p, q, h + 1.0/3.0),
+ hue_to_rgb(p, q, h),
+ hue_to_rgb(p, q, h - 1.0/3.0)
+ );
+}
+
+void main() {
+ // Continuous mask: use red channel (grayscale) as blend factor
+ float mask = texture(mask_sampler, frag_tex_coord).r;
+ if (mask < 0.004) discard; // ~1/255, skip fully transparent pixels
+
+ // Sample game framebuffer
+ vec2 ndc = pc.quad_ndc_min + frag_tex_coord * pc.quad_ndc_size;
+ vec2 screen_uv = ndc * 0.5 + 0.5;
+ vec3 original = texture(game_fb_sampler, screen_uv).rgb;
+ vec3 color = original;
+
+ // Fixed order: invert → dodge → burn → complement → luma_invert → hue_rotate → saturate
+
+ if (pc.invert_str > 0.0) {
+ color = mix(color, vec3(1.0) - color, pc.invert_str);
+ }
+
+ if (pc.dodge_str > 0.0) {
+ vec3 dodged = min(color + vec3(pc.dodge_r, pc.dodge_g, pc.dodge_b), vec3(1.0));
+ color = mix(color, dodged, pc.dodge_str);
+ }
+
+ if (pc.burn_str > 0.0) {
+ vec3 burned = max(color - vec3(pc.burn_r, pc.burn_g, pc.burn_b), vec3(0.0));
+ color = mix(color, burned, pc.burn_str);
+ }
+
+ if (pc.complement_str > 0.0) {
+ vec3 hsl = rgb_to_hsl(color);
+ hsl.x = mod(hsl.x + 0.5, 1.0);
+ hsl.y = 1.0;
+ hsl.z = 1.0 - hsl.z;
+ color = mix(color, hsl_to_rgb(hsl), pc.complement_str);
+ }
+
+ if (pc.lumainvert_str > 0.0) {
+ vec3 hsl = rgb_to_hsl(color);
+ hsl.z = 1.0 - hsl.z;
+ color = mix(color, hsl_to_rgb(hsl), pc.lumainvert_str);
+ }
+
+ if (pc.huerotate_str > 0.0) {
+ vec3 hsl = rgb_to_hsl(color);
+ float original_l = hsl.z;
+ hsl.x = mod(hsl.x + pc.huerotate_angle / 360.0, 1.0);
+ // hsl.y keeps original saturation, L = 1.0 - original_L matches crosshair-maker
+ hsl.z = 1.0 - original_l;
+ color = mix(color, hsl_to_rgb(hsl), pc.huerotate_str);
+ }
+
+ if (pc.saturate_str > 0.0) {
+ vec3 hsl = rgb_to_hsl(color);
+ // Match crosshair-maker: hsl_to_rgb_f32(h, amount, 1.0 - original_l)
+ float original_l = hsl.z;
+ hsl.y = pc.saturate_amount;
+ hsl.z = 1.0 - original_l; // Match crosshair-maker formula
+ color = mix(color, hsl_to_rgb(hsl), pc.saturate_str);
+ }
+
+ // Apply overall opacity, then blend by mask alpha (supports anti-aliased edges)
+ color = mix(original, color, mask * pc.opacity);
+
+ out_color = vec4(color, 1.0);
+}
diff --git a/shaders/dynamic_frag.spv b/shaders/dynamic_frag.spv
new file mode 100644
index 0000000..5e720a1
Binary files /dev/null and b/shaders/dynamic_frag.spv differ
diff --git a/shaders/dynamic_spv.h b/shaders/dynamic_spv.h
new file mode 100644
index 0000000..039786a
--- /dev/null
+++ b/shaders/dynamic_spv.h
@@ -0,0 +1,9 @@
+// Auto-generated SPIR-V headers for dynamic shader
+// DO NOT EDIT — regenerate from .frag/.vert sources
+
+static const unsigned char dynamic_vert_spv[] = {0x03,0x02,0x23,0x07,0x00,0x00,0x01,0x00,0x0b,0x00,0x08,0x00,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x0b,0x00,0x06,0x00,0x01,0x00,0x00,0x00,0x47,0x4c,0x53,0x4c,0x2e,0x73,0x74,0x64,0x2e,0x34,0x35,0x30,0x00,0x00,0x00,0x00,0x0e,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0f,0x00,0x09,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x6d,0x61,0x69,0x6e,0x00,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x1d,0x00,0x00,0x00,0x03,0x00,0x03,0x00,0x02,0x00,0x00,0x00,0xc2,0x01,0x00,0x00,0x05,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x6d,0x61,0x69,0x6e,0x00,0x00,0x00,0x00,0x05,0x00,0x06,0x00,0x0b,0x00,0x00,0x00,0x67,0x6c,0x5f,0x50,0x65,0x72,0x56,0x65,0x72,0x74,0x65,0x78,0x00,0x00,0x00,0x00,0x06,0x00,0x06,0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x06,0x00,0x07,0x00,0x0b,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a,0x65,0x00,0x00,0x00,0x00,0x06,0x00,0x07,0x00,0x0b,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x67,0x6c,0x5f,0x43,0x6c,0x69,0x70,0x44,0x69,0x73,0x74,0x61,0x6e,0x63,0x65,0x00,0x06,0x00,0x07,0x00,0x0b,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x67,0x6c,0x5f,0x43,0x75,0x6c,0x6c,0x44,0x69,0x73,0x74,0x61,0x6e,0x63,0x65,0x00,0x05,0x00,0x03,0x00,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x05,0x00,0x12,0x00,0x00,0x00,0x69,0x6e,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x05,0x00,0x06,0x00,0x1c,0x00,0x00,0x00,0x66,0x72,0x61,0x67,0x5f,0x74,0x65,0x78,0x5f,0x63,0x6f,0x6f,0x72,0x64,0x00,0x00,0x05,0x00,0x06,0x00,0x1d,0x00,0x00,0x00,0x69,0x6e,0x5f,0x74,0x65,0x78,0x5f,0x63,0x6f,0x6f,0x72,0x64,0x00,0x00,0x00,0x00,0x47,0x00,0x03,0x00,0x0b,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x0b,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x0b,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x0b,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x12,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x1c,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x1d,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x21,0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x16,0x00,0x03,0x00,0x06,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x15,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1c,0x00,0x04,0x00,0x0a,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x1e,0x00,0x06,0x00,0x0b,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x0c,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0c,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x15,0x00,0x04,0x00,0x0e,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x0e,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x10,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x11,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x11,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x00,0x00,0x80,0x3f,0x20,0x00,0x04,0x00,0x19,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x1b,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x1b,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x11,0x00,0x00,0x00,0x1d,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x36,0x00,0x05,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x05,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x10,0x00,0x00,0x00,0x13,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x51,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x16,0x00,0x00,0x00,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x51,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x13,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x50,0x00,0x07,0x00,0x07,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x16,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x19,0x00,0x00,0x00,0x1a,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x1a,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x10,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x1d,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x1c,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0xfd,0x00,0x01,0x00,0x38,0x00,0x01,0x00};
+static const unsigned int dynamic_vert_spv_size = 988;
+
+static const unsigned char dynamic_frag_spv[] = {0x03,0x02,0x23,0x07,0x00,0x00,0x01,0x00,0x0b,0x00,0x08,0x00,0xf6,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x0b,0x00,0x06,0x00,0x01,0x00,0x00,0x00,0x47,0x4c,0x53,0x4c,0x2e,0x73,0x74,0x64,0x2e,0x34,0x35,0x30,0x00,0x00,0x00,0x00,0x0e,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0f,0x00,0x07,0x00,0x04,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x6d,0x61,0x69,0x6e,0x00,0x00,0x00,0x00,0x0a,0x01,0x00,0x00,0xf0,0x01,0x00,0x00,0x10,0x00,0x03,0x00,0x04,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x03,0x00,0x03,0x00,0x02,0x00,0x00,0x00,0xc2,0x01,0x00,0x00,0x05,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x6d,0x61,0x69,0x6e,0x00,0x00,0x00,0x00,0x05,0x00,0x06,0x00,0x0b,0x00,0x00,0x00,0x72,0x67,0x62,0x5f,0x74,0x6f,0x5f,0x68,0x73,0x6c,0x28,0x76,0x66,0x33,0x3b,0x00,0x05,0x00,0x03,0x00,0x0a,0x00,0x00,0x00,0x63,0x00,0x00,0x00,0x05,0x00,0x08,0x00,0x12,0x00,0x00,0x00,0x68,0x75,0x65,0x5f,0x74,0x6f,0x5f,0x72,0x67,0x62,0x28,0x66,0x31,0x3b,0x66,0x31,0x3b,0x66,0x31,0x3b,0x00,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0x0f,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0x10,0x00,0x00,0x00,0x71,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0x11,0x00,0x00,0x00,0x74,0x00,0x00,0x00,0x05,0x00,0x06,0x00,0x15,0x00,0x00,0x00,0x68,0x73,0x6c,0x5f,0x74,0x6f,0x5f,0x72,0x67,0x62,0x28,0x76,0x66,0x33,0x3b,0x00,0x05,0x00,0x03,0x00,0x14,0x00,0x00,0x00,0x68,0x73,0x6c,0x00,0x05,0x00,0x03,0x00,0x17,0x00,0x00,0x00,0x6d,0x78,0x00,0x00,0x05,0x00,0x03,0x00,0x24,0x00,0x00,0x00,0x6d,0x6e,0x00,0x00,0x05,0x00,0x03,0x00,0x2d,0x00,0x00,0x00,0x6c,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0x33,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0x41,0x00,0x00,0x00,0x73,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0x5b,0x00,0x00,0x00,0x68,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0xbf,0x00,0x00,0x00,0x68,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0xc2,0x00,0x00,0x00,0x73,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0xc5,0x00,0x00,0x00,0x6c,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0xcf,0x00,0x00,0x00,0x71,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0xe2,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0xea,0x00,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0xec,0x00,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0xee,0x00,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0xf0,0x00,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0xf2,0x00,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0xf4,0x00,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0xf9,0x00,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0xfb,0x00,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0xfd,0x00,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0x02,0x01,0x00,0x00,0x6d,0x61,0x73,0x6b,0x00,0x00,0x00,0x00,0x05,0x00,0x06,0x00,0x06,0x01,0x00,0x00,0x6d,0x61,0x73,0x6b,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x00,0x00,0x00,0x00,0x05,0x00,0x06,0x00,0x0a,0x01,0x00,0x00,0x66,0x72,0x61,0x67,0x5f,0x74,0x65,0x78,0x5f,0x63,0x6f,0x6f,0x72,0x64,0x00,0x00,0x05,0x00,0x03,0x00,0x16,0x01,0x00,0x00,0x6e,0x64,0x63,0x00,0x05,0x00,0x06,0x00,0x17,0x01,0x00,0x00,0x50,0x75,0x73,0x68,0x43,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x73,0x00,0x00,0x00,0x06,0x00,0x07,0x00,0x17,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x71,0x75,0x61,0x64,0x5f,0x6e,0x64,0x63,0x5f,0x6d,0x69,0x6e,0x00,0x00,0x00,0x00,0x06,0x00,0x07,0x00,0x17,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x71,0x75,0x61,0x64,0x5f,0x6e,0x64,0x63,0x5f,0x73,0x69,0x7a,0x65,0x00,0x00,0x00,0x06,0x00,0x06,0x00,0x17,0x01,0x00,0x00,0x02,0x00,0x00,0x00,0x69,0x6e,0x76,0x65,0x72,0x74,0x5f,0x73,0x74,0x72,0x00,0x00,0x06,0x00,0x06,0x00,0x17,0x01,0x00,0x00,0x03,0x00,0x00,0x00,0x64,0x6f,0x64,0x67,0x65,0x5f,0x73,0x74,0x72,0x00,0x00,0x00,0x06,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x04,0x00,0x00,0x00,0x64,0x6f,0x64,0x67,0x65,0x5f,0x72,0x00,0x06,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x05,0x00,0x00,0x00,0x64,0x6f,0x64,0x67,0x65,0x5f,0x67,0x00,0x06,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x06,0x00,0x00,0x00,0x64,0x6f,0x64,0x67,0x65,0x5f,0x62,0x00,0x06,0x00,0x06,0x00,0x17,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x62,0x75,0x72,0x6e,0x5f,0x73,0x74,0x72,0x00,0x00,0x00,0x00,0x06,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x08,0x00,0x00,0x00,0x62,0x75,0x72,0x6e,0x5f,0x72,0x00,0x00,0x06,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x09,0x00,0x00,0x00,0x62,0x75,0x72,0x6e,0x5f,0x67,0x00,0x00,0x06,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x0a,0x00,0x00,0x00,0x62,0x75,0x72,0x6e,0x5f,0x62,0x00,0x00,0x06,0x00,0x07,0x00,0x17,0x01,0x00,0x00,0x0b,0x00,0x00,0x00,0x63,0x6f,0x6d,0x70,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x5f,0x73,0x74,0x72,0x00,0x00,0x06,0x00,0x07,0x00,0x17,0x01,0x00,0x00,0x0c,0x00,0x00,0x00,0x6c,0x75,0x6d,0x61,0x69,0x6e,0x76,0x65,0x72,0x74,0x5f,0x73,0x74,0x72,0x00,0x00,0x06,0x00,0x07,0x00,0x17,0x01,0x00,0x00,0x0d,0x00,0x00,0x00,0x68,0x75,0x65,0x72,0x6f,0x74,0x61,0x74,0x65,0x5f,0x73,0x74,0x72,0x00,0x00,0x00,0x06,0x00,0x07,0x00,0x17,0x01,0x00,0x00,0x0e,0x00,0x00,0x00,0x68,0x75,0x65,0x72,0x6f,0x74,0x61,0x74,0x65,0x5f,0x61,0x6e,0x67,0x6c,0x65,0x00,0x06,0x00,0x07,0x00,0x17,0x01,0x00,0x00,0x0f,0x00,0x00,0x00,0x73,0x61,0x74,0x75,0x72,0x61,0x74,0x65,0x5f,0x73,0x74,0x72,0x00,0x00,0x00,0x00,0x06,0x00,0x07,0x00,0x17,0x01,0x00,0x00,0x10,0x00,0x00,0x00,0x73,0x61,0x74,0x75,0x72,0x61,0x74,0x65,0x5f,0x61,0x6d,0x6f,0x75,0x6e,0x74,0x00,0x06,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x11,0x00,0x00,0x00,0x6f,0x70,0x61,0x63,0x69,0x74,0x79,0x00,0x05,0x00,0x03,0x00,0x19,0x01,0x00,0x00,0x70,0x63,0x00,0x00,0x05,0x00,0x05,0x00,0x25,0x01,0x00,0x00,0x73,0x63,0x72,0x65,0x65,0x6e,0x5f,0x75,0x76,0x00,0x00,0x00,0x05,0x00,0x05,0x00,0x2a,0x01,0x00,0x00,0x6f,0x72,0x69,0x67,0x69,0x6e,0x61,0x6c,0x00,0x00,0x00,0x00,0x05,0x00,0x06,0x00,0x2b,0x01,0x00,0x00,0x67,0x61,0x6d,0x65,0x5f,0x66,0x62,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x00,0x05,0x00,0x04,0x00,0x30,0x01,0x00,0x00,0x63,0x6f,0x6c,0x6f,0x72,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0x47,0x01,0x00,0x00,0x64,0x6f,0x64,0x67,0x65,0x64,0x00,0x00,0x05,0x00,0x04,0x00,0x61,0x01,0x00,0x00,0x62,0x75,0x72,0x6e,0x65,0x64,0x00,0x00,0x05,0x00,0x03,0x00,0x7c,0x01,0x00,0x00,0x68,0x73,0x6c,0x00,0x05,0x00,0x04,0x00,0x7d,0x01,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0x8b,0x01,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0x98,0x01,0x00,0x00,0x68,0x73,0x6c,0x00,0x05,0x00,0x04,0x00,0x99,0x01,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x04,0x00,0xa1,0x01,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0xae,0x01,0x00,0x00,0x68,0x73,0x6c,0x00,0x05,0x00,0x04,0x00,0xaf,0x01,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x05,0x00,0xb2,0x01,0x00,0x00,0x6f,0x72,0x69,0x67,0x69,0x6e,0x61,0x6c,0x5f,0x6c,0x00,0x00,0x05,0x00,0x04,0x00,0xc3,0x01,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x03,0x00,0xd0,0x01,0x00,0x00,0x68,0x73,0x6c,0x00,0x05,0x00,0x04,0x00,0xd1,0x01,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x05,0x00,0xd4,0x01,0x00,0x00,0x6f,0x72,0x69,0x67,0x69,0x6e,0x61,0x6c,0x5f,0x6c,0x00,0x00,0x05,0x00,0x04,0x00,0xdf,0x01,0x00,0x00,0x70,0x61,0x72,0x61,0x6d,0x00,0x00,0x00,0x05,0x00,0x05,0x00,0xf0,0x01,0x00,0x00,0x6f,0x75,0x74,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x06,0x01,0x00,0x00,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x06,0x01,0x00,0x00,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x0a,0x01,0x00,0x00,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x03,0x00,0x17,0x01,0x00,0x00,0x02,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x02,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x03,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x04,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x05,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x06,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x24,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x08,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x09,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x2c,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x0a,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x0b,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x34,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x0c,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x0d,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x0e,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x0f,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x44,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x10,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x17,0x01,0x00,0x00,0x11,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x4c,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x2b,0x01,0x00,0x00,0x21,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x2b,0x01,0x00,0x00,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0xf0,0x01,0x00,0x00,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x21,0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x16,0x00,0x03,0x00,0x06,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x21,0x00,0x04,0x00,0x09,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x21,0x00,0x06,0x00,0x0e,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x15,0x00,0x04,0x00,0x18,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x18,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x18,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x18,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0xac,0xc5,0x27,0x37,0x14,0x00,0x02,0x00,0x39,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x3d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x63,0x00,0x00,0x00,0x00,0x00,0xc0,0x40,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x7c,0x00,0x00,0x00,0x00,0x00,0x80,0x40,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x84,0x00,0x00,0x00,0x00,0x00,0x80,0x3f,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x9a,0x00,0x00,0x00,0xab,0xaa,0x2a,0x3e,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xae,0x00,0x00,0x00,0xab,0xaa,0x2a,0x3f,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xe8,0x00,0x00,0x00,0xab,0xaa,0xaa,0x3e,0x19,0x00,0x09,0x00,0x03,0x01,0x00,0x00,0x06,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1b,0x00,0x03,0x00,0x04,0x01,0x00,0x00,0x03,0x01,0x00,0x00,0x20,0x00,0x04,0x00,0x05,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x01,0x00,0x00,0x3b,0x00,0x04,0x00,0x05,0x01,0x00,0x00,0x06,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x08,0x01,0x00,0x00,0x06,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x09,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x01,0x00,0x00,0x3b,0x00,0x04,0x00,0x09,0x01,0x00,0x00,0x0a,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x0c,0x01,0x00,0x00,0x06,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x6f,0x12,0x83,0x3b,0x20,0x00,0x04,0x00,0x15,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x08,0x01,0x00,0x00,0x1e,0x00,0x14,0x00,0x17,0x01,0x00,0x00,0x08,0x01,0x00,0x00,0x08,0x01,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x18,0x01,0x00,0x00,0x09,0x00,0x00,0x00,0x17,0x01,0x00,0x00,0x3b,0x00,0x04,0x00,0x18,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x09,0x00,0x00,0x00,0x15,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x20,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x1b,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x1c,0x01,0x00,0x00,0x09,0x00,0x00,0x00,0x08,0x01,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x20,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x05,0x01,0x00,0x00,0x2b,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x32,0x01,0x00,0x00,0x02,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x33,0x01,0x00,0x00,0x09,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x2c,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0x3a,0x01,0x00,0x00,0x84,0x00,0x00,0x00,0x84,0x00,0x00,0x00,0x84,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x41,0x01,0x00,0x00,0x03,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x49,0x01,0x00,0x00,0x04,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x4c,0x01,0x00,0x00,0x05,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x4f,0x01,0x00,0x00,0x06,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x5b,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x63,0x01,0x00,0x00,0x08,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x66,0x01,0x00,0x00,0x09,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x69,0x01,0x00,0x00,0x0a,0x00,0x00,0x00,0x2c,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0x6e,0x01,0x00,0x00,0x3d,0x00,0x00,0x00,0x3d,0x00,0x00,0x00,0x3d,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x76,0x01,0x00,0x00,0x0b,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0x92,0x01,0x00,0x00,0x0c,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0xa8,0x01,0x00,0x00,0x0d,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0xb7,0x01,0x00,0x00,0x0e,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xba,0x01,0x00,0x00,0x00,0x00,0xb4,0x43,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0xca,0x01,0x00,0x00,0x0f,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0xd7,0x01,0x00,0x00,0x10,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x1a,0x01,0x00,0x00,0xe9,0x01,0x00,0x00,0x11,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0xef,0x01,0x00,0x00,0x03,0x00,0x00,0x00,0x0c,0x01,0x00,0x00,0x3b,0x00,0x04,0x00,0xef,0x01,0x00,0x00,0xf0,0x01,0x00,0x00,0x03,0x00,0x00,0x00,0x36,0x00,0x05,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x05,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0x02,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x15,0x01,0x00,0x00,0x16,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x15,0x01,0x00,0x00,0x25,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x2a,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x30,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x47,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x61,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x7c,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x7d,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x8b,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x98,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x99,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0xa1,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0xae,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0xaf,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xb2,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0xc3,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0xd0,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0xd1,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xd4,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0xdf,0x01,0x00,0x00,0x07,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x04,0x01,0x00,0x00,0x07,0x01,0x00,0x00,0x06,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x08,0x01,0x00,0x00,0x0b,0x01,0x00,0x00,0x0a,0x01,0x00,0x00,0x57,0x00,0x05,0x00,0x0c,0x01,0x00,0x00,0x0d,0x01,0x00,0x00,0x07,0x01,0x00,0x00,0x0b,0x01,0x00,0x00,0x51,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x0e,0x01,0x00,0x00,0x0d,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x02,0x01,0x00,0x00,0x0e,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x0f,0x01,0x00,0x00,0x02,0x01,0x00,0x00,0xb8,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x11,0x01,0x00,0x00,0x0f,0x01,0x00,0x00,0x10,0x01,0x00,0x00,0xf7,0x00,0x03,0x00,0x13,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x11,0x01,0x00,0x00,0x12,0x01,0x00,0x00,0x13,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0x12,0x01,0x00,0x00,0xfc,0x00,0x01,0x00,0xf8,0x00,0x02,0x00,0x13,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x1c,0x01,0x00,0x00,0x1d,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x1b,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x08,0x01,0x00,0x00,0x1e,0x01,0x00,0x00,0x1d,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x08,0x01,0x00,0x00,0x1f,0x01,0x00,0x00,0x0a,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x1c,0x01,0x00,0x00,0x21,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x20,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x08,0x01,0x00,0x00,0x22,0x01,0x00,0x00,0x21,0x01,0x00,0x00,0x85,0x00,0x05,0x00,0x08,0x01,0x00,0x00,0x23,0x01,0x00,0x00,0x1f,0x01,0x00,0x00,0x22,0x01,0x00,0x00,0x81,0x00,0x05,0x00,0x08,0x01,0x00,0x00,0x24,0x01,0x00,0x00,0x1e,0x01,0x00,0x00,0x23,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x16,0x01,0x00,0x00,0x24,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x08,0x01,0x00,0x00,0x26,0x01,0x00,0x00,0x16,0x01,0x00,0x00,0x8e,0x00,0x05,0x00,0x08,0x01,0x00,0x00,0x27,0x01,0x00,0x00,0x26,0x01,0x00,0x00,0x31,0x00,0x00,0x00,0x50,0x00,0x05,0x00,0x08,0x01,0x00,0x00,0x28,0x01,0x00,0x00,0x31,0x00,0x00,0x00,0x31,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x08,0x01,0x00,0x00,0x29,0x01,0x00,0x00,0x27,0x01,0x00,0x00,0x28,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x25,0x01,0x00,0x00,0x29,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x04,0x01,0x00,0x00,0x2c,0x01,0x00,0x00,0x2b,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x08,0x01,0x00,0x00,0x2d,0x01,0x00,0x00,0x25,0x01,0x00,0x00,0x57,0x00,0x05,0x00,0x0c,0x01,0x00,0x00,0x2e,0x01,0x00,0x00,0x2c,0x01,0x00,0x00,0x2d,0x01,0x00,0x00,0x4f,0x00,0x08,0x00,0x07,0x00,0x00,0x00,0x2f,0x01,0x00,0x00,0x2e,0x01,0x00,0x00,0x2e,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x2a,0x01,0x00,0x00,0x2f,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x31,0x01,0x00,0x00,0x2a,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x30,0x01,0x00,0x00,0x31,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x34,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x32,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x35,0x01,0x00,0x00,0x34,0x01,0x00,0x00,0xba,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x36,0x01,0x00,0x00,0x35,0x01,0x00,0x00,0x3d,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x38,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x36,0x01,0x00,0x00,0x37,0x01,0x00,0x00,0x38,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0x37,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x39,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x3b,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x83,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0x3c,0x01,0x00,0x00,0x3a,0x01,0x00,0x00,0x3b,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x3d,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x32,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x3e,0x01,0x00,0x00,0x3d,0x01,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0x3f,0x01,0x00,0x00,0x3e,0x01,0x00,0x00,0x3e,0x01,0x00,0x00,0x3e,0x01,0x00,0x00,0x0c,0x00,0x08,0x00,0x07,0x00,0x00,0x00,0x40,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x2e,0x00,0x00,0x00,0x39,0x01,0x00,0x00,0x3c,0x01,0x00,0x00,0x3f,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x30,0x01,0x00,0x00,0x40,0x01,0x00,0x00,0xf9,0x00,0x02,0x00,0x38,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0x38,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x42,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x41,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x43,0x01,0x00,0x00,0x42,0x01,0x00,0x00,0xba,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x44,0x01,0x00,0x00,0x43,0x01,0x00,0x00,0x3d,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x46,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x44,0x01,0x00,0x00,0x45,0x01,0x00,0x00,0x46,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0x45,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x48,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x4a,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x49,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x4b,0x01,0x00,0x00,0x4a,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x4d,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x4c,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x4e,0x01,0x00,0x00,0x4d,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x50,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x4f,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x51,0x01,0x00,0x00,0x50,0x01,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0x52,0x01,0x00,0x00,0x4b,0x01,0x00,0x00,0x4e,0x01,0x00,0x00,0x51,0x01,0x00,0x00,0x81,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0x53,0x01,0x00,0x00,0x48,0x01,0x00,0x00,0x52,0x01,0x00,0x00,0x0c,0x00,0x07,0x00,0x07,0x00,0x00,0x00,0x54,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x53,0x01,0x00,0x00,0x3a,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x47,0x01,0x00,0x00,0x54,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x55,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x56,0x01,0x00,0x00,0x47,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x57,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x41,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x58,0x01,0x00,0x00,0x57,0x01,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0x59,0x01,0x00,0x00,0x58,0x01,0x00,0x00,0x58,0x01,0x00,0x00,0x58,0x01,0x00,0x00,0x0c,0x00,0x08,0x00,0x07,0x00,0x00,0x00,0x5a,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x2e,0x00,0x00,0x00,0x55,0x01,0x00,0x00,0x56,0x01,0x00,0x00,0x59,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x30,0x01,0x00,0x00,0x5a,0x01,0x00,0x00,0xf9,0x00,0x02,0x00,0x46,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0x46,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x5c,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x5b,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x5d,0x01,0x00,0x00,0x5c,0x01,0x00,0x00,0xba,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x5e,0x01,0x00,0x00,0x5d,0x01,0x00,0x00,0x3d,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x60,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x5e,0x01,0x00,0x00,0x5f,0x01,0x00,0x00,0x60,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0x5f,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x62,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x64,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x63,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x65,0x01,0x00,0x00,0x64,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x67,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x66,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x68,0x01,0x00,0x00,0x67,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x6a,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x69,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x6b,0x01,0x00,0x00,0x6a,0x01,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0x6c,0x01,0x00,0x00,0x65,0x01,0x00,0x00,0x68,0x01,0x00,0x00,0x6b,0x01,0x00,0x00,0x83,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0x6d,0x01,0x00,0x00,0x62,0x01,0x00,0x00,0x6c,0x01,0x00,0x00,0x0c,0x00,0x07,0x00,0x07,0x00,0x00,0x00,0x6f,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x6d,0x01,0x00,0x00,0x6e,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x61,0x01,0x00,0x00,0x6f,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x70,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x71,0x01,0x00,0x00,0x61,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x72,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x5b,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x73,0x01,0x00,0x00,0x72,0x01,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0x74,0x01,0x00,0x00,0x73,0x01,0x00,0x00,0x73,0x01,0x00,0x00,0x73,0x01,0x00,0x00,0x0c,0x00,0x08,0x00,0x07,0x00,0x00,0x00,0x75,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x2e,0x00,0x00,0x00,0x70,0x01,0x00,0x00,0x71,0x01,0x00,0x00,0x74,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x30,0x01,0x00,0x00,0x75,0x01,0x00,0x00,0xf9,0x00,0x02,0x00,0x60,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0x60,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x77,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x76,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x78,0x01,0x00,0x00,0x77,0x01,0x00,0x00,0xba,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x79,0x01,0x00,0x00,0x78,0x01,0x00,0x00,0x3d,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x7b,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x79,0x01,0x00,0x00,0x7a,0x01,0x00,0x00,0x7b,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0x7a,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x7e,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x7d,0x01,0x00,0x00,0x7e,0x01,0x00,0x00,0x39,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0x7f,0x01,0x00,0x00,0x0b,0x00,0x00,0x00,0x7d,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x7c,0x01,0x00,0x00,0x7f,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x80,0x01,0x00,0x00,0x7c,0x01,0x00,0x00,0x19,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x81,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x82,0x01,0x00,0x00,0x81,0x01,0x00,0x00,0x31,0x00,0x00,0x00,0x8d,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x83,0x01,0x00,0x00,0x82,0x01,0x00,0x00,0x84,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x84,0x01,0x00,0x00,0x7c,0x01,0x00,0x00,0x19,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x84,0x01,0x00,0x00,0x83,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x85,0x01,0x00,0x00,0x7c,0x01,0x00,0x00,0x1c,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x85,0x01,0x00,0x00,0x84,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x86,0x01,0x00,0x00,0x7c,0x01,0x00,0x00,0x1f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x87,0x01,0x00,0x00,0x86,0x01,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x88,0x01,0x00,0x00,0x84,0x00,0x00,0x00,0x87,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x89,0x01,0x00,0x00,0x7c,0x01,0x00,0x00,0x1f,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x89,0x01,0x00,0x00,0x88,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x8a,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x8c,0x01,0x00,0x00,0x7c,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x8b,0x01,0x00,0x00,0x8c,0x01,0x00,0x00,0x39,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0x8d,0x01,0x00,0x00,0x15,0x00,0x00,0x00,0x8b,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x8e,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x76,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x8f,0x01,0x00,0x00,0x8e,0x01,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0x90,0x01,0x00,0x00,0x8f,0x01,0x00,0x00,0x8f,0x01,0x00,0x00,0x8f,0x01,0x00,0x00,0x0c,0x00,0x08,0x00,0x07,0x00,0x00,0x00,0x91,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x2e,0x00,0x00,0x00,0x8a,0x01,0x00,0x00,0x8d,0x01,0x00,0x00,0x90,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x30,0x01,0x00,0x00,0x91,0x01,0x00,0x00,0xf9,0x00,0x02,0x00,0x7b,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0x7b,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0x93,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x92,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x94,0x01,0x00,0x00,0x93,0x01,0x00,0x00,0xba,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x95,0x01,0x00,0x00,0x94,0x01,0x00,0x00,0x3d,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x97,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x95,0x01,0x00,0x00,0x96,0x01,0x00,0x00,0x97,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0x96,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x9a,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x99,0x01,0x00,0x00,0x9a,0x01,0x00,0x00,0x39,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0x9b,0x01,0x00,0x00,0x0b,0x00,0x00,0x00,0x99,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x98,0x01,0x00,0x00,0x9b,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x9c,0x01,0x00,0x00,0x98,0x01,0x00,0x00,0x1f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x9d,0x01,0x00,0x00,0x9c,0x01,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x9e,0x01,0x00,0x00,0x84,0x00,0x00,0x00,0x9d,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x9f,0x01,0x00,0x00,0x98,0x01,0x00,0x00,0x1f,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x9f,0x01,0x00,0x00,0x9e,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0xa0,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0xa2,0x01,0x00,0x00,0x98,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0xa1,0x01,0x00,0x00,0xa2,0x01,0x00,0x00,0x39,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0xa3,0x01,0x00,0x00,0x15,0x00,0x00,0x00,0xa1,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0xa4,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0x92,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xa5,0x01,0x00,0x00,0xa4,0x01,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0xa6,0x01,0x00,0x00,0xa5,0x01,0x00,0x00,0xa5,0x01,0x00,0x00,0xa5,0x01,0x00,0x00,0x0c,0x00,0x08,0x00,0x07,0x00,0x00,0x00,0xa7,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x2e,0x00,0x00,0x00,0xa0,0x01,0x00,0x00,0xa3,0x01,0x00,0x00,0xa6,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x30,0x01,0x00,0x00,0xa7,0x01,0x00,0x00,0xf9,0x00,0x02,0x00,0x97,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0x97,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0xa9,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0xa8,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xaa,0x01,0x00,0x00,0xa9,0x01,0x00,0x00,0xba,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0xab,0x01,0x00,0x00,0xaa,0x01,0x00,0x00,0x3d,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0xad,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0xab,0x01,0x00,0x00,0xac,0x01,0x00,0x00,0xad,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0xac,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0xb0,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0xaf,0x01,0x00,0x00,0xb0,0x01,0x00,0x00,0x39,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0xb1,0x01,0x00,0x00,0x0b,0x00,0x00,0x00,0xaf,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0xae,0x01,0x00,0x00,0xb1,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0xb3,0x01,0x00,0x00,0xae,0x01,0x00,0x00,0x1f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xb4,0x01,0x00,0x00,0xb3,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0xb2,0x01,0x00,0x00,0xb4,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0xb5,0x01,0x00,0x00,0xae,0x01,0x00,0x00,0x19,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xb6,0x01,0x00,0x00,0xb5,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0xb8,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0xb7,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xb9,0x01,0x00,0x00,0xb8,0x01,0x00,0x00,0x88,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xbb,0x01,0x00,0x00,0xb9,0x01,0x00,0x00,0xba,0x01,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xbc,0x01,0x00,0x00,0xb6,0x01,0x00,0x00,0xbb,0x01,0x00,0x00,0x8d,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xbd,0x01,0x00,0x00,0xbc,0x01,0x00,0x00,0x84,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0xbe,0x01,0x00,0x00,0xae,0x01,0x00,0x00,0x19,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xbe,0x01,0x00,0x00,0xbd,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xbf,0x01,0x00,0x00,0xb2,0x01,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xc0,0x01,0x00,0x00,0x84,0x00,0x00,0x00,0xbf,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0xc1,0x01,0x00,0x00,0xae,0x01,0x00,0x00,0x1f,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xc1,0x01,0x00,0x00,0xc0,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0xc2,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0xc4,0x01,0x00,0x00,0xae,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0xc3,0x01,0x00,0x00,0xc4,0x01,0x00,0x00,0x39,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0xc5,0x01,0x00,0x00,0x15,0x00,0x00,0x00,0xc3,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0xc6,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0xa8,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xc7,0x01,0x00,0x00,0xc6,0x01,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0xc8,0x01,0x00,0x00,0xc7,0x01,0x00,0x00,0xc7,0x01,0x00,0x00,0xc7,0x01,0x00,0x00,0x0c,0x00,0x08,0x00,0x07,0x00,0x00,0x00,0xc9,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x2e,0x00,0x00,0x00,0xc2,0x01,0x00,0x00,0xc5,0x01,0x00,0x00,0xc8,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x30,0x01,0x00,0x00,0xc9,0x01,0x00,0x00,0xf9,0x00,0x02,0x00,0xad,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0xad,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0xcb,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0xca,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xcc,0x01,0x00,0x00,0xcb,0x01,0x00,0x00,0xba,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0xcd,0x01,0x00,0x00,0xcc,0x01,0x00,0x00,0x3d,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0xcf,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0xcd,0x01,0x00,0x00,0xce,0x01,0x00,0x00,0xcf,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0xce,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0xd2,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0xd1,0x01,0x00,0x00,0xd2,0x01,0x00,0x00,0x39,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0xd3,0x01,0x00,0x00,0x0b,0x00,0x00,0x00,0xd1,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0xd0,0x01,0x00,0x00,0xd3,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0xd5,0x01,0x00,0x00,0xd0,0x01,0x00,0x00,0x1f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xd6,0x01,0x00,0x00,0xd5,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0xd4,0x01,0x00,0x00,0xd6,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0xd8,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0xd7,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xd9,0x01,0x00,0x00,0xd8,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0xda,0x01,0x00,0x00,0xd0,0x01,0x00,0x00,0x1c,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xda,0x01,0x00,0x00,0xd9,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xdb,0x01,0x00,0x00,0xd4,0x01,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xdc,0x01,0x00,0x00,0x84,0x00,0x00,0x00,0xdb,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0xdd,0x01,0x00,0x00,0xd0,0x01,0x00,0x00,0x1f,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xdd,0x01,0x00,0x00,0xdc,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0xde,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0xe0,0x01,0x00,0x00,0xd0,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0xdf,0x01,0x00,0x00,0xe0,0x01,0x00,0x00,0x39,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0xe1,0x01,0x00,0x00,0x15,0x00,0x00,0x00,0xdf,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0xe2,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0xca,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xe3,0x01,0x00,0x00,0xe2,0x01,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0xe4,0x01,0x00,0x00,0xe3,0x01,0x00,0x00,0xe3,0x01,0x00,0x00,0xe3,0x01,0x00,0x00,0x0c,0x00,0x08,0x00,0x07,0x00,0x00,0x00,0xe5,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x2e,0x00,0x00,0x00,0xde,0x01,0x00,0x00,0xe1,0x01,0x00,0x00,0xe4,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x30,0x01,0x00,0x00,0xe5,0x01,0x00,0x00,0xf9,0x00,0x02,0x00,0xcf,0x01,0x00,0x00,0xf8,0x00,0x02,0x00,0xcf,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0xe6,0x01,0x00,0x00,0x2a,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0xe7,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xe8,0x01,0x00,0x00,0x02,0x01,0x00,0x00,0x41,0x00,0x05,0x00,0x33,0x01,0x00,0x00,0xea,0x01,0x00,0x00,0x19,0x01,0x00,0x00,0xe9,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xeb,0x01,0x00,0x00,0xea,0x01,0x00,0x00,0x85,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xec,0x01,0x00,0x00,0xe8,0x01,0x00,0x00,0xeb,0x01,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0xed,0x01,0x00,0x00,0xec,0x01,0x00,0x00,0xec,0x01,0x00,0x00,0xec,0x01,0x00,0x00,0x0c,0x00,0x08,0x00,0x07,0x00,0x00,0x00,0xee,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x2e,0x00,0x00,0x00,0xe6,0x01,0x00,0x00,0xe7,0x01,0x00,0x00,0xed,0x01,0x00,0x00,0x3e,0x00,0x03,0x00,0x30,0x01,0x00,0x00,0xee,0x01,0x00,0x00,0x3d,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0xf1,0x01,0x00,0x00,0x30,0x01,0x00,0x00,0x51,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xf2,0x01,0x00,0x00,0xf1,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x51,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xf3,0x01,0x00,0x00,0xf1,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x51,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xf4,0x01,0x00,0x00,0xf1,0x01,0x00,0x00,0x02,0x00,0x00,0x00,0x50,0x00,0x07,0x00,0x0c,0x01,0x00,0x00,0xf5,0x01,0x00,0x00,0xf2,0x01,0x00,0x00,0xf3,0x01,0x00,0x00,0xf4,0x01,0x00,0x00,0x84,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xf0,0x01,0x00,0x00,0xf5,0x01,0x00,0x00,0xfd,0x00,0x01,0x00,0x38,0x00,0x01,0x00,0x36,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x37,0x00,0x03,0x00,0x08,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x0c,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0x24,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0x2d,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0x41,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0x44,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0x5b,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x1a,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x1b,0x00,0x00,0x00,0x1a,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x1d,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x1d,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x0c,0x00,0x07,0x00,0x06,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x0c,0x00,0x07,0x00,0x06,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x1b,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x17,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x27,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x27,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x29,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x2a,0x00,0x00,0x00,0x29,0x00,0x00,0x00,0x0c,0x00,0x07,0x00,0x06,0x00,0x00,0x00,0x2b,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x2a,0x00,0x00,0x00,0x0c,0x00,0x07,0x00,0x06,0x00,0x00,0x00,0x2c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x2b,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x24,0x00,0x00,0x00,0x2c,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x2e,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x2f,0x00,0x00,0x00,0x24,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x2e,0x00,0x00,0x00,0x2f,0x00,0x00,0x00,0x85,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x32,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x31,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x2d,0x00,0x00,0x00,0x32,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x34,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x35,0x00,0x00,0x00,0x24,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x36,0x00,0x00,0x00,0x34,0x00,0x00,0x00,0x35,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x33,0x00,0x00,0x00,0x36,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x37,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0xb8,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x3a,0x00,0x00,0x00,0x37,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x3a,0x00,0x00,0x00,0x3b,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x3b,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x3e,0x00,0x00,0x00,0x2d,0x00,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0x3f,0x00,0x00,0x00,0x3d,0x00,0x00,0x00,0x3d,0x00,0x00,0x00,0x3e,0x00,0x00,0x00,0xfe,0x00,0x02,0x00,0x3f,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x3c,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x42,0x00,0x00,0x00,0x2d,0x00,0x00,0x00,0xba,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x43,0x00,0x00,0x00,0x42,0x00,0x00,0x00,0x31,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x43,0x00,0x00,0x00,0x45,0x00,0x00,0x00,0x4e,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x45,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x47,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x49,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x4a,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x49,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x4b,0x00,0x00,0x00,0x24,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x4c,0x00,0x00,0x00,0x4a,0x00,0x00,0x00,0x4b,0x00,0x00,0x00,0x88,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x4d,0x00,0x00,0x00,0x47,0x00,0x00,0x00,0x4c,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x44,0x00,0x00,0x00,0x4d,0x00,0x00,0x00,0xf9,0x00,0x02,0x00,0x46,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x4e,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x4f,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x51,0x00,0x00,0x00,0x24,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x52,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x51,0x00,0x00,0x00,0x88,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x53,0x00,0x00,0x00,0x4f,0x00,0x00,0x00,0x52,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x44,0x00,0x00,0x00,0x53,0x00,0x00,0x00,0xf9,0x00,0x02,0x00,0x46,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x46,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x54,0x00,0x00,0x00,0x44,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x41,0x00,0x00,0x00,0x54,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x55,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x56,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x57,0x00,0x00,0x00,0x56,0x00,0x00,0x00,0xb4,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x55,0x00,0x00,0x00,0x57,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x5a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x58,0x00,0x00,0x00,0x59,0x00,0x00,0x00,0x65,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x59,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x5c,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x5d,0x00,0x00,0x00,0x5c,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x5e,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x5f,0x00,0x00,0x00,0x5e,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x5d,0x00,0x00,0x00,0x5f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x61,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x88,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x62,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x61,0x00,0x00,0x00,0x8d,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x62,0x00,0x00,0x00,0x63,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x5b,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0xf9,0x00,0x02,0x00,0x5a,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x65,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x66,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x67,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x68,0x00,0x00,0x00,0x67,0x00,0x00,0x00,0xb4,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x69,0x00,0x00,0x00,0x66,0x00,0x00,0x00,0x68,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x6b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x69,0x00,0x00,0x00,0x6a,0x00,0x00,0x00,0x74,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x6a,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x6c,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x6d,0x00,0x00,0x00,0x6c,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x6e,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x6f,0x00,0x00,0x00,0x6e,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x6d,0x00,0x00,0x00,0x6f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x71,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x88,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x72,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x71,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x73,0x00,0x00,0x00,0x72,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x5b,0x00,0x00,0x00,0x73,0x00,0x00,0x00,0xf9,0x00,0x02,0x00,0x6b,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x74,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x75,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x76,0x00,0x00,0x00,0x75,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0x77,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x77,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x79,0x00,0x00,0x00,0x76,0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x7a,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x88,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x7b,0x00,0x00,0x00,0x79,0x00,0x00,0x00,0x7a,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x7d,0x00,0x00,0x00,0x7b,0x00,0x00,0x00,0x7c,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x5b,0x00,0x00,0x00,0x7d,0x00,0x00,0x00,0xf9,0x00,0x02,0x00,0x6b,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x6b,0x00,0x00,0x00,0xf9,0x00,0x02,0x00,0x5a,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x5a,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x7e,0x00,0x00,0x00,0x5b,0x00,0x00,0x00,0x88,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x7f,0x00,0x00,0x00,0x7e,0x00,0x00,0x00,0x63,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x5b,0x00,0x00,0x00,0x7f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x5b,0x00,0x00,0x00,0xb8,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x81,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x3d,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x83,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x81,0x00,0x00,0x00,0x82,0x00,0x00,0x00,0x83,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x82,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x85,0x00,0x00,0x00,0x5b,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x86,0x00,0x00,0x00,0x85,0x00,0x00,0x00,0x84,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x5b,0x00,0x00,0x00,0x86,0x00,0x00,0x00,0xf9,0x00,0x02,0x00,0x83,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x83,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x87,0x00,0x00,0x00,0x5b,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x88,0x00,0x00,0x00,0x41,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x89,0x00,0x00,0x00,0x2d,0x00,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0x8a,0x00,0x00,0x00,0x87,0x00,0x00,0x00,0x88,0x00,0x00,0x00,0x89,0x00,0x00,0x00,0xfe,0x00,0x02,0x00,0x8a,0x00,0x00,0x00,0x38,0x00,0x01,0x00,0x36,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0x00,0x00,0x00,0x37,0x00,0x03,0x00,0x0d,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x37,0x00,0x03,0x00,0x0d,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x37,0x00,0x03,0x00,0x0d,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x13,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x8d,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0xb8,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x8e,0x00,0x00,0x00,0x8d,0x00,0x00,0x00,0x3d,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x8e,0x00,0x00,0x00,0x8f,0x00,0x00,0x00,0x90,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x8f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x91,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x92,0x00,0x00,0x00,0x91,0x00,0x00,0x00,0x84,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x11,0x00,0x00,0x00,0x92,0x00,0x00,0x00,0xf9,0x00,0x02,0x00,0x90,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x90,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x93,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0xba,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x94,0x00,0x00,0x00,0x93,0x00,0x00,0x00,0x84,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x96,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x94,0x00,0x00,0x00,0x95,0x00,0x00,0x00,0x96,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x95,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x97,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x98,0x00,0x00,0x00,0x97,0x00,0x00,0x00,0x84,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x11,0x00,0x00,0x00,0x98,0x00,0x00,0x00,0xf9,0x00,0x02,0x00,0x96,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x96,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x99,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0xb8,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0x9b,0x00,0x00,0x00,0x99,0x00,0x00,0x00,0x9a,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0x9d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0x9b,0x00,0x00,0x00,0x9c,0x00,0x00,0x00,0x9d,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x9c,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x9e,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x9f,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xa0,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xa1,0x00,0x00,0x00,0x9f,0x00,0x00,0x00,0xa0,0x00,0x00,0x00,0x85,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xa2,0x00,0x00,0x00,0xa1,0x00,0x00,0x00,0x63,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xa3,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x85,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xa4,0x00,0x00,0x00,0xa2,0x00,0x00,0x00,0xa3,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xa5,0x00,0x00,0x00,0x9e,0x00,0x00,0x00,0xa4,0x00,0x00,0x00,0xfe,0x00,0x02,0x00,0xa5,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x9d,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xa7,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0xb8,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0xa8,0x00,0x00,0x00,0xa7,0x00,0x00,0x00,0x31,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0xaa,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0xa8,0x00,0x00,0x00,0xa9,0x00,0x00,0x00,0xaa,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0xa9,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xab,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0xfe,0x00,0x02,0x00,0xab,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0xaa,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xad,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0xb8,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0xaf,0x00,0x00,0x00,0xad,0x00,0x00,0x00,0xae,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0xb1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0xaf,0x00,0x00,0x00,0xb0,0x00,0x00,0x00,0xb1,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0xb0,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xb2,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xb3,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xb4,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xb5,0x00,0x00,0x00,0xb3,0x00,0x00,0x00,0xb4,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xb7,0x00,0x00,0x00,0xae,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x85,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xb8,0x00,0x00,0x00,0xb5,0x00,0x00,0x00,0xb7,0x00,0x00,0x00,0x85,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xb9,0x00,0x00,0x00,0xb8,0x00,0x00,0x00,0x63,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xba,0x00,0x00,0x00,0xb2,0x00,0x00,0x00,0xb9,0x00,0x00,0x00,0xfe,0x00,0x02,0x00,0xba,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0xb1,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xbc,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0xfe,0x00,0x02,0x00,0xbc,0x00,0x00,0x00,0x38,0x00,0x01,0x00,0x36,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x37,0x00,0x03,0x00,0x08,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x16,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xc2,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xc5,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xcf,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xd2,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xe2,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xea,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xec,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xee,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xf0,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xf2,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xf4,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xf9,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xfb,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0xfd,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xc1,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xbf,0x00,0x00,0x00,0xc1,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0xc3,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xc4,0x00,0x00,0x00,0xc3,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xc2,0x00,0x00,0x00,0xc4,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0d,0x00,0x00,0x00,0xc6,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xc7,0x00,0x00,0x00,0xc6,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xc5,0x00,0x00,0x00,0xc7,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xc8,0x00,0x00,0x00,0xc2,0x00,0x00,0x00,0xb8,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0xc8,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0xcb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0xc9,0x00,0x00,0x00,0xca,0x00,0x00,0x00,0xcb,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0xca,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xcc,0x00,0x00,0x00,0xc5,0x00,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0xcd,0x00,0x00,0x00,0xcc,0x00,0x00,0x00,0xcc,0x00,0x00,0x00,0xcc,0x00,0x00,0x00,0xfe,0x00,0x02,0x00,0xcd,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0xcb,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xd0,0x00,0x00,0x00,0xc5,0x00,0x00,0x00,0xb8,0x00,0x05,0x00,0x39,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0xd0,0x00,0x00,0x00,0x31,0x00,0x00,0x00,0xf7,0x00,0x03,0x00,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfa,0x00,0x04,0x00,0xd1,0x00,0x00,0x00,0xd3,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0xd3,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xd5,0x00,0x00,0x00,0xc5,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xd6,0x00,0x00,0x00,0xc2,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xd7,0x00,0x00,0x00,0x84,0x00,0x00,0x00,0xd6,0x00,0x00,0x00,0x85,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xd8,0x00,0x00,0x00,0xd5,0x00,0x00,0x00,0xd7,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xd2,0x00,0x00,0x00,0xd8,0x00,0x00,0x00,0xf9,0x00,0x02,0x00,0xd4,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0xd9,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xda,0x00,0x00,0x00,0xc5,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0xc2,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xdc,0x00,0x00,0x00,0xda,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xdd,0x00,0x00,0x00,0xc5,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xde,0x00,0x00,0x00,0xc2,0x00,0x00,0x00,0x85,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xdf,0x00,0x00,0x00,0xdd,0x00,0x00,0x00,0xde,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xe0,0x00,0x00,0x00,0xdc,0x00,0x00,0x00,0xdf,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xd2,0x00,0x00,0x00,0xe0,0x00,0x00,0x00,0xf9,0x00,0x02,0x00,0xd4,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0xd4,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xe1,0x00,0x00,0x00,0xd2,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xcf,0x00,0x00,0x00,0xe1,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xe3,0x00,0x00,0x00,0xc5,0x00,0x00,0x00,0x85,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xe4,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0xe3,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xe5,0x00,0x00,0x00,0xcf,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xe6,0x00,0x00,0x00,0xe4,0x00,0x00,0x00,0xe5,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xe2,0x00,0x00,0x00,0xe6,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xe7,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xe9,0x00,0x00,0x00,0xe7,0x00,0x00,0x00,0xe8,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xeb,0x00,0x00,0x00,0xe2,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xea,0x00,0x00,0x00,0xeb,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xed,0x00,0x00,0x00,0xcf,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xec,0x00,0x00,0x00,0xed,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xee,0x00,0x00,0x00,0xe9,0x00,0x00,0x00,0x39,0x00,0x07,0x00,0x06,0x00,0x00,0x00,0xef,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0xea,0x00,0x00,0x00,0xec,0x00,0x00,0x00,0xee,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xf1,0x00,0x00,0x00,0xe2,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xf0,0x00,0x00,0x00,0xf1,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xf3,0x00,0x00,0x00,0xcf,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xf2,0x00,0x00,0x00,0xf3,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xf5,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xf4,0x00,0x00,0x00,0xf5,0x00,0x00,0x00,0x39,0x00,0x07,0x00,0x06,0x00,0x00,0x00,0xf6,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0xf0,0x00,0x00,0x00,0xf2,0x00,0x00,0x00,0xf4,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xf7,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x83,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0xf8,0x00,0x00,0x00,0xf7,0x00,0x00,0x00,0xe8,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xfa,0x00,0x00,0x00,0xe2,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xf9,0x00,0x00,0x00,0xfa,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0xfc,0x00,0x00,0x00,0xcf,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xfb,0x00,0x00,0x00,0xfc,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0xfd,0x00,0x00,0x00,0xf8,0x00,0x00,0x00,0x39,0x00,0x07,0x00,0x06,0x00,0x00,0x00,0xfe,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0xf9,0x00,0x00,0x00,0xfb,0x00,0x00,0x00,0xfd,0x00,0x00,0x00,0x50,0x00,0x06,0x00,0x07,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xef,0x00,0x00,0x00,0xf6,0x00,0x00,0x00,0xfe,0x00,0x00,0x00,0xfe,0x00,0x02,0x00,0xff,0x00,0x00,0x00,0x38,0x00,0x01,0x00};
+static const unsigned int dynamic_frag_spv_size = 11984;
+
diff --git a/shaders/dynamic_vert.spv b/shaders/dynamic_vert.spv
new file mode 100644
index 0000000..1f1fbbf
Binary files /dev/null and b/shaders/dynamic_vert.spv differ
diff --git a/shaders/shader.frag b/shaders/shader.frag
index 261bf45..805e415 100644
--- a/shaders/shader.frag
+++ b/shaders/shader.frag
@@ -7,8 +7,4 @@ layout(binding = 0) uniform sampler2D tex_sampler;
void main() {
out_color = texture(tex_sampler, frag_tex_coord);
-
- if (out_color.a < 0.1) {
- discard;
- }
}
diff --git a/src/layer.c b/src/layer.c
index e1bd290..649fd1d 100644
--- a/src/layer.c
+++ b/src/layer.c
@@ -5,15 +5,19 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
+#include
#include
#include "../include/dispatch.h"
#define STB_IMAGE_IMPLEMENTATION
#include "../include/default_crosshair.h"
#include "../include/stb_image.h"
+#include "../shaders/dynamic_spv.h"
#define K_NO_MEMORYTYPE 0xFFFFFFFF
@@ -27,11 +31,24 @@
(const struct VkBaseInStructure*)(__start); \
__iter; __iter = __iter->pNext)
+#ifdef NDEBUG
+#define KROSSHAIR_LOG(fmt, ...) ((void)0)
+#else
+#define KROSSHAIR_LOG(fmt, ...) \
+ do { \
+ FILE* _kf = fopen("/tmp/krosshair.log", "a"); \
+ if (_kf) { \
+ fprintf(_kf, fmt, ##__VA_ARGS__); \
+ fclose(_kf); \
+ } \
+ } while (0)
+#endif
+
#define VK_CHECK(expr) \
do { \
VkResult __result = (expr); \
if (__result != VK_SUCCESS) { \
- printf("error\n"); \
+ KROSSHAIR_LOG("[KROSSHAIR_ERROR] VK_CHECK failed: %d\n", __result); \
} \
} while (0)
@@ -40,7 +57,7 @@ VkPhysicalDeviceDriverProperties driver_properties = {};
unsigned char frag_spv[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x0d, 0x00,
- 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -84,12 +101,6 @@ unsigned char frag_spv[] = {
0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0xcd, 0xcc, 0xcc, 0x3d, 0x14, 0x00, 0x02, 0x00, 0x1a, 0x00, 0x00, 0x00,
0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0x05, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00,
@@ -97,16 +108,8 @@ unsigned char frag_spv[] = {
0x0f, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
0x0e, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x05, 0x00,
- 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x19, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x1b, 0x00, 0x00, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x01, 0x00, 0xf8, 0x00, 0x02, 0x00,
- 0x1d, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00};
+ 0x09, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00,
+ 0x38, 0x00, 0x01, 0x00};
unsigned char vert_spv[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x0d, 0x00,
@@ -273,16 +276,16 @@ int vk_map_get(vk_object_map_t* map, uint64_t obj, vk_object_t* obj_out)
void vk_map_print(vk_object_map_t* map)
{
- printf(
+ KROSSHAIR_LOG(
"#################################### [ITERATING MAP] "
"######################################\n");
for (size_t i = 0; i < MAX_VK_OBJECTS; i++) {
if (map->data[i].obj == 0) continue;
- printf("item %lu (%s) has obj: \t\t %lu, data \t\t %p\n", i,
+ KROSSHAIR_LOG("item %lu (%s) has obj: \t\t %lu, data \t\t %p\n", i,
map->data[i].name, map->data[i].obj, map->data[i].data);
}
- printf(
+ KROSSHAIR_LOG(
"##################################################################"
"####"
"#####################\n");
@@ -345,11 +348,93 @@ typedef struct krosshair_draw {
VkDeviceMemory vertex_buffer_mem;
VkDeviceSize vertex_buffer_size;
+ VkBuffer vertex_buffer2; /* separate vertex buffer for dynamic mask */
+ VkDeviceMemory vertex_buffer2_mem;
+ VkDeviceSize vertex_buffer2_size;
+
VkBuffer index_buffer;
VkDeviceMemory index_buffer_mem;
VkDeviceSize index_buffer_size;
+
+ int vertex_buffer_initialized;
+ int index_buffer_initialized;
} krosshair_draw_t;
+struct dynamic_push_constants {
+ float quad_ndc_min[2]; /* 8 bytes */
+ float quad_ndc_size[2]; /* 8 bytes */
+ float invert_str; /* 4 */
+ float dodge_str; /* 4 */
+ float dodge_r, dodge_g, dodge_b; /* 12 */
+ float burn_str; /* 4 */
+ float burn_r, burn_g, burn_b; /* 12 */
+ float complement_str; /* 4 */
+ float lumainvert_str; /* 4 */
+ float huerotate_str; /* 4 */
+ float huerotate_angle; /* 4 */
+ float saturate_str; /* 4 */
+ float saturate_amount; /* 4 */
+ float opacity; /* 4 */
+};
+/* Total: 80 bytes (20 floats) */
+
+static void parse_dynamic_cfg(const char* path, struct dynamic_push_constants* pc)
+{
+ /* zero everything except quad_ndc_min/size (caller sets those) */
+ pc->invert_str = 0.0f;
+ pc->dodge_str = 0.0f;
+ pc->dodge_r = 1.0f; pc->dodge_g = 1.0f; pc->dodge_b = 1.0f;
+ pc->burn_str = 0.0f;
+ pc->burn_r = 1.0f; pc->burn_g = 1.0f; pc->burn_b = 1.0f;
+ pc->complement_str = 0.0f;
+ pc->lumainvert_str = 0.0f;
+ pc->huerotate_str = 0.0f;
+ pc->huerotate_angle = 180.0f;
+ pc->saturate_str = 0.0f;
+ pc->saturate_amount = 0.0f;
+ pc->opacity = 1.0f;
+
+ if (!path) return;
+
+ FILE* f = fopen(path, "r");
+ if (!f) return;
+
+ char line[512];
+ while (fgets(line, sizeof(line), f)) {
+ /* skip comments and blank lines */
+ if (line[0] == '#' || line[0] == '\n' || line[0] == '\r')
+ continue;
+
+ char name[64];
+ float a1 = 0, a2 = 0, a3 = 0, a4 = 0;
+ int n = sscanf(line, "%63s %f %f %f %f", name, &a1, &a2, &a3, &a4);
+ if (n < 2) continue;
+
+ if (strcmp(name, "invert") == 0) {
+ pc->invert_str = a1;
+ } else if (strcmp(name, "dodge") == 0) {
+ pc->dodge_str = a1;
+ if (n >= 5) { pc->dodge_r = a2; pc->dodge_g = a3; pc->dodge_b = a4; }
+ } else if (strcmp(name, "burn") == 0) {
+ pc->burn_str = a1;
+ if (n >= 5) { pc->burn_r = a2; pc->burn_g = a3; pc->burn_b = a4; }
+ } else if (strcmp(name, "complement") == 0) {
+ pc->complement_str = a1;
+ } else if (strcmp(name, "lumainvert") == 0) {
+ pc->lumainvert_str = a1;
+ } else if (strcmp(name, "huerotate") == 0) {
+ pc->huerotate_str = a1;
+ if (n >= 3) pc->huerotate_angle = a2;
+ } else if (strcmp(name, "saturate") == 0) {
+ pc->saturate_str = a1;
+ if (n >= 3) pc->saturate_amount = a2;
+ } else if (strcmp(name, "opacity") == 0) {
+ pc->opacity = a1;
+ }
+ }
+ fclose(f);
+}
+
typedef struct swapchain_data {
device_data_t* device_data;
@@ -383,37 +468,71 @@ typedef struct swapchain_data {
VkBuffer crosshair_upload_buffer;
VkDeviceMemory crosshair_upload_buffer_mem;
+ char* crosshair_path;
+ struct timespec crosshair_mtime;
+
+ /* crosshair texture dimensions (for vertex setup) */
+ int crosshair_tex_width;
+
+ /* per-swapchain vertex data (avoids global shared across swapchains) */
+ vertex_t vertices[4];
+
+ /* animation state (GIF / APNG) */
+ int anim_frame_count;
+ int anim_frame_height; /* height of a single frame */
+ int* anim_delays; /* delay per frame in ms */
+ int anim_current_frame;
+ struct timespec anim_last_frame_time;
+
krosshair_draw_t* draw;
+ /* ── single dynamic effect mask (optional) ── */
+ struct {
+ int uploaded;
+ VkImage image;
+ VkImageView image_view;
+ VkDeviceMemory mem;
+ VkBuffer upload_buffer;
+ VkDeviceMemory upload_buffer_mem;
+ VkDescriptorSet descriptor_set;
+ char* path;
+ struct timespec mtime;
+ int tex_width;
+ vertex_t vertices[4];
+ } dynamic_mask;
+ char* dynamic_cfg_path;
+ struct timespec dynamic_cfg_mtime;
+ struct dynamic_push_constants dynamic_pc;
+
+ /* game framebuffer copy (for shader-based dynamic effects) */
+ VkImage game_fb_image;
+ VkImageView game_fb_image_view;
+ VkDeviceMemory game_fb_mem;
+ uint32_t game_fb_width, game_fb_height;
+
+ /* separate descriptor set layout + pipeline for shader-based dynamic */
+ VkDescriptorSetLayout shader_desc_layout;
+ VkDescriptorPool shader_desc_pool;
+ VkPipelineLayout shader_pipeline_layout;
+ VkPipeline shader_pipeline;
+
+ /* descriptor set for dynamic mask + game_fb */
+ VkDescriptorSet shader_mask_desc_set;
+
} swapchain_data_t;
-// TODO: change all of this to be adjustable at runtime
-// const float texture_size_px = 24.0f;
-// const float screen_width = 1920.0f;
-// const float screen_height = 1080.0f;
-//
-// const float width = (texture_size_px / screen_width) * 2.0f;
-// const float height = (texture_size_px / screen_height) * 2.0f;
-//
-// const float tex_height = 520.0f;
-// const float tex_width = 520.0f;
-//
-// const float texture_correct_width = width * (tex_height / tex_width);
-
-// vertex_t vertices[] = {
-// {{-texture_correct_width, -height}, {0.0f, 0.0f}},
-// { {texture_correct_width, -height}, {1.0f, 0.0f}},
-// { {texture_correct_width, height}, {1.0f, 1.0f}},
-// { {-texture_correct_width, height}, {0.0f, 1.0f}}
-// };
-//
-vertex_t vertices[4] = {0};
-
-static void setup_vertices(float canvas_width, float canvas_height,
- float tex_width, float tex_height, float scale)
+/*
+ * Set up the quad vertices for rendering.
+ * uv_top / uv_bottom: vertical UV range (0..1 for full texture,
+ * or a sub-range for atlas frame selection)
+ */
+static void setup_vertices_uv(vertex_t* vertices,
+ float canvas_width, float canvas_height,
+ float tex_width, float tex_height, float scale,
+ float uv_top, float uv_bottom)
{
float width_ndc = ((tex_width * scale) / canvas_width);
- float height_ndc = ((tex_width * scale)/ canvas_height) ;
+ float height_ndc = ((tex_width * scale) / canvas_height);
/* should fix even-length crosshairs */
float pixel_offset_x =
@@ -423,16 +542,24 @@ static void setup_vertices(float canvas_width, float canvas_height,
vertices[0].pos =
(vec2_t){-width_ndc + pixel_offset_x, -height_ndc + pixel_offset_y};
- vertices[0].tex_pos = (vec2_t){0.0f, 0.0f};
+ vertices[0].tex_pos = (vec2_t){0.0f, uv_top};
vertices[1].pos =
(vec2_t){width_ndc + pixel_offset_x, -height_ndc + pixel_offset_y};
- vertices[1].tex_pos = (vec2_t){1.0f, 0.0f};
+ vertices[1].tex_pos = (vec2_t){1.0f, uv_top};
vertices[2].pos =
(vec2_t){width_ndc + pixel_offset_x, height_ndc + pixel_offset_y};
- vertices[2].tex_pos = (vec2_t){1.0f, 1.0f};
+ vertices[2].tex_pos = (vec2_t){1.0f, uv_bottom};
vertices[3].pos =
(vec2_t){-width_ndc + pixel_offset_x, height_ndc + pixel_offset_y};
- vertices[3].tex_pos = (vec2_t){0.0f, 1.0f};
+ vertices[3].tex_pos = (vec2_t){0.0f, uv_bottom};
+}
+
+static void setup_vertices(vertex_t* vertices,
+ float canvas_width, float canvas_height,
+ float tex_width, float tex_height, float scale)
+{
+ setup_vertices_uv(vertices, canvas_width, canvas_height, tex_width,
+ tex_height, scale, 0.0f, 1.0f);
}
uint16_t indices[] = {0, 1, 2, 2, 3, 0};
@@ -506,8 +633,8 @@ static instance_data_t* new_instance_data(VkInstance instance)
{
instance_data_t* instance_data = malloc(sizeof(instance_data_t));
instance_data->instance = instance;
- printf("[*] mapping data->instance obj: %lu data: %p\n",
- HKEY(instance_data->instance), instance_data);
+ KROSSHAIR_LOG("[*] mapping data->instance obj: %lu data: %p\n",
+ HKEY(instance_data->instance), (void*)instance_data);
map_object(HKEY(instance_data->instance), instance_data,
"instance_data->instance");
return instance_data;
@@ -519,8 +646,8 @@ static device_data_t* new_device_data(VkDevice device,
device_data_t* device_data = malloc(sizeof(device_data_t));
device_data->instance = instance;
device_data->device = device;
- printf("[*] mapping data->device obj: %lu %p\n",
- HKEY(device_data->device), device_data);
+ KROSSHAIR_LOG("[*] mapping data->device obj: %lu %p\n",
+ HKEY(device_data->device), (void*)device_data);
map_object(HKEY(device_data->device), device_data,
"device_data->device");
return device_data;
@@ -534,8 +661,8 @@ static cmd_buffer_data_t* new_cmd_buffer_data(VkCommandBuffer cmd_buffer,
cmdbuffer_data->device_data = device_data;
cmdbuffer_data->cmd_buffer = cmd_buffer;
cmdbuffer_data->level = level;
- printf("[*] mapping data->cmd_buffer obj: %lu %p\n",
- HKEY(cmdbuffer_data->cmd_buffer), cmdbuffer_data);
+ KROSSHAIR_LOG("[*] mapping data->cmd_buffer obj: %lu %p\n",
+ HKEY(cmdbuffer_data->cmd_buffer), (void*)cmdbuffer_data);
map_object(HKEY(cmdbuffer_data->cmd_buffer), cmdbuffer_data,
"cmdbuffer_data->cmd_buffer");
return cmdbuffer_data;
@@ -588,20 +715,223 @@ static void create_or_resize_buffer(device_data_t* device_data,
*buffer_size = new_size;
}
+static void shutdown_dynamic_mask(swapchain_data_t* data);
+
static void shutdown_krosshair_image(swapchain_data_t* data)
{
device_data_t* device_data = data->device_data;
- device_data->vtable.DestroyImageView(device_data->device,
- data->crosshair_image_view, NULL);
- device_data->vtable.DestroyImage(device_data->device,
- data->crosshair_image, NULL);
- device_data->vtable.FreeMemory(device_data->device, data->crosshair_mem,
- NULL);
-
- device_data->vtable.DestroyBuffer(device_data->device,
- data->crosshair_upload_buffer, NULL);
- device_data->vtable.FreeMemory(device_data->device,
- data->crosshair_upload_buffer_mem, NULL);
+
+ if (data->crosshair_image_view) {
+ device_data->vtable.DestroyImageView(device_data->device,
+ data->crosshair_image_view, NULL);
+ data->crosshair_image_view = VK_NULL_HANDLE;
+ }
+ if (data->crosshair_image) {
+ device_data->vtable.DestroyImage(device_data->device,
+ data->crosshair_image, NULL);
+ data->crosshair_image = VK_NULL_HANDLE;
+ }
+ if (data->crosshair_mem) {
+ device_data->vtable.FreeMemory(device_data->device, data->crosshair_mem,
+ NULL);
+ data->crosshair_mem = VK_NULL_HANDLE;
+ }
+
+ if (data->crosshair_upload_buffer) {
+ device_data->vtable.DestroyBuffer(device_data->device,
+ data->crosshair_upload_buffer, NULL);
+ data->crosshair_upload_buffer = VK_NULL_HANDLE;
+ }
+ if (data->crosshair_upload_buffer_mem) {
+ device_data->vtable.FreeMemory(device_data->device,
+ data->crosshair_upload_buffer_mem, NULL);
+ data->crosshair_upload_buffer_mem = VK_NULL_HANDLE;
+ }
+
+ if (data->descriptor_set) {
+ /* Tear down dynamic mask GPU resources BEFORE resetting the pool,
+ * since its descriptor set comes from the same pool and
+ * would become invalid. shutdown_dynamic_mask only destroys
+ * image/view/memory — the descriptor_set handle is wiped
+ * by the pool reset below. */
+ shutdown_dynamic_mask(data);
+ data->dynamic_mask.uploaded = 0;
+ if (data->dynamic_mask.path) {
+ free(data->dynamic_mask.path);
+ data->dynamic_mask.path = NULL;
+ }
+
+ device_data->vtable.ResetDescriptorPool(device_data->device,
+ data->descriptor_pool, 0);
+ data->descriptor_set = VK_NULL_HANDLE;
+
+ /* also reset shader desc pool (shader mask desc set is invalid) */
+ if (data->shader_desc_pool) {
+ device_data->vtable.ResetDescriptorPool(
+ device_data->device, data->shader_desc_pool, 0);
+ data->shader_mask_desc_set = VK_NULL_HANDLE;
+ }
+ }
+
+ /* clean up animation state */
+ if (data->anim_delays) {
+ free(data->anim_delays);
+ data->anim_delays = NULL;
+ }
+ data->anim_frame_count = 0;
+ data->anim_frame_height = 0;
+ data->anim_current_frame = 0;
+ data->crosshair_tex_width = 0;
+}
+
+static void shutdown_dynamic_mask(swapchain_data_t* data)
+{
+ device_data_t* device_data = data->device_data;
+
+ if (data->dynamic_mask.image_view) {
+ device_data->vtable.DestroyImageView(device_data->device,
+ data->dynamic_mask.image_view, NULL);
+ data->dynamic_mask.image_view = VK_NULL_HANDLE;
+ }
+ if (data->dynamic_mask.image) {
+ device_data->vtable.DestroyImage(device_data->device,
+ data->dynamic_mask.image, NULL);
+ data->dynamic_mask.image = VK_NULL_HANDLE;
+ }
+ if (data->dynamic_mask.mem) {
+ device_data->vtable.FreeMemory(device_data->device,
+ data->dynamic_mask.mem, NULL);
+ data->dynamic_mask.mem = VK_NULL_HANDLE;
+ }
+ if (data->dynamic_mask.upload_buffer) {
+ device_data->vtable.DestroyBuffer(device_data->device,
+ data->dynamic_mask.upload_buffer, NULL);
+ data->dynamic_mask.upload_buffer = VK_NULL_HANDLE;
+ }
+ if (data->dynamic_mask.upload_buffer_mem) {
+ device_data->vtable.FreeMemory(device_data->device,
+ data->dynamic_mask.upload_buffer_mem, NULL);
+ data->dynamic_mask.upload_buffer_mem = VK_NULL_HANDLE;
+ }
+
+ data->dynamic_mask.descriptor_set = VK_NULL_HANDLE;
+ data->dynamic_mask.tex_width = 0;
+}
+
+static void destroy_draw(swapchain_data_t* data, krosshair_draw_t* draw);
+
+static void destroy_swapchain_data(swapchain_data_t* data)
+{
+ if (!data) return;
+
+ device_data_t* device_data = data->device_data;
+
+ if (data->draw) {
+ destroy_draw(data, data->draw);
+ data->draw = NULL;
+ }
+
+ shutdown_krosshair_image(data);
+ shutdown_dynamic_mask(data);
+
+ /* shader-based dynamic resources */
+ if (data->game_fb_image_view) {
+ device_data->vtable.DestroyImageView(device_data->device,
+ data->game_fb_image_view, NULL);
+ data->game_fb_image_view = VK_NULL_HANDLE;
+ }
+ if (data->game_fb_image) {
+ device_data->vtable.DestroyImage(device_data->device,
+ data->game_fb_image, NULL);
+ data->game_fb_image = VK_NULL_HANDLE;
+ }
+ if (data->game_fb_mem) {
+ device_data->vtable.FreeMemory(device_data->device,
+ data->game_fb_mem, NULL);
+ data->game_fb_mem = VK_NULL_HANDLE;
+ }
+ if (data->shader_pipeline != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyPipeline(device_data->device,
+ data->shader_pipeline, NULL);
+ data->shader_pipeline = VK_NULL_HANDLE;
+ }
+ if (data->shader_pipeline_layout != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyPipelineLayout(device_data->device,
+ data->shader_pipeline_layout, NULL);
+ data->shader_pipeline_layout = VK_NULL_HANDLE;
+ }
+ if (data->shader_desc_pool != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyDescriptorPool(device_data->device,
+ data->shader_desc_pool, NULL);
+ data->shader_desc_pool = VK_NULL_HANDLE;
+ }
+ if (data->shader_desc_layout != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyDescriptorSetLayout(device_data->device,
+ data->shader_desc_layout, NULL);
+ data->shader_desc_layout = VK_NULL_HANDLE;
+ }
+
+ if (data->descriptor_pool != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyDescriptorPool(device_data->device,
+ data->descriptor_pool, NULL);
+ data->descriptor_pool = VK_NULL_HANDLE;
+ }
+ if (data->descriptor_layout != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyDescriptorSetLayout(device_data->device,
+ data->descriptor_layout, NULL);
+ data->descriptor_layout = VK_NULL_HANDLE;
+ }
+ if (data->crosshair_sampler != VK_NULL_HANDLE) {
+ device_data->vtable.DestroySampler(device_data->device,
+ data->crosshair_sampler, NULL);
+ data->crosshair_sampler = VK_NULL_HANDLE;
+ }
+ if (data->pipeline != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyPipeline(device_data->device,
+ data->pipeline, NULL);
+ data->pipeline = VK_NULL_HANDLE;
+ }
+ if (data->pipeline_layout != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyPipelineLayout(device_data->device,
+ data->pipeline_layout, NULL);
+ data->pipeline_layout = VK_NULL_HANDLE;
+ }
+ if (data->render_pass != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyRenderPass(device_data->device,
+ data->render_pass, NULL);
+ data->render_pass = VK_NULL_HANDLE;
+ }
+ if (data->cmd_pool != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyCommandPool(device_data->device,
+ data->cmd_pool, NULL);
+ data->cmd_pool = VK_NULL_HANDLE;
+ }
+
+ for (uint32_t i = 0; i < data->n_images; i++) {
+ if (data->framebuffers[i] != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyFramebuffer(device_data->device,
+ data->framebuffers[i], NULL);
+ data->framebuffers[i] = VK_NULL_HANDLE;
+ }
+ if (data->image_views[i] != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyImageView(device_data->device,
+ data->image_views[i], NULL);
+ data->image_views[i] = VK_NULL_HANDLE;
+ }
+ }
+
+ if (data->crosshair_path) {
+ free(data->crosshair_path);
+ data->crosshair_path = NULL;
+ }
+ if (data->dynamic_mask.path) {
+ free(data->dynamic_mask.path);
+ data->dynamic_mask.path = NULL;
+ }
+ if (data->dynamic_cfg_path) {
+ free(data->dynamic_cfg_path);
+ data->dynamic_cfg_path = NULL;
+ }
}
static void update_image_descriptor(swapchain_data_t* data,
@@ -701,15 +1031,25 @@ static VkDescriptorSet create_image_with_desc(swapchain_data_t* data,
return descriptor_set;
}
-static void upload_image_data(device_data_t* device_data,
- VkCommandBuffer cmd_buffer, void* pixels,
- VkDeviceSize upload_size, uint32_t width,
- uint32_t height, VkBuffer* upload_buffer,
- VkDeviceMemory* upload_buffer_mem, VkImage image)
+static void create_or_resize_upload_buffer(device_data_t* device_data,
+ VkBuffer* upload_buffer,
+ VkDeviceMemory* upload_buffer_mem,
+ VkDeviceSize new_size)
{
+ if (*upload_buffer != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyBuffer(device_data->device, *upload_buffer,
+ NULL);
+ *upload_buffer = VK_NULL_HANDLE;
+ }
+ if (*upload_buffer_mem) {
+ device_data->vtable.FreeMemory(device_data->device, *upload_buffer_mem,
+ NULL);
+ *upload_buffer_mem = VK_NULL_HANDLE;
+ }
+
VkBufferCreateInfo buffer_info = {};
buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
- buffer_info.size = upload_size;
+ buffer_info.size = new_size;
buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VK_CHECK(device_data->vtable.CreateBuffer(
@@ -729,6 +1069,17 @@ static void upload_image_data(device_data_t* device_data,
device_data->device, &upload_alloc_info, NULL, upload_buffer_mem));
VK_CHECK(device_data->vtable.BindBufferMemory(
device_data->device, *upload_buffer, *upload_buffer_mem, 0));
+}
+
+static void upload_image_data(device_data_t* device_data,
+ VkCommandBuffer cmd_buffer, void* pixels,
+ VkDeviceSize upload_size, uint32_t width,
+ uint32_t height, VkBuffer* upload_buffer,
+ VkDeviceMemory* upload_buffer_mem, VkImage image)
+{
+ /* always create the upload buffer - each swapchain has its own */
+ create_or_resize_upload_buffer(device_data, upload_buffer,
+ upload_buffer_mem, upload_size);
char* map = NULL;
VK_CHECK(device_data->vtable.MapMemory(device_data->device,
@@ -811,11 +1162,526 @@ static char* get_crosshair_file(const char* path)
return expanded_str;
}
+static int get_file_mtime(const char* path, struct timespec* mtime)
+{
+ struct stat st;
+ if (stat(path, &st) != 0) return -1;
+ *mtime = st.st_mtim;
+ return 0;
+}
+
+// malloc's returned string, free later
+// returns crosshair-maker default if installed and no explicit KROSSHAIR_IMG set
+// tries current.apng first, then current.gif, then current.png
+static char* get_crosshair_path(void)
+{
+ const char* explicit = getenv("KROSSHAIR_IMG");
+ if (explicit)
+ return get_crosshair_file(explicit);
+
+ const char* home = getenv("HOME");
+ if (!home)
+ return NULL;
+
+ char cm_path[4096];
+ struct stat st;
+
+ /* prefer animated formats first, then static PNG */
+ snprintf(cm_path, sizeof(cm_path),
+ "%s/.config/crosshair-maker/projects/current.apng", home);
+ if (stat(cm_path, &st) == 0)
+ return strdup(cm_path);
+
+ snprintf(cm_path, sizeof(cm_path),
+ "%s/.config/crosshair-maker/projects/current.gif", home);
+ if (stat(cm_path, &st) == 0)
+ return strdup(cm_path);
+
+ snprintf(cm_path, sizeof(cm_path),
+ "%s/.config/crosshair-maker/projects/current.png", home);
+ if (stat(cm_path, &st) == 0)
+ return strdup(cm_path);
+
+ return NULL;
+}
+
+// malloc's returned string, free later
+// returns the path to {stem}.dynamic.png, or NULL if absent
+static char* get_dynamic_mask_path(void)
+{
+ const char* home = getenv("HOME");
+ if (!home) return NULL;
+
+ char cm_path[4096];
+ struct stat st;
+
+ snprintf(cm_path, sizeof(cm_path),
+ "%s/.config/crosshair-maker/projects/current.dynamic.png",
+ home);
+ if (stat(cm_path, &st) == 0)
+ return strdup(cm_path);
+
+ return NULL;
+}
+
+// malloc's returned string, free later
+// returns the path to {stem}.dynamic.cfg, or NULL if absent
+static char* get_dynamic_cfg_path(void)
+{
+ const char* home = getenv("HOME");
+ if (!home) return NULL;
+
+ char cm_path[4096];
+ struct stat st;
+
+ snprintf(cm_path, sizeof(cm_path),
+ "%s/.config/crosshair-maker/projects/current.dynamic.cfg",
+ home);
+ if (stat(cm_path, &st) == 0)
+ return strdup(cm_path);
+
+ return NULL;
+}
+
+/* ───────────────────── APNG loader ───────────────────── */
+
+static const unsigned char png_signature[8] = {
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a
+};
+
+static uint32_t apng_read_be32(const unsigned char* p)
+{
+ return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
+ ((uint32_t)p[2] << 8) | (uint32_t)p[3];
+}
+
+static uint16_t apng_read_be16(const unsigned char* p)
+{
+ return ((uint16_t)p[0] << 8) | (uint16_t)p[1];
+}
+
+static void apng_write_be32(unsigned char* p, uint32_t v)
+{
+ p[0] = (v >> 24) & 0xff;
+ p[1] = (v >> 16) & 0xff;
+ p[2] = (v >> 8) & 0xff;
+ p[3] = v & 0xff;
+}
+
+/* CRC32 used by PNG chunk checksums */
+static uint32_t apng_crc32_table[256];
+static int apng_crc32_table_ready = 0;
+
+static void apng_crc32_init(void)
+{
+ if (apng_crc32_table_ready) return;
+ for (uint32_t i = 0; i < 256; i++) {
+ uint32_t c = i;
+ for (int j = 0; j < 8; j++)
+ c = (c & 1) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
+ apng_crc32_table[i] = c;
+ }
+ apng_crc32_table_ready = 1;
+}
+
+static uint32_t apng_crc32(const unsigned char* data, size_t len)
+{
+ uint32_t crc = 0xFFFFFFFF;
+ for (size_t i = 0; i < len; i++)
+ crc = apng_crc32_table[(crc ^ data[i]) & 0xff] ^ (crc >> 8);
+ return crc ^ 0xFFFFFFFF;
+}
+
+/* write a complete PNG chunk: length + type + data + crc */
+static size_t apng_write_chunk(unsigned char* out, const char* type,
+ const unsigned char* data, uint32_t data_len)
+{
+ apng_write_be32(out, data_len);
+ memcpy(out + 4, type, 4);
+ if (data && data_len)
+ memcpy(out + 8, data, data_len);
+ /* CRC covers type + data */
+ uint32_t crc = apng_crc32(out + 4, 4 + data_len);
+ apng_write_be32(out + 8 + data_len, crc);
+ return 12 + data_len; /* length(4) + type(4) + data + crc(4) */
+}
+
+typedef struct apng_frame_info {
+ uint32_t width, height;
+ uint32_t x_offset, y_offset;
+ uint16_t delay_num, delay_den;
+ uint8_t dispose_op, blend_op;
+ /* raw compressed data (IDAT content or fdAT content minus seq) */
+ unsigned char* idat_data;
+ size_t idat_size;
+ size_t idat_capacity;
+} apng_frame_info_t;
+
+static void apng_frame_append_idat(apng_frame_info_t* f,
+ const unsigned char* data, size_t len)
+{
+ if (f->idat_size + len > f->idat_capacity) {
+ size_t new_cap = (f->idat_capacity == 0) ? 4096 : f->idat_capacity;
+ while (new_cap < f->idat_size + len)
+ new_cap *= 2;
+ f->idat_data = realloc(f->idat_data, new_cap);
+ f->idat_capacity = new_cap;
+ }
+ memcpy(f->idat_data + f->idat_size, data, len);
+ f->idat_size += len;
+}
+
+/*
+ * Build a minimal valid PNG in memory for a single APNG frame,
+ * then decode it with stbi. Returns RGBA pixels (caller must
+ * stbi_image_free), or NULL on failure.
+ *
+ * ihdr_raw: the 13 bytes of the original IHDR *data* (no length/type/crc)
+ */
+static stbi_uc* apng_decode_frame(const apng_frame_info_t* f,
+ const unsigned char* ihdr_raw,
+ int* out_w, int* out_h)
+{
+ /* build a new IHDR with the frame's dimensions */
+ unsigned char ihdr[13];
+ memcpy(ihdr, ihdr_raw, 13);
+ apng_write_be32(ihdr + 0, f->width);
+ apng_write_be32(ihdr + 4, f->height);
+
+ /* total PNG size: sig(8) + IHDR chunk(25) + IDAT chunk(12+data) + IEND(12) */
+ size_t png_size = 8 + 25 + (12 + f->idat_size) + 12;
+ unsigned char* png = malloc(png_size);
+ if (!png) return NULL;
+
+ size_t off = 0;
+ memcpy(png, png_signature, 8);
+ off += 8;
+ off += apng_write_chunk(png + off, "IHDR", ihdr, 13);
+ off += apng_write_chunk(png + off, "IDAT", f->idat_data, (uint32_t)f->idat_size);
+ off += apng_write_chunk(png + off, "IEND", NULL, 0);
+
+ int w, h, c;
+ stbi_uc* pixels = stbi_load_from_memory(png, (int)off, &w, &h, &c, 4);
+ free(png);
+
+ if (pixels) {
+ *out_w = w;
+ *out_h = h;
+ }
+ return pixels;
+}
+
+/* alpha-composite src over dst (premultiply-aware, straight alpha) */
+static void apng_blend_over(unsigned char* dst, const unsigned char* src,
+ uint32_t canvas_w, uint32_t canvas_h,
+ uint32_t x_off, uint32_t y_off,
+ uint32_t fw, uint32_t fh)
+{
+ for (uint32_t y = 0; y < fh; y++) {
+ if (y + y_off >= canvas_h) break;
+ for (uint32_t x = 0; x < fw; x++) {
+ if (x + x_off >= canvas_w) break;
+ size_t di = ((y + y_off) * canvas_w + (x + x_off)) * 4;
+ size_t si = (y * fw + x) * 4;
+ uint8_t sa = src[si + 3];
+ if (sa == 255) {
+ memcpy(dst + di, src + si, 4);
+ } else if (sa > 0) {
+ uint8_t da = dst[di + 3];
+ /* standard "over" blend */
+ for (int k = 0; k < 3; k++) {
+ int sv = src[si + k] * sa;
+ int dv = dst[di + k] * da * (255 - sa) / 255;
+ int oa = sa + da * (255 - sa) / 255;
+ dst[di + k] = oa ? (uint8_t)((sv + dv) / oa) : 0;
+ }
+ dst[di + 3] = (uint8_t)(sa + da * (255 - sa) / 255);
+ }
+ }
+ }
+}
+
+/* copy src frame pixels into canvas at offset (source replace, no blending) */
+static void apng_blend_source(unsigned char* dst, const unsigned char* src,
+ uint32_t canvas_w, uint32_t canvas_h,
+ uint32_t x_off, uint32_t y_off,
+ uint32_t fw, uint32_t fh)
+{
+ for (uint32_t y = 0; y < fh; y++) {
+ if (y + y_off >= canvas_h) break;
+ size_t di = ((y + y_off) * canvas_w + x_off) * 4;
+ size_t si = (y * fw) * 4;
+ uint32_t copy_w = fw;
+ if (x_off + fw > canvas_w) copy_w = canvas_w - x_off;
+ memcpy(dst + di, src + si, copy_w * 4);
+ }
+}
+
+/* clear a region to transparent black */
+static void apng_clear_region(unsigned char* canvas, uint32_t canvas_w,
+ uint32_t canvas_h, uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ for (uint32_t row = y; row < y + h && row < canvas_h; row++) {
+ size_t off = (row * canvas_w + x) * 4;
+ uint32_t cw = w;
+ if (x + w > canvas_w) cw = canvas_w - x;
+ memset(canvas + off, 0, cw * 4);
+ }
+}
+
+/*
+ * Load an APNG file and return a vertical atlas of fully-composited frames,
+ * identical in layout to what stbi_load_gif_from_memory produces.
+ *
+ * Returns RGBA pixel data (free with free()) or NULL on failure.
+ * *out_width / *out_height are per-frame dimensions.
+ * *out_frames is the frame count.
+ * *out_delays is malloc'd array of per-frame delays in ms (free with free()).
+ */
+static unsigned char* load_apng(const unsigned char* file_data, size_t file_len,
+ int* out_width, int* out_height,
+ int* out_frames, int** out_delays)
+{
+ apng_crc32_init();
+
+ if (file_len < 8 + 25 || memcmp(file_data, png_signature, 8) != 0) {
+ KROSSHAIR_LOG("[APNG] not a PNG file\n");
+ return NULL;
+ }
+
+ /* parse IHDR */
+ size_t pos = 8;
+ uint32_t chunk_len = apng_read_be32(file_data + pos);
+ if (memcmp(file_data + pos + 4, "IHDR", 4) != 0 || chunk_len != 13) {
+ KROSSHAIR_LOG("[APNG] missing IHDR\n");
+ return NULL;
+ }
+ const unsigned char* ihdr_data = file_data + pos + 8;
+ uint32_t canvas_w = apng_read_be32(ihdr_data);
+ uint32_t canvas_h = apng_read_be32(ihdr_data + 4);
+
+ /* first pass: find acTL and count frames */
+ uint32_t num_frames = 0;
+ int found_actl = 0;
+ size_t scan = 8;
+ while (scan + 12 <= file_len) {
+ uint32_t clen = apng_read_be32(file_data + scan);
+ const unsigned char* ctype = file_data + scan + 4;
+ if (scan + 12 + clen > file_len) break;
+ if (memcmp(ctype, "acTL", 4) == 0 && clen >= 8) {
+ num_frames = apng_read_be32(file_data + scan + 8);
+ found_actl = 1;
+ }
+ if (memcmp(ctype, "IEND", 4) == 0) break;
+ scan += 12 + clen;
+ }
+
+ if (!found_actl || num_frames < 1) {
+ KROSSHAIR_LOG("[APNG] no acTL chunk or 0 frames (not an APNG)\n");
+ return NULL;
+ }
+
+ /* allocate frame info array */
+ apng_frame_info_t* frames = calloc(num_frames, sizeof(apng_frame_info_t));
+ if (!frames) return NULL;
+
+ /* second pass: collect fcTL + IDAT/fdAT data per frame */
+ int current_frame = -1; /* index into frames[] */
+ int first_frame_is_default = 0; /* fcTL before first IDAT? */
+ int seen_idat = 0;
+
+ pos = 8;
+ while (pos + 12 <= file_len) {
+ uint32_t clen = apng_read_be32(file_data + pos);
+ const unsigned char* ctype = file_data + pos + 4;
+ const unsigned char* cdata = file_data + pos + 8;
+ if (pos + 12 + clen > file_len) break;
+
+ if (memcmp(ctype, "fcTL", 4) == 0 && clen >= 26) {
+ current_frame++;
+ if (current_frame >= (int)num_frames) break;
+
+ if (!seen_idat && current_frame == 0)
+ first_frame_is_default = 1;
+
+ apng_frame_info_t* f = &frames[current_frame];
+ f->width = apng_read_be32(cdata + 4);
+ f->height = apng_read_be32(cdata + 8);
+ f->x_offset = apng_read_be32(cdata + 12);
+ f->y_offset = apng_read_be32(cdata + 16);
+ f->delay_num = apng_read_be16(cdata + 20);
+ f->delay_den = apng_read_be16(cdata + 22);
+ f->dispose_op = cdata[24];
+ f->blend_op = cdata[25];
+ } else if (memcmp(ctype, "IDAT", 4) == 0) {
+ seen_idat = 1;
+ if (first_frame_is_default && current_frame == 0) {
+ apng_frame_append_idat(&frames[0], cdata, clen);
+ }
+ } else if (memcmp(ctype, "fdAT", 4) == 0 && clen > 4) {
+ /* fdAT: first 4 bytes are sequence number, rest is
+ * IDAT-equivalent data */
+ if (current_frame >= 0 && current_frame < (int)num_frames) {
+ apng_frame_append_idat(&frames[current_frame],
+ cdata + 4, clen - 4);
+ }
+ } else if (memcmp(ctype, "IEND", 4) == 0) {
+ break;
+ }
+
+ pos += 12 + clen;
+ }
+
+ /* the actual number of frames we collected may be less than num_frames
+ * (e.g., truncated file) */
+ int actual_frames = current_frame + 1;
+ if (actual_frames < 1) {
+ KROSSHAIR_LOG("[APNG] no frames found\n");
+ for (uint32_t i = 0; i < num_frames; i++)
+ free(frames[i].idat_data);
+ free(frames);
+ return NULL;
+ }
+ if (actual_frames < (int)num_frames) {
+ KROSSHAIR_LOG("[APNG] warning: expected %u frames but found %d\n",
+ num_frames, actual_frames);
+ }
+
+ /* build vertical atlas: each row is canvas_w x canvas_h */
+ size_t frame_stride = (size_t)canvas_w * canvas_h * 4;
+ unsigned char* atlas = calloc(actual_frames, frame_stride);
+ int* delays = malloc(sizeof(int) * actual_frames);
+ unsigned char* canvas = calloc(1, frame_stride);
+ unsigned char* prev_canvas = NULL; /* for dispose_op = APNG_DISPOSE_OP_PREVIOUS */
+ if (!atlas || !delays || !canvas) {
+ free(atlas);
+ free(delays);
+ free(canvas);
+ for (uint32_t i = 0; i < num_frames; i++)
+ free(frames[i].idat_data);
+ free(frames);
+ return NULL;
+ }
+
+ for (int i = 0; i < actual_frames; i++) {
+ apng_frame_info_t* f = &frames[i];
+
+ /* compute delay in ms */
+ uint16_t den = f->delay_den ? f->delay_den : 100;
+ int delay_ms = (int)((uint32_t)f->delay_num * 1000 / den);
+ if (delay_ms <= 0) delay_ms = 100;
+ delays[i] = delay_ms;
+
+ /* save canvas for APNG_DISPOSE_OP_PREVIOUS before compositing */
+ if (f->dispose_op == 2) { /* APNG_DISPOSE_OP_PREVIOUS */
+ if (!prev_canvas) prev_canvas = malloc(frame_stride);
+ if (prev_canvas) memcpy(prev_canvas, canvas, frame_stride);
+ }
+
+ /* decode this frame's pixels */
+ if (f->idat_size == 0) {
+ KROSSHAIR_LOG("[APNG] frame %d has no image data, skipping\n", i);
+ memcpy(atlas + i * frame_stride, canvas, frame_stride);
+ continue;
+ }
+
+ int fw, fh;
+ stbi_uc* fpix = apng_decode_frame(f, ihdr_data, &fw, &fh);
+ if (!fpix) {
+ KROSSHAIR_LOG("[APNG] failed to decode frame %d\n", i);
+ memcpy(atlas + i * frame_stride, canvas, frame_stride);
+ continue;
+ }
+
+ /* apply blend_op */
+ if (f->blend_op == 0) { /* APNG_BLEND_OP_SOURCE */
+ apng_blend_source(canvas, fpix, canvas_w, canvas_h,
+ f->x_offset, f->y_offset,
+ f->width, f->height);
+ } else { /* APNG_BLEND_OP_OVER */
+ apng_blend_over(canvas, fpix, canvas_w, canvas_h,
+ f->x_offset, f->y_offset,
+ f->width, f->height);
+ }
+ stbi_image_free(fpix);
+
+ /* copy composited canvas to atlas row */
+ memcpy(atlas + i * frame_stride, canvas, frame_stride);
+
+ /* apply dispose_op (affects canvas for NEXT frame) */
+ if (f->dispose_op == 1) { /* APNG_DISPOSE_OP_BACKGROUND */
+ apng_clear_region(canvas, canvas_w, canvas_h,
+ f->x_offset, f->y_offset,
+ f->width, f->height);
+ } else if (f->dispose_op == 2) { /* APNG_DISPOSE_OP_PREVIOUS */
+ if (prev_canvas) memcpy(canvas, prev_canvas, frame_stride);
+ }
+ /* dispose_op 0 (APNG_DISPOSE_OP_NONE): leave canvas as-is */
+ }
+
+ free(canvas);
+ free(prev_canvas);
+ for (uint32_t i = 0; i < num_frames; i++)
+ free(frames[i].idat_data);
+ free(frames);
+
+ *out_width = (int)canvas_w;
+ *out_height = (int)canvas_h;
+ *out_frames = actual_frames;
+ *out_delays = delays;
+
+ KROSSHAIR_LOG("[APNG] decoded %d frames, canvas %ux%u\n",
+ actual_frames, canvas_w, canvas_h);
+
+ return atlas;
+}
+
+/* ────────────────── end APNG loader ─────────────────── */
+
static void ensure_swapchain_crosshair(swapchain_data_t* data,
VkCommandBuffer cmd_buffer)
{
device_data_t* device_data = data->device_data;
- if (data->crosshair_uploaded) return;
+
+ char* crosshair_path = get_crosshair_path();
+ int using_file = (crosshair_path != NULL);
+
+ if (data->crosshair_uploaded) {
+ int needs_reload = 0;
+
+ if (using_file && data->crosshair_path) {
+ if (strcmp(data->crosshair_path, crosshair_path) != 0) {
+ KROSSHAIR_LOG("[KROSSHAIR] path changed, reloading\n");
+ needs_reload = 1;
+ } else {
+ struct timespec new_mtime;
+ if (get_file_mtime(crosshair_path, &new_mtime) == 0) {
+ if (new_mtime.tv_sec != data->crosshair_mtime.tv_sec ||
+ new_mtime.tv_nsec != data->crosshair_mtime.tv_nsec) {
+ KROSSHAIR_LOG("[KROSSHAIR] mtime changed (%ld.%ld -> %ld.%ld), reloading\n",
+ data->crosshair_mtime.tv_sec,
+ data->crosshair_mtime.tv_nsec,
+ new_mtime.tv_sec,
+ new_mtime.tv_nsec);
+ needs_reload = 1;
+ }
+ }
+ }
+ }
+
+ if (!needs_reload) {
+ free(crosshair_path);
+ return;
+ }
+
+ KROSSHAIR_LOG("[KROSSHAIR] shutting down old crosshair image\n");
+
+ shutdown_krosshair_image(data);
+ data->crosshair_uploaded = 0;
+ free(data->crosshair_path);
+ data->crosshair_path = NULL;
+ }
int tex_width;
int tex_height;
@@ -823,18 +1689,196 @@ static void ensure_swapchain_crosshair(swapchain_data_t* data,
VkDeviceSize image_size;
stbi_uc* pixels;
- char* crosshair_path = get_crosshair_file(getenv("KROSSHAIR_IMG"));
+ if (using_file) {
+ const char* ext = strrchr(crosshair_path, '.');
+ int is_gif = ext && (strcasecmp(ext, ".gif") == 0);
+ int is_apng = ext && (strcasecmp(ext, ".apng") == 0);
+
+ /* .png files might also be APNG -- detect by checking for
+ * acTL chunk if the extension is .png */
+ int is_png = ext && (strcasecmp(ext, ".png") == 0);
+
+ if (is_gif) {
+ FILE* f = fopen(crosshair_path, "rb");
+ if (!f) {
+ KROSSHAIR_LOG("[KROSSHAIR_ERROR] failed to open GIF: %s\n",
+ crosshair_path);
+ free(crosshair_path);
+ return;
+ }
- if (crosshair_path) {
- pixels = stbi_load(crosshair_path, &tex_width, &tex_height,
- &tex_channels, STBI_rgb_alpha);
- free(crosshair_path);
- image_size = tex_width * tex_height * 4;
+ fseek(f, 0, SEEK_END);
+ long file_len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (file_len <= 0 || file_len > (long)INT_MAX) {
+ KROSSHAIR_LOG("[KROSSHAIR_ERROR] invalid GIF file size: %ld\n",
+ file_len);
+ fclose(f);
+ free(crosshair_path);
+ return;
+ }
+
+ unsigned char* file_buf = malloc((size_t)file_len);
+ if (!file_buf) {
+ KROSSHAIR_LOG("[KROSSHAIR_ERROR] failed to allocate GIF read buffer\n");
+ fclose(f);
+ free(crosshair_path);
+ return;
+ }
+
+ size_t bytes_read = fread(file_buf, 1, (size_t)file_len, f);
+ fclose(f);
+
+ if ((long)bytes_read != file_len) {
+ KROSSHAIR_LOG("[KROSSHAIR_ERROR] short read on GIF: %zu/%ld\n",
+ bytes_read, file_len);
+ free(file_buf);
+ free(crosshair_path);
+ return;
+ }
+
+ int* delays = NULL;
+ int frames = 0;
+ int gif_len = (int)file_len;
+ stbi_uc* gif_data = stbi_load_gif_from_memory(
+ file_buf, gif_len, &delays, &tex_width, &tex_height,
+ &frames, &tex_channels, STBI_rgb_alpha);
+ free(file_buf);
+
+ if (!gif_data || frames < 1) {
+ KROSSHAIR_LOG("[KROSSHAIR_ERROR] failed to decode GIF: %s (%s)\n",
+ crosshair_path,
+ gif_data ? "no frames" : "decode error");
+ if (gif_data) stbi_image_free(gif_data);
+ if (delays) free(delays);
+ free(crosshair_path);
+ return;
+ }
+
+ /*
+ * Build a vertical texture atlas: all frames stacked
+ * top-to-bottom in a single image. The UV coordinates
+ * are adjusted per-frame to select the right slice.
+ *
+ * gif_data from stbi is already laid out as
+ * [frame0][frame1]...[frameN] contiguously, each
+ * frame being (tex_width * tex_height * 4) bytes,
+ * which is exactly the atlas layout we need.
+ */
+ int frame_height = tex_height;
+ int atlas_height = tex_height * frames;
+ image_size = (VkDeviceSize)tex_width * atlas_height * 4;
+ pixels = gif_data;
+ /* tex_height now refers to the full atlas */
+ tex_height = atlas_height;
+
+ /* store animation state */
+ data->anim_frame_count = frames;
+ data->anim_frame_height = frame_height;
+ data->anim_current_frame = 0;
+ clock_gettime(CLOCK_MONOTONIC, &data->anim_last_frame_time);
+
+ if (delays) {
+ data->anim_delays = malloc(sizeof(int) * frames);
+ for (int gi = 0; gi < frames; gi++) {
+ /* stbi already converts GIF centisecond
+ * delays to milliseconds internally
+ * (10 * cs). delay 0 means "as fast
+ * as possible", default to ~100ms */
+ data->anim_delays[gi] =
+ delays[gi] > 0 ? delays[gi] : 100;
+ }
+ free(delays);
+ } else {
+ data->anim_delays = malloc(sizeof(int) * frames);
+ for (int gi = 0; gi < frames; gi++)
+ data->anim_delays[gi] = 100;
+ }
+
+ KROSSHAIR_LOG("[KROSSHAIR] loaded GIF atlas: %dx%d (%d frames, frame_h=%d)\n",
+ tex_width, atlas_height, frames, frame_height);
+ } else if (is_apng || is_png) {
+ /* try to load as APNG; if it's a plain PNG the
+ * loader will return NULL (no acTL) and we fall
+ * through to stbi_load below */
+ FILE* f = fopen(crosshair_path, "rb");
+ if (!f) {
+ KROSSHAIR_LOG("[KROSSHAIR_ERROR] failed to open: %s\n",
+ crosshair_path);
+ free(crosshair_path);
+ return;
+ }
+
+ fseek(f, 0, SEEK_END);
+ long file_len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ unsigned char* file_buf = NULL;
+ if (file_len > 0 && file_len <= (long)INT_MAX) {
+ file_buf = malloc((size_t)file_len);
+ if (file_buf) {
+ size_t bytes_read = fread(file_buf, 1, (size_t)file_len, f);
+ if ((long)bytes_read != file_len) {
+ free(file_buf);
+ file_buf = NULL;
+ }
+ }
+ }
+ fclose(f);
+
+ int* apng_delays = NULL;
+ int apng_frames = 0;
+ int apng_w = 0, apng_h = 0;
+ unsigned char* apng_data = NULL;
+
+ if (file_buf) {
+ apng_data = load_apng((const unsigned char*)file_buf,
+ (size_t)file_len, &apng_w, &apng_h,
+ &apng_frames, &apng_delays);
+ free(file_buf);
+ }
+
+ if (apng_data && apng_frames > 1) {
+ /* animated APNG -- same atlas approach as GIF */
+ tex_width = apng_w;
+ int frame_height = apng_h;
+ int atlas_height = apng_h * apng_frames;
+ tex_height = atlas_height;
+ image_size = (VkDeviceSize)tex_width * atlas_height * 4;
+ pixels = (stbi_uc*)apng_data;
+
+ data->anim_frame_count = apng_frames;
+ data->anim_frame_height = frame_height;
+ data->anim_current_frame = 0;
+ clock_gettime(CLOCK_MONOTONIC, &data->anim_last_frame_time);
+
+ data->anim_delays = apng_delays;
+
+ KROSSHAIR_LOG("[KROSSHAIR] loaded APNG atlas: %dx%d (%d frames, frame_h=%d)\n",
+ tex_width, atlas_height, apng_frames, frame_height);
+ } else {
+ /* not animated APNG (or single frame) --
+ * fall back to regular stbi_load for
+ * proper PNG handling */
+ if (apng_data) free(apng_data);
+ if (apng_delays) free(apng_delays);
+
+ pixels = stbi_load(crosshair_path, &tex_width, &tex_height,
+ &tex_channels, STBI_rgb_alpha);
+ image_size = tex_width * tex_height * 4;
+ }
+ } else {
+ pixels = stbi_load(crosshair_path, &tex_width, &tex_height,
+ &tex_channels, STBI_rgb_alpha);
+ image_size = tex_width * tex_height * 4;
+ }
if (!pixels) {
- printf(
+ KROSSHAIR_LOG(
"[KROSSHAIR_ERROR] failed to load crosshair "
"image.\n");
+ free(crosshair_path);
return;
}
@@ -848,7 +1892,15 @@ static void ensure_swapchain_crosshair(swapchain_data_t* data,
tex_height, &data->crosshair_upload_buffer,
&data->crosshair_upload_buffer_mem, data->crosshair_image);
stbi_image_free(pixels);
+
+ data->crosshair_path = crosshair_path;
+ get_file_mtime(crosshair_path, &data->crosshair_mtime);
+ KROSSHAIR_LOG("[KROSSHAIR] loaded crosshair from: %s (mtime %ld.%ld)\n",
+ crosshair_path,
+ data->crosshair_mtime.tv_sec,
+ data->crosshair_mtime.tv_nsec);
} else {
+ free(crosshair_path);
tex_width = default_crosshair_width;
tex_height = default_crosshair_height;
image_size = tex_width * tex_height * 4;
@@ -865,28 +1917,159 @@ static void ensure_swapchain_crosshair(swapchain_data_t* data,
&data->crosshair_upload_buffer_mem, data->crosshair_image);
}
- float scale = 1;
- const char* scale_str = getenv("KROSSHAIR_SCALE");
- if (scale_str) {
- char* end = NULL;
- for (float scale_cvt = strtof(scale_str, &end); scale_str != end;
- scale_cvt = strtof(scale_str, &end)) {
- printf("got scale: %f\n", scale_cvt);
- scale = scale_cvt;
- scale_str = end;
- if (errno == ERANGE) {
- printf("KROSSHAIR_SCALE invalid.\n");
- scale = 1;
+ data->crosshair_tex_width = tex_width;
+
+ if (data->anim_frame_count > 1) {
+ /* for animated crosshairs, use frame dimensions for quad size,
+ * UV selects first frame from atlas */
+ float uv_step = 1.0f / (float)data->anim_frame_count;
+ setup_vertices_uv(data->vertices,
+ (float)data->width, (float)data->height,
+ (float)data->anim_frame_height,
+ (float)tex_width, 1.0f,
+ 0.0f, uv_step);
+ } else {
+ setup_vertices(data->vertices,
+ (float)data->width, (float)data->height,
+ (float)tex_height, (float)tex_width, 1.0f);
+ }
+
+ data->crosshair_uploaded = 1;
+}
+
+/*
+ * Load the single dynamic mask + parse the config file.
+ * Both are optional — if no mask file exists, nothing happens.
+ */
+static void ensure_swapchain_dynamic_mask(swapchain_data_t* data,
+ VkCommandBuffer cmd_buffer)
+{
+ device_data_t* device_data = data->device_data;
+
+ char* mpath = get_dynamic_mask_path();
+ int using_file = (mpath != NULL);
+
+ /* ── check for mask image reload ── */
+ if (data->dynamic_mask.uploaded) {
+ int needs_reload = 0;
+
+ if (using_file && data->dynamic_mask.path) {
+ if (strcmp(data->dynamic_mask.path, mpath) != 0) {
+ needs_reload = 1;
+ } else {
+ struct timespec new_mtime;
+ if (get_file_mtime(mpath, &new_mtime) == 0) {
+ if (new_mtime.tv_sec != data->dynamic_mask.mtime.tv_sec ||
+ new_mtime.tv_nsec != data->dynamic_mask.mtime.tv_nsec) {
+ needs_reload = 1;
+ }
+ }
}
+ } else if (!using_file && data->dynamic_mask.path) {
+ shutdown_dynamic_mask(data);
+ data->dynamic_mask.uploaded = 0;
+ free(data->dynamic_mask.path);
+ data->dynamic_mask.path = NULL;
+ free(mpath);
+ return;
+ }
+
+ if (!needs_reload) {
+ free(mpath);
+ /* still check cfg for hot-reload */
+ goto check_cfg;
+ }
+
+ shutdown_dynamic_mask(data);
+ data->dynamic_mask.uploaded = 0;
+ free(data->dynamic_mask.path);
+ data->dynamic_mask.path = NULL;
+ /* invalidate shader desc set so it gets re-created */
+ if (data->shader_desc_pool) {
+ device_data->vtable.ResetDescriptorPool(
+ device_data->device, data->shader_desc_pool, 0);
+ data->shader_mask_desc_set = VK_NULL_HANDLE;
}
}
- printf("scale: %f\n", scale);
+ if (!using_file) {
+ free(mpath);
+ return;
+ }
- setup_vertices((float)data->width, (float)data->height,
- (float)tex_height, (float)tex_width, scale);
+ int tex_width, tex_height, tex_channels;
+ stbi_uc* pixels = stbi_load(mpath, &tex_width, &tex_height,
+ &tex_channels, STBI_rgb_alpha);
+ if (!pixels) {
+ KROSSHAIR_LOG("[KROSSHAIR_ERROR] failed to load dynamic mask: %s\n",
+ mpath);
+ free(mpath);
+ return;
+ }
- data->crosshair_uploaded = 1;
+ VkDeviceSize image_size = (VkDeviceSize)tex_width * tex_height * 4;
+
+ data->dynamic_mask.descriptor_set = create_image_with_desc(
+ data, tex_width, tex_height, VK_FORMAT_R8G8B8A8_SRGB,
+ &data->dynamic_mask.image, &data->dynamic_mask.mem,
+ &data->dynamic_mask.image_view);
+
+ upload_image_data(
+ device_data, cmd_buffer, pixels, image_size, tex_width,
+ tex_height, &data->dynamic_mask.upload_buffer,
+ &data->dynamic_mask.upload_buffer_mem, data->dynamic_mask.image);
+ stbi_image_free(pixels);
+
+ data->dynamic_mask.path = mpath;
+ get_file_mtime(mpath, &data->dynamic_mask.mtime);
+ data->dynamic_mask.tex_width = tex_width;
+
+ setup_vertices(data->dynamic_mask.vertices,
+ (float)data->width, (float)data->height,
+ (float)tex_height, (float)tex_width, 1.0f);
+
+ data->dynamic_mask.uploaded = 1;
+ KROSSHAIR_LOG("[KROSSHAIR] loaded dynamic mask from: %s\n", mpath);
+
+ /* invalidate shader desc set so it gets re-created with new image view */
+ if (data->shader_desc_pool) {
+ device_data->vtable.ResetDescriptorPool(
+ device_data->device, data->shader_desc_pool, 0);
+ data->shader_mask_desc_set = VK_NULL_HANDLE;
+ }
+
+check_cfg:
+ /* ── check for config file reload ── */
+ {
+ char* cfg_path = get_dynamic_cfg_path();
+ int cfg_exists = (cfg_path != NULL);
+
+ if (cfg_exists && data->dynamic_cfg_path) {
+ struct timespec new_mtime;
+ if (get_file_mtime(cfg_path, &new_mtime) == 0) {
+ if (new_mtime.tv_sec != data->dynamic_cfg_mtime.tv_sec ||
+ new_mtime.tv_nsec != data->dynamic_cfg_mtime.tv_nsec) {
+ parse_dynamic_cfg(cfg_path, &data->dynamic_pc);
+ data->dynamic_cfg_mtime = new_mtime;
+ KROSSHAIR_LOG("[KROSSHAIR] reloaded dynamic cfg: %s\n", cfg_path);
+ }
+ }
+ } else if (cfg_exists) {
+ /* first time loading cfg */
+ parse_dynamic_cfg(cfg_path, &data->dynamic_pc);
+ get_file_mtime(cfg_path, &data->dynamic_cfg_mtime);
+ data->dynamic_cfg_path = cfg_path;
+ cfg_path = NULL; /* don't free, ownership transferred */
+ KROSSHAIR_LOG("[KROSSHAIR] loaded dynamic cfg: %s\n", data->dynamic_cfg_path);
+ } else if (!cfg_exists && data->dynamic_cfg_path) {
+ /* cfg file was removed */
+ parse_dynamic_cfg(NULL, &data->dynamic_pc);
+ free(data->dynamic_cfg_path);
+ data->dynamic_cfg_path = NULL;
+ }
+
+ free(cfg_path);
+ }
}
/* allocated a krosshair_draw_t instance that must be free'd at the end of its
@@ -898,12 +2081,24 @@ static void create_draw(swapchain_data_t* data)
krosshair_draw_t* draw = data->draw;
if (draw) {
- if (device_data->vtable.GetFenceStatus(
- device_data->device, draw->fence) == VK_SUCCESS) {
+ VkResult fence_status = device_data->vtable.GetFenceStatus(
+ device_data->device, draw->fence);
+ if (fence_status == VK_SUCCESS) {
VK_CHECK(device_data->vtable.ResetFences(
device_data->device, 1, &draw->fence));
return;
}
+ if (fence_status == VK_NOT_READY) {
+ VkResult wait_result = device_data->vtable.WaitForFences(
+ device_data->device, 1, &draw->fence, VK_TRUE, 100000000);
+ if (wait_result == VK_SUCCESS) {
+ VK_CHECK(device_data->vtable.ResetFences(
+ device_data->device, 1, &draw->fence));
+ return;
+ }
+ }
+ destroy_draw(data, draw);
+ data->draw = NULL;
}
VkSemaphoreCreateInfo sem_info = {};
@@ -936,6 +2131,52 @@ static void create_draw(swapchain_data_t* data)
data->draw = draw;
}
+static void destroy_draw(swapchain_data_t* data, krosshair_draw_t* draw)
+{
+ if (!draw) return;
+
+ device_data_t* device_data = data->device_data;
+
+ if (draw->vertex_buffer != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyBuffer(device_data->device,
+ draw->vertex_buffer, NULL);
+ }
+ if (draw->vertex_buffer_mem != VK_NULL_HANDLE) {
+ device_data->vtable.FreeMemory(device_data->device,
+ draw->vertex_buffer_mem, NULL);
+ }
+ if (draw->vertex_buffer2 != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyBuffer(device_data->device,
+ draw->vertex_buffer2, NULL);
+ }
+ if (draw->vertex_buffer2_mem != VK_NULL_HANDLE) {
+ device_data->vtable.FreeMemory(device_data->device,
+ draw->vertex_buffer2_mem, NULL);
+ }
+ if (draw->index_buffer != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyBuffer(device_data->device,
+ draw->index_buffer, NULL);
+ }
+ if (draw->index_buffer_mem != VK_NULL_HANDLE) {
+ device_data->vtable.FreeMemory(device_data->device,
+ draw->index_buffer_mem, NULL);
+ }
+ if (draw->semaphore != VK_NULL_HANDLE) {
+ device_data->vtable.DestroySemaphore(device_data->device,
+ draw->semaphore, NULL);
+ }
+ if (draw->crossengine_semaphore != VK_NULL_HANDLE) {
+ device_data->vtable.DestroySemaphore(device_data->device,
+ draw->crossengine_semaphore, NULL);
+ }
+ if (draw->fence != VK_NULL_HANDLE) {
+ device_data->vtable.DestroyFence(device_data->device,
+ draw->fence, NULL);
+ }
+
+ free(draw);
+}
+
static krosshair_draw_t* render_swapchain_display(
swapchain_data_t* data, queue_data_t* present_queue,
const VkSemaphore* wait_semaphores, unsigned n_wait_semaphores,
@@ -945,6 +2186,10 @@ static krosshair_draw_t* render_swapchain_display(
create_draw(data);
krosshair_draw_t* draw = data->draw;
+ if (!draw) {
+ KROSSHAIR_LOG("[KROSSHAIR] create_draw failed, no draw available\n");
+ return NULL;
+ }
device_data->vtable.ResetCommandBuffer(draw->cmd_buffer, 0);
VkRenderPassBeginInfo render_pass_info = {};
@@ -960,14 +2205,14 @@ static krosshair_draw_t* render_swapchain_display(
device_data->vtable.BeginCommandBuffer(draw->cmd_buffer,
&buffer_begin_info);
ensure_swapchain_crosshair(data, draw->cmd_buffer);
+ ensure_swapchain_dynamic_mask(data, draw->cmd_buffer);
+
+ /* FB copy needed whenever the dynamic mask is active */
+ int needs_fb_copy = data->dynamic_mask.uploaded;
VkImageMemoryBarrier imb = {};
imb.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
imb.pNext = NULL;
- imb.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
- imb.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
- imb.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
- imb.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
imb.image = data->images[image_index];
imb.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imb.subresourceRange.baseMipLevel = 0;
@@ -976,64 +2221,265 @@ static krosshair_draw_t* render_swapchain_display(
imb.subresourceRange.layerCount = 1;
imb.srcQueueFamilyIndex = present_queue->family_index;
imb.dstQueueFamilyIndex = device_data->graphic_queue->family_index;
- device_data->vtable.CmdPipelineBarrier(
- draw->cmd_buffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
- VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, NULL, 0, NULL, 1, &imb);
+
+ if (needs_fb_copy) {
+ /* ── copy game framebuffer for shader-based modes ──
+ * 1) PRESENT_SRC → TRANSFER_SRC (so we can read)
+ * 2) blit full swapchain to game_fb_image
+ * 3) TRANSFER_SRC → COLOR_ATTACHMENT (so render pass works)
+ */
+
+ /* ensure game_fb texture exists at swapchain resolution */
+ if (!data->game_fb_image ||
+ data->game_fb_width != data->width ||
+ data->game_fb_height != data->height) {
+ if (data->game_fb_image_view) {
+ device_data->vtable.DestroyImageView(
+ device_data->device, data->game_fb_image_view, NULL);
+ data->game_fb_image_view = VK_NULL_HANDLE;
+ }
+ if (data->game_fb_image) {
+ device_data->vtable.DestroyImage(
+ device_data->device, data->game_fb_image, NULL);
+ data->game_fb_image = VK_NULL_HANDLE;
+ }
+ if (data->game_fb_mem) {
+ device_data->vtable.FreeMemory(
+ device_data->device, data->game_fb_mem, NULL);
+ data->game_fb_mem = VK_NULL_HANDLE;
+ }
+
+ VkImageCreateInfo fbi = {};
+ fbi.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ fbi.imageType = VK_IMAGE_TYPE_2D;
+ fbi.format = data->format;
+ fbi.extent.width = data->width;
+ fbi.extent.height = data->height;
+ fbi.extent.depth = 1;
+ fbi.mipLevels = 1;
+ fbi.arrayLayers = 1;
+ fbi.samples = VK_SAMPLE_COUNT_1_BIT;
+ fbi.tiling = VK_IMAGE_TILING_OPTIMAL;
+ fbi.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+ fbi.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ fbi.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ VK_CHECK(device_data->vtable.CreateImage(
+ device_data->device, &fbi, NULL, &data->game_fb_image));
+
+ VkMemoryRequirements mreq;
+ device_data->vtable.GetImageMemoryRequirements(
+ device_data->device, data->game_fb_image, &mreq);
+
+ VkMemoryAllocateInfo mai = {};
+ mai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ mai.allocationSize = mreq.size;
+ mai.memoryTypeIndex =
+ vk_memory_type(device_data,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+ mreq.memoryTypeBits);
+ VK_CHECK(device_data->vtable.AllocateMemory(
+ device_data->device, &mai, NULL, &data->game_fb_mem));
+ VK_CHECK(device_data->vtable.BindImageMemory(
+ device_data->device, data->game_fb_image,
+ data->game_fb_mem, 0));
+
+ VkImageViewCreateInfo fvi = {};
+ fvi.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ fvi.image = data->game_fb_image;
+ fvi.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ fvi.format = data->format;
+ fvi.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ fvi.subresourceRange.levelCount = 1;
+ fvi.subresourceRange.layerCount = 1;
+ VK_CHECK(device_data->vtable.CreateImageView(
+ device_data->device, &fvi, NULL,
+ &data->game_fb_image_view));
+
+ data->game_fb_width = data->width;
+ data->game_fb_height = data->height;
+
+ KROSSHAIR_LOG("[KROSSHAIR] created game FB copy texture %ux%u\n",
+ data->width, data->height);
+ }
+
+ /* transition swapchain: PRESENT_SRC → TRANSFER_SRC */
+ imb.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ imb.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+ imb.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ imb.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ device_data->vtable.CmdPipelineBarrier(
+ draw->cmd_buffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &imb);
+
+ /* transition game_fb: UNDEFINED → TRANSFER_DST */
+ VkImageMemoryBarrier fb_bar = {};
+ fb_bar.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ fb_bar.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ fb_bar.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ fb_bar.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+ fb_bar.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ fb_bar.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ fb_bar.image = data->game_fb_image;
+ fb_bar.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ fb_bar.subresourceRange.levelCount = 1;
+ fb_bar.subresourceRange.layerCount = 1;
+ device_data->vtable.CmdPipelineBarrier(
+ draw->cmd_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &fb_bar);
+
+ /* copy swapchain → game_fb */
+ VkImageCopy region = {};
+ region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.srcSubresource.layerCount = 1;
+ region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.dstSubresource.layerCount = 1;
+ region.extent.width = data->width;
+ region.extent.height = data->height;
+ region.extent.depth = 1;
+ device_data->vtable.CmdCopyImage(
+ draw->cmd_buffer,
+ data->images[image_index], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ data->game_fb_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1, ®ion);
+
+ /* transition game_fb: TRANSFER_DST → SHADER_READ_ONLY */
+ fb_bar.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ fb_bar.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+ fb_bar.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+ fb_bar.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ device_data->vtable.CmdPipelineBarrier(
+ draw->cmd_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ 0, 0, NULL, 0, NULL, 1, &fb_bar);
+
+ /* transition swapchain: TRANSFER_SRC → COLOR_ATTACHMENT */
+ imb.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+ imb.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ imb.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ imb.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ device_data->vtable.CmdPipelineBarrier(
+ draw->cmd_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0, 0, NULL, 0, NULL, 1, &imb);
+ } else {
+ /* no FB copy needed — simple transition */
+ imb.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ imb.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ imb.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ imb.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ device_data->vtable.CmdPipelineBarrier(
+ draw->cmd_buffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
+ VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
+ 0, 0, NULL, 0, NULL, 1, &imb);
+ }
device_data->vtable.CmdBeginRenderPass(
draw->cmd_buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE);
- // create vertex & index buffers
- // since our contents aren't changing at runtime, might as well do
- // creation and upload only once somewhere else? upload vertex & index
- // data bind vertex & index buffers
+ /* advance animation frame if needed (GIF or APNG) */
+ if (data->anim_frame_count > 1 && data->anim_delays) {
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ long elapsed_ms =
+ (now.tv_sec - data->anim_last_frame_time.tv_sec) * 1000 +
+ (now.tv_nsec - data->anim_last_frame_time.tv_nsec) / 1000000;
+
+ int advanced = 0;
+ int delay = data->anim_delays[data->anim_current_frame];
+
+ /* consume all elapsed time, advancing multiple frames if
+ * the present rate is lower than the animation rate */
+ while (elapsed_ms >= delay) {
+ /* accumulate: add delay to last_frame_time instead
+ * of resetting to now, so leftover time carries
+ * over and the animation stays in sync */
+ data->anim_last_frame_time.tv_nsec += (long)delay * 1000000L;
+ while (data->anim_last_frame_time.tv_nsec >= 1000000000L) {
+ data->anim_last_frame_time.tv_sec++;
+ data->anim_last_frame_time.tv_nsec -= 1000000000L;
+ }
+
+ data->anim_current_frame =
+ (data->anim_current_frame + 1) % data->anim_frame_count;
+ delay = data->anim_delays[data->anim_current_frame];
+ advanced = 1;
+
+ /* recalculate elapsed from updated base */
+ elapsed_ms =
+ (now.tv_sec - data->anim_last_frame_time.tv_sec) * 1000 +
+ (now.tv_nsec - data->anim_last_frame_time.tv_nsec) / 1000000;
+ }
+
+ if (advanced) {
+ float uv_step = 1.0f / (float)data->anim_frame_count;
+ float uv_top = uv_step * (float)data->anim_current_frame;
+ float uv_bot = uv_top + uv_step;
+ setup_vertices_uv(
+ data->vertices,
+ (float)data->width, (float)data->height,
+ (float)data->anim_frame_height,
+ (float)data->crosshair_tex_width, 1.0f,
+ uv_top, uv_bot);
+
+ /* force vertex buffer re-upload with new UVs */
+ draw->vertex_buffer_initialized = 0;
+ }
+ }
- size_t vertex_size = sizeof(vertices);
+ size_t vertex_size = sizeof(data->vertices);
size_t index_size = sizeof(indices);
if (draw->vertex_buffer_size < vertex_size) {
create_or_resize_buffer(device_data, &draw->vertex_buffer,
&draw->vertex_buffer_mem,
&draw->vertex_buffer_size, vertex_size,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
+ draw->vertex_buffer_initialized = 0;
}
if (draw->index_buffer_size < index_size) {
create_or_resize_buffer(device_data, &draw->index_buffer,
&draw->index_buffer_mem,
&draw->index_buffer_size, index_size,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
+ draw->index_buffer_initialized = 0;
}
- // upload vertex buffer
- void* vtx_dst = NULL;
- VK_CHECK(device_data->vtable.MapMemory(
- device_data->device, draw->vertex_buffer_mem, 0,
- draw->vertex_buffer_size, 0, &vtx_dst));
- memcpy(vtx_dst, vertices, (size_t)draw->vertex_buffer_size);
+ if (!draw->vertex_buffer_initialized) {
+ void* vtx_dst = NULL;
+ VK_CHECK(device_data->vtable.MapMemory(
+ device_data->device, draw->vertex_buffer_mem, 0,
+ draw->vertex_buffer_size, 0, &vtx_dst));
+ memcpy(vtx_dst, data->vertices, sizeof(data->vertices));
+
+ VkMappedMemoryRange vtx_range = {};
+ vtx_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
+ vtx_range.memory = draw->vertex_buffer_mem;
+ vtx_range.size = VK_WHOLE_SIZE;
+ VK_CHECK(device_data->vtable.FlushMappedMemoryRanges(
+ device_data->device, 1, &vtx_range));
+ device_data->vtable.UnmapMemory(device_data->device,
+ draw->vertex_buffer_mem);
+ draw->vertex_buffer_initialized = 1;
+ }
- VkMappedMemoryRange vtx_range = {};
- vtx_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
- vtx_range.memory = draw->vertex_buffer_mem;
- vtx_range.size = VK_WHOLE_SIZE;
- VK_CHECK(device_data->vtable.FlushMappedMemoryRanges(
- device_data->device, 1, &vtx_range));
- device_data->vtable.UnmapMemory(device_data->device,
- draw->vertex_buffer_mem);
-
- // upload index buffer
- void* idx_dst = NULL;
- VK_CHECK(device_data->vtable.MapMemory(
- device_data->device, draw->index_buffer_mem, 0,
- draw->index_buffer_size, 0, &idx_dst));
- memcpy(idx_dst, indices, (size_t)draw->index_buffer_size);
-
- VkMappedMemoryRange idx_range = {};
- idx_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
- idx_range.memory = draw->index_buffer_mem;
- idx_range.size = VK_WHOLE_SIZE;
- VK_CHECK(device_data->vtable.FlushMappedMemoryRanges(
- device_data->device, 1, &idx_range));
- device_data->vtable.UnmapMemory(device_data->device,
- draw->index_buffer_mem);
+ if (!draw->index_buffer_initialized) {
+ void* idx_dst = NULL;
+ VK_CHECK(device_data->vtable.MapMemory(
+ device_data->device, draw->index_buffer_mem, 0,
+ draw->index_buffer_size, 0, &idx_dst));
+ memcpy(idx_dst, indices, sizeof(indices));
+
+ VkMappedMemoryRange idx_range = {};
+ idx_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
+ idx_range.memory = draw->index_buffer_mem;
+ idx_range.size = VK_WHOLE_SIZE;
+ VK_CHECK(device_data->vtable.FlushMappedMemoryRanges(
+ device_data->device, 1, &idx_range));
+ device_data->vtable.UnmapMemory(device_data->device,
+ draw->index_buffer_mem);
+ draw->index_buffer_initialized = 1;
+ }
device_data->vtable.CmdBindPipeline(
draw->cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, data->pipeline);
@@ -1063,8 +2509,110 @@ static krosshair_draw_t* render_swapchain_display(
device_data->vtable.CmdBindDescriptorSets(
draw->cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
data->pipeline_layout, 0, 1, &data->descriptor_set, 0, NULL);
- device_data->vtable.CmdDrawIndexed(draw->cmd_buffer, sizeof(indices), 1,
- 0, 0, 0);
+ device_data->vtable.CmdDrawIndexed(draw->cmd_buffer,
+ sizeof(indices) / sizeof(indices[0]),
+ 1, 0, 0, 0);
+
+ /* ── single dynamic effect mask draw (optional) ──
+ * Uses the shader pipeline that samples the game FB copy.
+ * Only runs if the dynamic mask is uploaded and the shader pipeline exists.
+ */
+if (data->dynamic_mask.uploaded && data->shader_pipeline && data->game_fb_image_view) {
+ /* ensure vertex_buffer2 exists for dynamic mask */
+ size_t vtx2_size = sizeof(data->dynamic_mask.vertices);
+ if (draw->vertex_buffer2_size < vtx2_size) {
+ create_or_resize_buffer(device_data, &draw->vertex_buffer2,
+ &draw->vertex_buffer2_mem,
+ &draw->vertex_buffer2_size, vtx2_size,
+ VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
+ }
+
+ /* upload dynamic mask vertices to separate buffer */
+ void* vtx_dst = NULL;
+ VK_CHECK(device_data->vtable.MapMemory(
+ device_data->device, draw->vertex_buffer2_mem, 0,
+ draw->vertex_buffer2_size, 0, &vtx_dst));
+ memcpy(vtx_dst, data->dynamic_mask.vertices,
+ sizeof(data->dynamic_mask.vertices));
+
+ VkMappedMemoryRange vtx_range = {};
+ vtx_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
+ vtx_range.memory = draw->vertex_buffer2_mem;
+ vtx_range.size = VK_WHOLE_SIZE;
+ VK_CHECK(device_data->vtable.FlushMappedMemoryRanges(
+ device_data->device, 1, &vtx_range));
+ device_data->vtable.UnmapMemory(device_data->device,
+ draw->vertex_buffer2_mem);
+
+ device_data->vtable.CmdBindPipeline(
+ draw->cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+ data->shader_pipeline);
+
+ VkDeviceSize mask_offsets[1] = {0};
+ device_data->vtable.CmdBindVertexBuffers(
+ draw->cmd_buffer, 0, 1, &draw->vertex_buffer2, mask_offsets);
+
+ /* allocate/update descriptor set for mask + game_fb if needed */
+ if (!data->shader_mask_desc_set) {
+ VkDescriptorSetAllocateInfo dsai = {};
+ dsai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+ dsai.descriptorPool = data->shader_desc_pool;
+ dsai.descriptorSetCount = 1;
+ dsai.pSetLayouts = &data->shader_desc_layout;
+ VK_CHECK(device_data->vtable.AllocateDescriptorSets(
+ device_data->device, &dsai,
+ &data->shader_mask_desc_set));
+
+ VkDescriptorImageInfo di_mask = {};
+ di_mask.sampler = data->crosshair_sampler;
+ di_mask.imageView = data->dynamic_mask.image_view;
+ di_mask.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+ VkDescriptorImageInfo di_fb = {};
+ di_fb.sampler = data->crosshair_sampler;
+ di_fb.imageView = data->game_fb_image_view;
+ di_fb.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+ VkWriteDescriptorSet writes[2] = {};
+ writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ writes[0].dstSet = data->shader_mask_desc_set;
+ writes[0].dstBinding = 0;
+ writes[0].descriptorCount = 1;
+ writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ writes[0].pImageInfo = &di_mask;
+ writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ writes[1].dstSet = data->shader_mask_desc_set;
+ writes[1].dstBinding = 1;
+ writes[1].descriptorCount = 1;
+ writes[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ writes[1].pImageInfo = &di_fb;
+ device_data->vtable.UpdateDescriptorSets(
+ device_data->device, 2, writes, 0, NULL);
+ }
+
+ device_data->vtable.CmdBindDescriptorSets(
+ draw->cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+ data->shader_pipeline_layout, 0, 1,
+ &data->shader_mask_desc_set, 0, NULL);
+
+ /* fill quad NDC bounds into push constants */
+ data->dynamic_pc.quad_ndc_min[0] = data->dynamic_mask.vertices[0].pos.x;
+ data->dynamic_pc.quad_ndc_min[1] = data->dynamic_mask.vertices[0].pos.y;
+ data->dynamic_pc.quad_ndc_size[0] = data->dynamic_mask.vertices[2].pos.x -
+ data->dynamic_mask.vertices[0].pos.x;
+ data->dynamic_pc.quad_ndc_size[1] = data->dynamic_mask.vertices[2].pos.y -
+ data->dynamic_mask.vertices[0].pos.y;
+
+ device_data->vtable.CmdPushConstants(
+ draw->cmd_buffer, data->shader_pipeline_layout,
+ VK_SHADER_STAGE_FRAGMENT_BIT, 0,
+ sizeof(data->dynamic_pc), &data->dynamic_pc);
+
+ device_data->vtable.CmdDrawIndexed(
+ draw->cmd_buffer,
+ sizeof(indices) / sizeof(indices[0]),
+ 1, 0, 0, 0);
+ }
device_data->vtable.CmdEndRenderPass(draw->cmd_buffer);
@@ -1095,13 +2643,18 @@ static krosshair_draw_t* render_swapchain_display(
&imb);
}
- device_data->vtable.EndCommandBuffer(draw->cmd_buffer);
+ VkResult end_result = device_data->vtable.EndCommandBuffer(draw->cmd_buffer);
+ if (end_result != VK_SUCCESS) {
+ KROSSHAIR_LOG("[KROSSHAIR] EndCommandBuffer failed: %d\n", end_result);
+ return NULL;
+ }
/* when presenting on a different queue than where we're drawing the
* crosshair *AND* when the application does not provide a semaphore to
* vkQueuePresent, insert our own cross-engine synchronization
* semaphore.
* */
+ VkResult submit_result = VK_SUCCESS;
if (n_wait_semaphores == 0 &&
device_data->graphic_queue->queue != present_queue->queue) {
VkPipelineStageFlags stages_wait =
@@ -1114,8 +2667,13 @@ static krosshair_draw_t* render_swapchain_display(
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &draw->crossengine_semaphore;
- device_data->vtable.QueueSubmit(present_queue->queue, 1,
- &submit_info, VK_NULL_HANDLE);
+ submit_result = device_data->vtable.QueueSubmit(
+ present_queue->queue, 1, &submit_info, VK_NULL_HANDLE);
+ if (submit_result != VK_SUCCESS) {
+ KROSSHAIR_LOG("[KROSSHAIR] crossengine QueueSubmit failed: %d\n",
+ submit_result);
+ return NULL;
+ }
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
@@ -1126,16 +2684,21 @@ static krosshair_draw_t* render_swapchain_display(
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &draw->semaphore;
- device_data->vtable.QueueSubmit(
+ submit_result = device_data->vtable.QueueSubmit(
device_data->graphic_queue->queue, 1, &submit_info,
draw->fence);
+ if (submit_result != VK_SUCCESS) {
+ KROSSHAIR_LOG("[KROSSHAIR] graphic QueueSubmit failed: %d\n",
+ submit_result);
+ return NULL;
+ }
} else {
/* wait in the fragment stage until the swapchain image is ready
*/
VkPipelineStageFlags* stages_wait =
- malloc(sizeof(*stages_wait) * n_wait_semaphores);
- memset(stages_wait, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
- sizeof(*stages_wait) * n_wait_semaphores);
+ malloc(sizeof(*stages_wait) * (n_wait_semaphores ? n_wait_semaphores : 1));
+ for (unsigned s = 0; s < n_wait_semaphores; s++)
+ stages_wait[s] = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
@@ -1147,10 +2710,15 @@ static krosshair_draw_t* render_swapchain_display(
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &draw->semaphore;
- device_data->vtable.QueueSubmit(
+ submit_result = device_data->vtable.QueueSubmit(
device_data->graphic_queue->queue, 1, &submit_info,
draw->fence);
free(stages_wait);
+ if (submit_result != VK_SUCCESS) {
+ KROSSHAIR_LOG("[KROSSHAIR] QueueSubmit failed: %d (wait_sems=%u)\n",
+ submit_result, n_wait_semaphores);
+ return NULL;
+ }
}
return draw;
@@ -1193,11 +2761,11 @@ static void setup_swapchain_data_pipeline(swapchain_data_t* data)
VkDescriptorPoolSize sampler_pool_size = {};
sampler_pool_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
- sampler_pool_size.descriptorCount = 1;
+ sampler_pool_size.descriptorCount = 1 + 1; /* crosshair + dynamic mask */
VkDescriptorPoolCreateInfo desc_pool_info = {};
desc_pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
- desc_pool_info.maxSets = 1;
+ desc_pool_info.maxSets = 1 + 1;
desc_pool_info.poolSizeCount = 1;
desc_pool_info.pPoolSizes = &sampler_pool_size;
VK_CHECK(device_data->vtable.CreateDescriptorPool(
@@ -1293,7 +2861,7 @@ static void setup_swapchain_data_pipeline(swapchain_data_t* data)
ms_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineColorBlendAttachmentState color_attachment = {};
- color_attachment.blendEnable = VK_FALSE;
+ color_attachment.blendEnable = VK_TRUE;
color_attachment.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
@@ -1346,6 +2914,135 @@ static void setup_swapchain_data_pipeline(swapchain_data_t* data)
vert_module, NULL);
device_data->vtable.DestroyShaderModule(device_data->device,
frag_module, NULL);
+
+ /* ═══════════════════════════════════════════════════════════
+ * Shader-based dynamic pipeline (Complement, LumaInvert, etc.)
+ * Uses a custom fragment shader that reads the game framebuffer.
+ * ═══════════════════════════════════════════════════════════ */
+ {
+ VkShaderModule dyn_vert_mod, dyn_frag_mod;
+
+ VkShaderModuleCreateInfo dvi = {};
+ dvi.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+ dvi.codeSize = sizeof(dynamic_vert_spv);
+ dvi.pCode = (const uint32_t*)dynamic_vert_spv;
+ VK_CHECK(device_data->vtable.CreateShaderModule(
+ device_data->device, &dvi, NULL, &dyn_vert_mod));
+
+ VkShaderModuleCreateInfo dfi = {};
+ dfi.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+ dfi.codeSize = sizeof(dynamic_frag_spv);
+ dfi.pCode = (const uint32_t*)dynamic_frag_spv;
+ VK_CHECK(device_data->vtable.CreateShaderModule(
+ device_data->device, &dfi, NULL, &dyn_frag_mod));
+
+ /* descriptor set layout: binding 0 = mask, binding 1 = game FB */
+ VkDescriptorSetLayoutBinding shader_bindings[2] = {};
+ shader_bindings[0].binding = 0;
+ shader_bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ shader_bindings[0].descriptorCount = 1;
+ shader_bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+ shader_bindings[0].pImmutableSamplers = &sampler;
+ shader_bindings[1].binding = 1;
+ shader_bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ shader_bindings[1].descriptorCount = 1;
+ shader_bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+ shader_bindings[1].pImmutableSamplers = &sampler;
+
+ VkDescriptorSetLayoutCreateInfo sdl_info = {};
+ sdl_info.sType =
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+ sdl_info.bindingCount = 2;
+ sdl_info.pBindings = shader_bindings;
+ VK_CHECK(device_data->vtable.CreateDescriptorSetLayout(
+ device_data->device, &sdl_info, NULL,
+ &data->shader_desc_layout));
+
+ /* push constant range: full dynamic_push_constants (80 bytes) */
+ VkPushConstantRange pc_range = {};
+ pc_range.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+ pc_range.offset = 0;
+ pc_range.size = sizeof(struct dynamic_push_constants);
+
+ VkPipelineLayoutCreateInfo spl_info = {};
+ spl_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+ spl_info.setLayoutCount = 1;
+ spl_info.pSetLayouts = &data->shader_desc_layout;
+ spl_info.pushConstantRangeCount = 1;
+ spl_info.pPushConstantRanges = &pc_range;
+ VK_CHECK(device_data->vtable.CreatePipelineLayout(
+ device_data->device, &spl_info, NULL,
+ &data->shader_pipeline_layout));
+
+ /* descriptor pool for shader dynamic desc set */
+ VkDescriptorPoolSize sp_size = {};
+ sp_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ sp_size.descriptorCount = 1 * 2; /* 2 bindings: mask + game_fb */
+
+ VkDescriptorPoolCreateInfo sp_info = {};
+ sp_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+ sp_info.maxSets = 1;
+ sp_info.poolSizeCount = 1;
+ sp_info.pPoolSizes = &sp_size;
+ VK_CHECK(device_data->vtable.CreateDescriptorPool(
+ device_data->device, &sp_info, NULL,
+ &data->shader_desc_pool));
+
+ /* shader stages */
+ VkPipelineShaderStageCreateInfo sstage[2] = {};
+ sstage[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ sstage[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+ sstage[0].module = dyn_vert_mod;
+ sstage[0].pName = "main";
+ sstage[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ sstage[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ sstage[1].module = dyn_frag_mod;
+ sstage[1].pName = "main";
+
+ /* the shader outputs final composited color — standard alpha blend */
+ VkPipelineColorBlendAttachmentState shader_ca = {};
+ shader_ca.blendEnable = VK_TRUE;
+ shader_ca.colorWriteMask =
+ VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+ shader_ca.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+ shader_ca.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
+ shader_ca.colorBlendOp = VK_BLEND_OP_ADD;
+ shader_ca.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+ shader_ca.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
+ shader_ca.alphaBlendOp = VK_BLEND_OP_ADD;
+
+ VkPipelineColorBlendStateCreateInfo shader_blend = {};
+ shader_blend.sType =
+ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+ shader_blend.attachmentCount = 1;
+ shader_blend.pAttachments = &shader_ca;
+
+ /* reuse vertex_info, input_asm_info, viewport_info, raster_info,
+ * ms_info, depth_info, dynamic_state from earlier */
+ VkGraphicsPipelineCreateInfo spi = {};
+ spi.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+ spi.stageCount = 2;
+ spi.pStages = sstage;
+ spi.pVertexInputState = &vertex_info;
+ spi.pInputAssemblyState = &input_asm_info;
+ spi.pViewportState = &viewport_info;
+ spi.pRasterizationState = &raster_info;
+ spi.pMultisampleState = &ms_info;
+ spi.pDepthStencilState = &depth_info;
+ spi.pColorBlendState = &shader_blend;
+ spi.pDynamicState = &dynamic_state;
+ spi.layout = data->shader_pipeline_layout;
+ spi.renderPass = data->render_pass;
+ VK_CHECK(device_data->vtable.CreateGraphicsPipelines(
+ device_data->device, VK_NULL_HANDLE, 1, &spi, NULL,
+ &data->shader_pipeline));
+
+ device_data->vtable.DestroyShaderModule(device_data->device,
+ dyn_vert_mod, NULL);
+ device_data->vtable.DestroyShaderModule(device_data->device,
+ dyn_frag_mod, NULL);
+ }
}
static void setup_swapchain_data(swapchain_data_t* data,
@@ -1403,6 +3100,7 @@ static void setup_swapchain_data(swapchain_data_t* data,
VK_CHECK(device_data->vtable.GetSwapchainImagesKHR(
device_data->device, data->swapchain, &n_images, data->images));
+ data->n_images = n_images;
VkImageViewCreateInfo view_info = {};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
@@ -1461,13 +3159,17 @@ static void instance_data_map_physical_devices(instance_data_t* instance_data,
for (uint32_t i = 0; i < physical_device_count; i++) {
if (map) {
- printf("[*] mapping physical_devices[%d] obj: %lu %p\n",
- i, HKEY(physical_devices[i]), instance_data);
+ KROSSHAIR_LOG("[*] mapping physical_devices[%d] obj: %lu %p\n",
+ i, HKEY(physical_devices[i]), (void*)instance_data);
char* fmt_phys_device;
asprintf(&fmt_phys_device, "physical_devices[%d]", i);
map_object(HKEY(physical_devices[i]), instance_data,
fmt_phys_device);
} else {
+ /* free the name string we allocated with asprintf */
+ vk_object_t obj;
+ if (vk_map_get(&vk_obj_map, HKEY(physical_devices[i]), &obj))
+ free((char*)obj.name);
unmap_object(HKEY(physical_devices[i]));
}
}
@@ -1482,8 +3184,8 @@ static swapchain_data_t* new_swapchain_data(VkSwapchainKHR swapchain,
memset(swapchain_data, 0, sizeof(*swapchain_data));
swapchain_data->device_data = device_data;
swapchain_data->swapchain = swapchain;
- printf("[*] mapping data->swapchain obj: %lu %p\n",
- HKEY(swapchain_data->swapchain), swapchain_data);
+ KROSSHAIR_LOG("[*] mapping data->swapchain obj: %lu %p\n",
+ HKEY(swapchain_data->swapchain), (void*)swapchain_data);
map_object(HKEY(swapchain_data->swapchain), swapchain_data,
"swapchain_data->swapchain");
return swapchain_data;
@@ -1494,41 +3196,59 @@ static VkResult overlay_CreateSwapchainKHR(
const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain)
{
VkSwapchainCreateInfoKHR create_info = *pCreateInfo;
- create_info.imageUsage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ create_info.imageUsage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
device_data_t* device_data = FIND_OBJ(device_data_t, device);
- VkResult result = device_data->vtable.CreateSwapchainKHR(
+ /* save reference to old swapchain data - we must NOT destroy it before
+ * the create call because the driver needs oldSwapchain to be valid */
+ swapchain_data_t* old_swapchain_data = NULL;
+ if (pCreateInfo->oldSwapchain != VK_NULL_HANDLE) {
+ old_swapchain_data =
+ FIND_OBJ(swapchain_data_t, pCreateInfo->oldSwapchain);
+ }
+
+ VkResult result = device_data->vtable.CreateSwapchainKHR(
device, &create_info, pAllocator, pSwapchain);
if (result != VK_SUCCESS) return result;
+ /* now that the new swapchain is created, clean up the old one's
+ * internal resources (Vulkan retires oldSwapchain automatically) */
+ if (old_swapchain_data) {
+ destroy_swapchain_data(old_swapchain_data);
+ unmap_object(HKEY(old_swapchain_data->swapchain));
+ free(old_swapchain_data);
+ }
+
swapchain_data_t* swapchain_data =
new_swapchain_data(*pSwapchain, device_data);
setup_swapchain_data(swapchain_data, pCreateInfo);
+ KROSSHAIR_LOG("[KROSSHAIR] CreateSwapchainKHR: created %lu (%ux%u, n_images=%u, old=%lu)\n",
+ (unsigned long)*pSwapchain, pCreateInfo->imageExtent.width,
+ pCreateInfo->imageExtent.height, swapchain_data->n_images,
+ (unsigned long)pCreateInfo->oldSwapchain);
+
return result;
}
static void overlay_DestroySwapchainKHR(VkDevice device,
- VkSwapchainKHR* swapchain,
- VkAllocationCallbacks* pAllocator)
+ VkSwapchainKHR swapchain,
+ const VkAllocationCallbacks* pAllocator)
{
device_data_t* device_data = FIND_OBJ(device_data_t, device);
swapchain_data_t* data = FIND_OBJ(swapchain_data_t, swapchain);
- for (size_t i = 0; i < data->n_images; i++) {
- device_data->vtable.DestroyFramebuffer(
- device_data->device, data->framebuffers[i], NULL);
- device_data->vtable.DestroyImageView(
- device_data->device, data->image_views[i], NULL);
+ if (data) {
+ destroy_swapchain_data(data);
+ unmap_object(HKEY(data->swapchain));
+ free(data);
}
- free(data->draw);
- unmap_object(HKEY(data->swapchain));
device_data->vtable.DestroySwapchainKHR(device_data->device,
- data->swapchain, NULL);
- free(data);
+ swapchain, pAllocator);
}
static VkResult overlay_CreateInstance(const VkInstanceCreateInfo* pCreateInfo,
@@ -1572,8 +3292,8 @@ static queue_data_t* new_queue_data(VkQueue queue,
queue_data->queue = queue;
queue_data->flags = family_props->queueFlags;
queue_data->family_index = family_index;
- printf("[*] mapping data->queue obj: %lu %p\n", HKEY(queue_data->queue),
- queue_data);
+ KROSSHAIR_LOG("[*] mapping data->queue obj: %lu %p\n", HKEY(queue_data->queue),
+ (void*)queue_data);
map_object(HKEY(queue_data->queue), queue_data, "queue_data->queue");
if (queue_data->flags & VK_QUEUE_GRAPHICS_BIT) {
@@ -1754,6 +3474,11 @@ static VkResult overlay_QueuePresentKHR(VkQueue queue,
{
queue_data_t* queue_data = FIND_OBJ(queue_data_t, queue);
+ if (!queue_data) {
+ KROSSHAIR_LOG("[KROSSHAIR] QueuePresent: queue_data is NULL!\n");
+ return VK_SUCCESS;
+ }
+
VkResult result = VK_SUCCESS;
for (uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) {
VkSwapchainKHR swapchain = pPresentInfo->pSwapchains[i];
@@ -1767,9 +3492,14 @@ static VkResult overlay_QueuePresentKHR(VkQueue queue,
present_info.pSwapchains = &swapchain;
present_info.pImageIndices = &image_index;
- krosshair_draw_t* draw = before_present(
- swapchain_data, queue_data, pPresentInfo->pWaitSemaphores,
- i == 0 ? pPresentInfo->waitSemaphoreCount : 0, image_index);
+ krosshair_draw_t* draw = NULL;
+ if (swapchain_data) {
+ draw = before_present(
+ swapchain_data, queue_data,
+ pPresentInfo->pWaitSemaphores,
+ i == 0 ? pPresentInfo->waitSemaphoreCount : 0,
+ image_index);
+ }
if (draw) {
present_info.pWaitSemaphores = &draw->semaphore;
@@ -1779,6 +3509,7 @@ static VkResult overlay_QueuePresentKHR(VkQueue queue,
VkResult chain_result =
queue_data->device->vtable.QueuePresentKHR(queue,
&present_info);
+
if (present_info.pResults) {
pPresentInfo->pResults[i] = chain_result;
}
@@ -1786,6 +3517,7 @@ static VkResult overlay_QueuePresentKHR(VkQueue queue,
result = chain_result;
}
}
+
return result;
}