diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..cb3b7e4
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+###############################
+# Core EditorConfig Options #
+###############################
+
+root = true
+
+# All files
+[*]
+indent_style = space
+
+# Code files
+[*.cs]
+indent_size = 4
+insert_final_newline = true
+charset = utf-8
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..81e4b6a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,18 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+#* text=auto
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+#*.c text
+#*.h text
+
+# Declare files that will always have CRLF line endings on checkout.
+#*.sln text eol=crlf
+
+# Denote all files that are truly binary and should not be modified.
+*.DAT binary
+*.ico binary
+*.jpg binary
+*.exe binary
+*.dll binary
+*.pfx binary
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..160740e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,27 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..ba350df
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: feature
+assignees: ''
+
+---
+
+**Please describe the idea.**
+A clear and concise description of the features functionality is. Ex. I'd like to have a command that [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 0000000..73c045a
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,13 @@
+name: Build
+on: [pull_request]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 3.0.100
+ - name: Build Project
+ run: dotnet build --configuration Release
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..3de6199
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,85 @@
+name: Create Release
+on:
+ push:
+ branches:
+ - master
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+
+ - name: Checkout
+ uses: actions/checkout@v1
+
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 3.0.100
+
+ - name: Build Release
+ id: build_release
+ run: |
+ MHF_VERSION_FILE=${{ github.workspace }}/mhf.version
+ MHF_VERSION=$(<"$MHF_VERSION_FILE")
+ echo ::set-env name=MHF_VERSION::$MHF_VERSION
+ echo ::set-env name=MHF_VERSION_E::$(echo ${GITHUB_SHA} | cut -c1-8)
+ mkdir ./release
+ for MHF_RUNTIME in win-x86 win-x64 linux-x64 osx-x64; do
+ # Server
+ dotnet publish Mhf.Cli/Mhf.Cli.csproj /p:Version=$MHF_VERSION /p:FromMSBuild=true --runtime $MHF_RUNTIME --configuration Release --output ./publish/$MHF_RUNTIME-$MHF_VERSION/Server
+ # ReleaseFiles
+ cp -r ./ReleaseFiles/. ./publish/$MHF_RUNTIME-$MHF_VERSION/
+ # Pack
+ tar cjf ./release/$MHF_RUNTIME-$MHF_VERSION.tar.gz ./publish/$MHF_RUNTIME-$MHF_VERSION
+ done
+
+ - name: Create Release
+ id: create_release
+ uses: actions/create-release@v1.0.0
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: release-${{ env.MHF_VERSION }}-${{ env.MHF_VERSION_E }}
+ release_name: Release ${{ env.MHF_VERSION }}-${{ env.MHF_VERSION_E }}
+ draft: false
+ prerelease: false
+
+ - name: Upload win-x86 Release Asset
+ uses: actions/upload-release-asset@v1.0.1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./release/win-x86-${{ env.MHF_VERSION }}.tar.gz
+ asset_name: win-x86-${{ env.MHF_VERSION }}-${{ env.MHF_VERSION_E }}.tar.gz
+ asset_content_type: application/gzip
+
+ - name: Upload win-x64 Release Asset
+ uses: actions/upload-release-asset@v1.0.1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./release/win-x64-${{ env.MHF_VERSION }}.tar.gz
+ asset_name: win-x64-${{ env.MHF_VERSION }}-${{ env.MHF_VERSION_E }}.tar.gz
+ asset_content_type: application/gzip
+
+ - name: Upload linux-x64 Release Asset
+ uses: actions/upload-release-asset@v1.0.1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./release/linux-x64-${{ env.MHF_VERSION }}.tar.gz
+ asset_name: linux-x64-${{ env.MHF_VERSION }}-${{ env.MHF_VERSION_E }}.tar.gz
+ asset_content_type: application/gzip
+
+ - name: Upload osx-x64 Release Asset
+ uses: actions/upload-release-asset@v1.0.1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./release/osx-x64-${{ env.MHF_VERSION }}.tar.gz
+ asset_name: osx-x64-${{ env.MHF_VERSION }}-${{ env.MHF_VERSION_E }}.tar.gz
+ asset_content_type: application/gzip
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dc12c96
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,42 @@
+## Folders specific
+/publish/
+/release/
+/packages/
+
+## Folders anywhere
+.idea/
+.vs/
+
+## Files anywhere
+*.DS_Store
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e72bfdd
--- /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
+.
\ No newline at end of file
diff --git a/Mhf.Cli/Argument/ConsoleParameter.cs b/Mhf.Cli/Argument/ConsoleParameter.cs
new file mode 100644
index 0000000..43e31d9
--- /dev/null
+++ b/Mhf.Cli/Argument/ConsoleParameter.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+
+namespace Mhf.Cli.Argument
+{
+ public class ConsoleParameter
+ {
+ public ConsoleParameter(string key)
+ {
+ Key = key;
+ Arguments = new List();
+ Switches = new List();
+ ArgumentMap = new Dictionary();
+ SwitchMap = new Dictionary();
+ }
+
+ public string Key { get; }
+ public List Arguments { get; }
+ public List Switches { get; }
+ public Dictionary SwitchMap { get; }
+ public Dictionary ArgumentMap { get; }
+
+ public void Clear()
+ {
+ Arguments.Clear();
+ Switches.Clear();
+ ArgumentMap.Clear();
+ SwitchMap.Clear();
+ }
+ }
+}
diff --git a/Mhf.Cli/Argument/ISwitchConsumer.cs b/Mhf.Cli/Argument/ISwitchConsumer.cs
new file mode 100644
index 0000000..982cb31
--- /dev/null
+++ b/Mhf.Cli/Argument/ISwitchConsumer.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Mhf.Cli.Argument
+{
+ public interface ISwitchConsumer
+ {
+ List Switches { get; }
+ }
+}
diff --git a/Mhf.Cli/Argument/ISwitchProperty.cs b/Mhf.Cli/Argument/ISwitchProperty.cs
new file mode 100644
index 0000000..c9e0045
--- /dev/null
+++ b/Mhf.Cli/Argument/ISwitchProperty.cs
@@ -0,0 +1,10 @@
+namespace Mhf.Cli.Argument
+{
+ public interface ISwitchProperty
+ {
+ string Key { get; }
+ string Description { get; }
+ string ValueDescription { get; }
+ bool Assign(string value);
+ }
+}
diff --git a/Mhf.Cli/Argument/SwitchProperty.cs b/Mhf.Cli/Argument/SwitchProperty.cs
new file mode 100644
index 0000000..4f113f6
--- /dev/null
+++ b/Mhf.Cli/Argument/SwitchProperty.cs
@@ -0,0 +1,48 @@
+namespace Mhf.Cli.Argument
+{
+ public class SwitchProperty : ISwitchProperty
+ {
+ public static TryParseHandler NoOp = (string value, out T result) =>
+ {
+ result = default;
+ return true;
+ };
+
+ private TryParseHandler _parser;
+ private AssignHandler _assigner;
+
+ public SwitchProperty(string key, string valueDescription, string description, TryParseHandler parser,
+ AssignHandler assigner)
+ {
+ Key = key;
+ ValueDescription = valueDescription;
+ Description = description;
+ _parser = parser;
+ _assigner = assigner;
+ }
+
+ public delegate bool TryParseHandler(string value, out T result);
+
+ public delegate void AssignHandler(T result);
+
+ public string Key { get; }
+ public string Description { get; }
+ public string ValueDescription { get; }
+
+ public bool Assign(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ return false;
+ }
+
+ if (!_parser(value, out T result))
+ {
+ return false;
+ }
+
+ _assigner(result);
+ return true;
+ }
+ }
+}
diff --git a/Mhf.Cli/Command/CommandResultType.cs b/Mhf.Cli/Command/CommandResultType.cs
new file mode 100644
index 0000000..e5d4ad6
--- /dev/null
+++ b/Mhf.Cli/Command/CommandResultType.cs
@@ -0,0 +1,9 @@
+namespace Mhf.Cli.Command
+{
+ public enum CommandResultType
+ {
+ Exit,
+ Continue,
+ Completed
+ }
+}
diff --git a/Mhf.Cli/Command/Commands/DecryptCommand.cs b/Mhf.Cli/Command/Commands/DecryptCommand.cs
new file mode 100644
index 0000000..54264b2
--- /dev/null
+++ b/Mhf.Cli/Command/Commands/DecryptCommand.cs
@@ -0,0 +1,19 @@
+using Mhf.Cli.Argument;
+using Mhf.Server.Packet;
+using Mhf.Server.Setting;
+
+namespace Mhf.Cli.Command.Commands
+{
+ public class DecryptCommand : ConsoleCommand
+ {
+ public override CommandResultType Handle(ConsoleParameter parameter)
+ {
+ PacketFactory pf = new PacketFactory(new MhfSetting());
+ pf.Test();
+ return CommandResultType.Completed;
+ }
+
+ public override string Key => "decrypt";
+ public override string Description => "Decrypt packet data";
+ }
+}
diff --git a/Mhf.Cli/Command/Commands/ExitCommand.cs b/Mhf.Cli/Command/Commands/ExitCommand.cs
new file mode 100644
index 0000000..e70ca5e
--- /dev/null
+++ b/Mhf.Cli/Command/Commands/ExitCommand.cs
@@ -0,0 +1,16 @@
+using Mhf.Cli.Argument;
+
+namespace Mhf.Cli.Command.Commands
+{
+ public class ExitCommand : ConsoleCommand
+ {
+ public override CommandResultType Handle(ConsoleParameter parameter)
+ {
+ Logger.Info("Exiting...");
+ return CommandResultType.Exit;
+ }
+
+ public override string Key => "exit";
+ public override string Description => "Closes the program";
+ }
+}
diff --git a/Mhf.Cli/Command/Commands/HelpCommand.cs b/Mhf.Cli/Command/Commands/HelpCommand.cs
new file mode 100644
index 0000000..0d059db
--- /dev/null
+++ b/Mhf.Cli/Command/Commands/HelpCommand.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mhf.Cli.Argument;
+
+namespace Mhf.Cli.Command.Commands
+{
+ public class HelpCommand : ConsoleCommand
+ {
+ private readonly Dictionary _commands;
+
+ public HelpCommand(Dictionary commands)
+ {
+ _commands = commands;
+ }
+
+ public override CommandResultType Handle(ConsoleParameter parameter)
+ {
+ if (parameter.Arguments.Count >= 1)
+ {
+ string subKey = parameter.Arguments[0];
+ if (!_commands.ContainsKey(subKey))
+ {
+ Logger.Error(
+ $"Command: 'help {subKey}' not available. Type 'help' for a list of available commands.");
+ return CommandResultType.Continue;
+ }
+
+ IConsoleCommand consoleCommandHelp = _commands[subKey];
+ Logger.Info(ShowHelp(consoleCommandHelp));
+ return CommandResultType.Continue;
+ }
+
+ ShowHelp();
+ return CommandResultType.Continue;
+ }
+
+ private void ShowHelp()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append(Environment.NewLine);
+ sb.Append("Available Commands:");
+ foreach (string key in _commands.Keys)
+ {
+ sb.Append(Environment.NewLine);
+ sb.Append("----------");
+ IConsoleCommand command = _commands[key];
+ sb.Append(ShowHelp(command));
+ }
+
+ Logger.Info(sb.ToString());
+ }
+
+ private string ShowHelp(IConsoleCommand command)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append(Environment.NewLine);
+ sb.Append(command.Key);
+ sb.Append(Environment.NewLine);
+ sb.Append($"- {command.Description}");
+ return sb.ToString();
+ }
+
+ public override string Key => "help";
+ public override string Description => "Displays this text";
+ }
+}
diff --git a/Mhf.Cli/Command/Commands/ServerCommand.cs b/Mhf.Cli/Command/Commands/ServerCommand.cs
new file mode 100644
index 0000000..59327c5
--- /dev/null
+++ b/Mhf.Cli/Command/Commands/ServerCommand.cs
@@ -0,0 +1,55 @@
+using System;
+using Mhf.Cli.Argument;
+using Mhf.Server;
+using Mhf.Server.Setting;
+
+namespace Mhf.Cli.Command.Commands
+{
+ public class ServerCommand : ConsoleCommand
+ {
+ private MhfServer _server;
+ private readonly LogWriter _logWriter;
+
+ public ServerCommand(LogWriter logWriter)
+ {
+ _logWriter = logWriter;
+ }
+
+ public override void Shutdown()
+ {
+ if (_server != null)
+ {
+ _server.Stop();
+ }
+ }
+
+ public override CommandResultType Handle(ConsoleParameter parameter)
+ {
+ if (_server == null)
+ {
+ MhfSetting setting = new MhfSetting();
+ _server = new MhfServer(setting);
+ }
+
+ if (parameter.Arguments.Contains("start"))
+ {
+ _server.Start();
+ return CommandResultType.Completed;
+ }
+
+ if (parameter.Arguments.Contains("stop"))
+ {
+ _server.Stop();
+ return CommandResultType.Completed;
+ }
+
+ return CommandResultType.Continue;
+ }
+
+ public override string Key => "server";
+
+
+ public override string Description =>
+ $"Monster Hunter Frontier Z Online Server. Ex.:{Environment.NewLine}server start{Environment.NewLine}server stop";
+ }
+}
diff --git a/Mhf.Cli/Command/Commands/ShowCommand.cs b/Mhf.Cli/Command/Commands/ShowCommand.cs
new file mode 100644
index 0000000..a081820
--- /dev/null
+++ b/Mhf.Cli/Command/Commands/ShowCommand.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Text;
+using Mhf.Cli.Argument;
+
+namespace Mhf.Cli.Command.Commands
+{
+ public class ShowCommand : ConsoleCommand
+ {
+ public override CommandResultType Handle(ConsoleParameter parameter)
+ {
+ if (parameter.Arguments.Contains("w"))
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append(Environment.NewLine);
+ sb.Append("15. Disclaimer of Warranty.");
+ sb.Append(Environment.NewLine);
+ sb.Append("THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY");
+ sb.Append(Environment.NewLine);
+ sb.Append("APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT");
+ sb.Append(Environment.NewLine);
+ sb.Append("HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY");
+ sb.Append(Environment.NewLine);
+ sb.Append("OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,");
+ sb.Append(Environment.NewLine);
+ sb.Append("THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR");
+ sb.Append(Environment.NewLine);
+ sb.Append("PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM");
+ sb.Append(Environment.NewLine);
+ sb.Append("IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF");
+ sb.Append(Environment.NewLine);
+ sb.Append("ALL NECESSARY SERVICING, REPAIR OR CORRECTION.");
+ sb.Append(Environment.NewLine);
+ Logger.Info(sb.ToString());
+ return CommandResultType.Completed;
+ }
+
+ if (parameter.Arguments.Contains("c"))
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append(Environment.NewLine);
+ sb.Append("2. Basic Permissions.");
+ sb.Append(Environment.NewLine);
+ sb.Append(Environment.NewLine);
+ sb.Append("All rights granted under this License are granted for the term of");
+ sb.Append(Environment.NewLine);
+ sb.Append("copyright on the Program, and are irrevocable provided the stated");
+ sb.Append(Environment.NewLine);
+ sb.Append("conditions are met. This License explicitly affirms your unlimited");
+ sb.Append(Environment.NewLine);
+ sb.Append("permission to run the unmodified Program. The output from running a");
+ sb.Append(Environment.NewLine);
+ sb.Append("covered work is covered by this License only if the output, given its");
+ sb.Append(Environment.NewLine);
+ sb.Append("content, constitutes a covered work. This License acknowledges your");
+ sb.Append(Environment.NewLine);
+ sb.Append("rights of fair use or other equivalent, as provided by copyright law.");
+ sb.Append(Environment.NewLine);
+ sb.Append(Environment.NewLine);
+ sb.Append("You may make, run and propagate covered works that you do not");
+ sb.Append(Environment.NewLine);
+ sb.Append("convey, without conditions so long as your license otherwise remains");
+ sb.Append(Environment.NewLine);
+ sb.Append("in force. You may convey covered works to others for the sole purpose");
+ sb.Append(Environment.NewLine);
+ sb.Append("of having them make modifications exclusively for you, or provide you");
+ sb.Append(Environment.NewLine);
+ sb.Append("with facilities for running those works, provided that you comply with");
+ sb.Append(Environment.NewLine);
+ sb.Append("the terms of this License in conveying all material for which you do");
+ sb.Append(Environment.NewLine);
+ sb.Append("not control copyright. Those thus making or running the covered works");
+ sb.Append(Environment.NewLine);
+ sb.Append("for you must do so exclusively on your behalf, under your direction");
+ sb.Append(Environment.NewLine);
+ sb.Append("and control, on terms that prohibit them from making any copies of");
+ sb.Append(Environment.NewLine);
+ sb.Append("your copyrighted material outside their relationship with you.");
+ sb.Append(Environment.NewLine);
+ sb.Append(Environment.NewLine);
+ sb.Append("Conveying under any other circumstances is permitted solely under");
+ sb.Append(Environment.NewLine);
+ sb.Append("the conditions stated below. Sublicensing is not allowed; section 10");
+ sb.Append(Environment.NewLine);
+ sb.Append("makes it unnecessary.");
+ sb.Append(Environment.NewLine);
+ Logger.Info(sb.ToString());
+ return CommandResultType.Completed;
+ }
+
+ return CommandResultType.Continue;
+ }
+
+ public override string Key => "show";
+
+ public override string Description =>
+ $"Shows Copyright. Ex.:{Environment.NewLine}show w{Environment.NewLine}show c";
+ }
+}
diff --git a/Mhf.Cli/Command/Commands/SwitchCommand.cs b/Mhf.Cli/Command/Commands/SwitchCommand.cs
new file mode 100644
index 0000000..121fa57
--- /dev/null
+++ b/Mhf.Cli/Command/Commands/SwitchCommand.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mhf.Cli.Argument;
+
+namespace Mhf.Cli.Command.Commands
+{
+ public class SwitchCommand : ConsoleCommand
+ {
+ private readonly List _parameterConsumers;
+
+ public SwitchCommand(List parameterConsumers)
+ {
+ _parameterConsumers = parameterConsumers;
+ }
+
+ public override CommandResultType Handle(ConsoleParameter parameter)
+ {
+ foreach (string key in parameter.SwitchMap.Keys)
+ {
+ ISwitchProperty property = FindSwitch(key);
+ if (property == null)
+ {
+ Logger.Error($"Switch '{key}' not found");
+ continue;
+ }
+
+ string value = parameter.SwitchMap[key];
+ if (!property.Assign(value))
+ {
+ Logger.Error($"Switch '{key}' failed, value: '{value}' is invalid");
+ }
+ else
+ {
+ Logger.Info($"Applied {key}={value}");
+ }
+ }
+
+ foreach (string booleanSwitch in parameter.Switches)
+ {
+ ISwitchProperty property = FindSwitch(booleanSwitch);
+ if (property == null)
+ {
+ Logger.Error($"Switch '{booleanSwitch}' not found");
+ continue;
+ }
+ if (!property.Assign(bool.TrueString))
+ {
+ Logger.Error($"Switch '{booleanSwitch}' failed, value: '{bool.TrueString}' is invalid");
+ }
+ else
+ {
+ Logger.Info($"Applied {booleanSwitch}={bool.TrueString}");
+ }
+ }
+
+ return CommandResultType.Completed;
+ }
+
+ private ISwitchProperty FindSwitch(string key)
+ {
+ foreach (ISwitchConsumer consumer in _parameterConsumers)
+ {
+ foreach (ISwitchProperty property in consumer.Switches)
+ {
+ if (property.Key == key)
+ {
+ return property;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private string ShowSwitches()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("Available Switches:");
+ sb.Append(Environment.NewLine);
+ foreach (ISwitchConsumer consumer in _parameterConsumers)
+ {
+ foreach (ISwitchProperty property in consumer.Switches)
+ {
+ sb.Append(Environment.NewLine);
+ sb.Append(property.Key);
+ sb.Append(Environment.NewLine);
+ sb.Append("> Ex.: ");
+ sb.Append(property.ValueDescription);
+ sb.Append(Environment.NewLine);
+ sb.Append("> ");
+ sb.Append(property.Description);
+ sb.Append(Environment.NewLine);
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ public override string Key => "switch";
+ public override string Description => $"Changes configuration switches{Environment.NewLine}{ShowSwitches()}";
+ }
+}
diff --git a/Mhf.Cli/Command/ConsoleCommand.cs b/Mhf.Cli/Command/ConsoleCommand.cs
new file mode 100644
index 0000000..c9110bb
--- /dev/null
+++ b/Mhf.Cli/Command/ConsoleCommand.cs
@@ -0,0 +1,23 @@
+using Arrowgene.Services.Logging;
+using Mhf.Cli.Argument;
+
+namespace Mhf.Cli.Command
+{
+ public abstract class ConsoleCommand : IConsoleCommand
+ {
+ protected ConsoleCommand()
+ {
+ Logger = LogProvider.Logger(this);
+ }
+
+ protected readonly ILogger Logger;
+
+ public abstract string Key { get; }
+ public abstract string Description { get; }
+ public abstract CommandResultType Handle(ConsoleParameter parameter);
+
+ public virtual void Shutdown()
+ {
+ }
+ }
+}
diff --git a/Mhf.Cli/Command/IConsoleCommand.cs b/Mhf.Cli/Command/IConsoleCommand.cs
new file mode 100644
index 0000000..40c7de4
--- /dev/null
+++ b/Mhf.Cli/Command/IConsoleCommand.cs
@@ -0,0 +1,12 @@
+using Mhf.Cli.Argument;
+
+namespace Mhf.Cli.Command
+{
+ public interface IConsoleCommand
+ {
+ CommandResultType Handle(ConsoleParameter parameter);
+ void Shutdown();
+ string Key { get; }
+ string Description { get; }
+ }
+}
diff --git a/Mhf.Cli/LogWriter.cs b/Mhf.Cli/LogWriter.cs
new file mode 100644
index 0000000..ff47a29
--- /dev/null
+++ b/Mhf.Cli/LogWriter.cs
@@ -0,0 +1,367 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using System.Threading;
+using Arrowgene.Services.Buffers;
+using Arrowgene.Services.Logging;
+using Mhf.Cli.Argument;
+using Mhf.Server.Logging;
+
+namespace Mhf.Cli
+{
+ public class LogWriter : ISwitchConsumer
+ {
+ private readonly object _consoleLock;
+ private readonly HashSet _packetIdWhitelist;
+ private readonly HashSet _packetIdBlacklist;
+ private readonly ILogger _logger;
+ private readonly Queue _logQueue;
+ private bool _paused;
+ private bool _continueing;
+
+ public LogWriter()
+ {
+ _logger = LogProvider.Logger(this);
+ _packetIdWhitelist = new HashSet();
+ _packetIdBlacklist = new HashSet();
+ _logQueue = new Queue();
+ _consoleLock = new object();
+ Switches = new List();
+ _paused = false;
+ _continueing = false;
+ Reset();
+ LoadSwitches();
+ LogProvider.GlobalLogWrite += LogProviderOnGlobalLogWrite;
+ }
+
+ public List Switches { get; }
+
+ ///
+ /// --max-packet-size=64
+ ///
+ public int MaxPacketSize { get; set; }
+
+ ///
+ /// --no-data=true
+ ///
+ public bool NoData { get; set; }
+
+ ///
+ /// --log-level=2
+ ///
+ public int MinLogLevel { get; set; }
+
+ public void Reset()
+ {
+ MaxPacketSize = -1;
+ NoData = false;
+ MinLogLevel = (int) LogLevel.Debug;
+ _packetIdWhitelist.Clear();
+ _packetIdBlacklist.Clear();
+ }
+
+ public void WhitelistPacket(ushort packetId)
+ {
+ if (_packetIdWhitelist.Contains(packetId))
+ {
+ _logger.Error($"PacketId:{packetId} is already whitelisted");
+ return;
+ }
+
+ _packetIdWhitelist.Add(packetId);
+ }
+
+ public void BlacklistPacket(ushort packetId)
+ {
+ if (_packetIdBlacklist.Contains(packetId))
+ {
+ _logger.Error($"PacketId:{packetId} is already blacklisted");
+ return;
+ }
+
+ _packetIdBlacklist.Add(packetId);
+ }
+
+ public void Pause()
+ {
+ _paused = true;
+ }
+
+ public void Continue()
+ {
+ _continueing = true;
+ while (_logQueue.TryDequeue(out Log log))
+ {
+ WriteLog(log);
+ }
+
+ _paused = false;
+ _continueing = false;
+ }
+
+ private void LoadSwitches()
+ {
+ Switches.Add(
+ new SwitchProperty(
+ "--no-data",
+ "--no-data=true (true|false)",
+ "Don't display packet data",
+ bool.TryParse,
+ (result => NoData = result)
+ )
+ );
+ Switches.Add(
+ new SwitchProperty(
+ "--max-packet-size",
+ "--max-packet-size=64 (integer)",
+ "Don't display packet data",
+ int.TryParse,
+ (result => MaxPacketSize = result)
+ )
+ );
+ Switches.Add(
+ new SwitchProperty(
+ "--log-level",
+ "--log-level=20 (integer) [Debug=10, Info=20, Error=30]",
+ "Only display logs of the same level or above",
+ int.TryParse,
+ (result => MinLogLevel = result)
+ )
+ );
+ Switches.Add(
+ new SwitchProperty(
+ "--clear",
+ "--clear",
+ "Resets all switches to default",
+ SwitchProperty.NoOp,
+ result => Reset()
+ )
+ );
+ Switches.Add(
+ new SwitchProperty>(
+ "--b-list",
+ "--b-list=1000,2000,0xAA (PacketId[0xA|10])",
+ "A blacklist that does not logs packets specified",
+ TryParsePacketIdList,
+ results => { AssinPacketIdList(results, BlacklistPacket); }
+ )
+ );
+ Switches.Add(
+ new SwitchProperty>(
+ "--w-list",
+ "--w-list=1000,2000,0xAA (PacketId[0xA|10])",
+ "A whitelist that only logs packets specified",
+ TryParsePacketIdList,
+ results => { AssinPacketIdList(results, WhitelistPacket); }
+ )
+ );
+ }
+
+ ///
+ /// parses strings like "1:1000,2000,3:4000" into ServerType and PacketId
+ ///
+ private bool TryParsePacketIdList(string value, out List result)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ result = null;
+ return false;
+ }
+
+ string[] values = value.Split(",");
+
+ result = new List();
+ foreach (string entry in values)
+ {
+ NumberStyles numberStyles;
+ String entryStr;
+ if (entry.StartsWith("0x"))
+ {
+ entryStr = entry.Substring(2);
+ numberStyles = NumberStyles.HexNumber;
+ }
+ else
+ {
+ entryStr = entry;
+ numberStyles = NumberStyles.Integer;
+ }
+
+
+ if (!ushort.TryParse(entryStr, numberStyles, null, out ushort val))
+ {
+ return false;
+ }
+
+ result.Add(val);
+ }
+
+ return true;
+ }
+
+ private void AssinPacketIdList(List results, Action addToPacketList)
+ {
+ foreach (ushort entry in results)
+ {
+ addToPacketList(entry);
+ }
+ }
+
+ private void LogProviderOnGlobalLogWrite(object sender, LogWriteEventArgs logWriteEventArgs)
+ {
+ while (_continueing)
+ {
+ Thread.Sleep(10000);
+ }
+
+ if (_paused)
+ {
+ _logQueue.Enqueue(logWriteEventArgs.Log);
+ return;
+ }
+
+ WriteLog(logWriteEventArgs.Log);
+ }
+
+ private void WriteLog(Log log)
+ {
+ ConsoleColor consoleColor;
+ string text;
+
+ object tag = log.Tag;
+ if (tag is MhfLogPacket logPacket)
+ {
+ switch (logPacket.LogType)
+ {
+ case MhfLogType.PacketIn:
+ consoleColor = ConsoleColor.Green;
+ break;
+ case MhfLogType.PacketOut:
+ consoleColor = ConsoleColor.Blue;
+ break;
+ case MhfLogType.PacketUnhandled:
+ consoleColor = ConsoleColor.Red;
+ break;
+ default:
+ consoleColor = ConsoleColor.Gray;
+ break;
+ }
+
+ text = CreatePacketLog(logPacket);
+ }
+ else
+ {
+ LogLevel logLevel = log.LogLevel;
+ if ((int) logLevel < MinLogLevel)
+ {
+ return;
+ }
+
+ switch (logLevel)
+ {
+ case LogLevel.Debug:
+ consoleColor = ConsoleColor.DarkCyan;
+ break;
+ case LogLevel.Info:
+ consoleColor = ConsoleColor.Cyan;
+ break;
+ case LogLevel.Error:
+ consoleColor = ConsoleColor.Red;
+ break;
+ default:
+ consoleColor = ConsoleColor.Gray;
+ break;
+ }
+
+ text = log.ToString();
+ }
+
+ if (text == null)
+ {
+ return;
+ }
+
+ lock (_consoleLock)
+ {
+ Console.ForegroundColor = consoleColor;
+ Console.WriteLine(text);
+ Console.ResetColor();
+ }
+ }
+
+ private bool ExcludeLog(ushort packetId)
+ {
+ bool useWhitelist = _packetIdWhitelist.Count > 0;
+ bool whitelisted = _packetIdWhitelist.Contains(packetId);
+ bool blacklisted = _packetIdBlacklist.Contains(packetId);
+
+ if (useWhitelist && whitelisted)
+ {
+ return false;
+ }
+
+ if (useWhitelist)
+ {
+ return true;
+ }
+
+ if (blacklisted)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private string CreatePacketLog(MhfLogPacket logPacket)
+ {
+ ushort packetId = logPacket.Id;
+
+ if (ExcludeLog(packetId))
+ {
+ return null;
+ }
+
+ int dataSize = logPacket.Data.Size;
+
+ StringBuilder sb = new StringBuilder();
+ sb.Append($"{logPacket.ClientIdentity} Packet Log");
+ sb.Append(Environment.NewLine);
+ sb.Append("----------");
+ sb.Append(Environment.NewLine);
+ sb.Append($"[{logPacket.TimeStamp:HH:mm:ss}][Typ:{logPacket.LogType}]");
+ sb.Append(Environment.NewLine);
+ sb.Append(
+ $"[Id:0x{logPacket.Id:X2}|{logPacket.Id}][BodyLen:{logPacket.Data.Size}][{logPacket.PacketIdName}]");
+ sb.Append(Environment.NewLine);
+ sb.Append(logPacket.Header.ToLogText());
+ sb.Append(Environment.NewLine);
+
+ if (!NoData)
+ {
+ IBuffer data = logPacket.Data;
+ int maxPacketSize = MaxPacketSize;
+ if (maxPacketSize > 0 && dataSize > maxPacketSize)
+ {
+ data = data.Clone(0, maxPacketSize);
+
+ sb.Append($"- Truncated Data showing {maxPacketSize} of {dataSize} bytes");
+ sb.Append(Environment.NewLine);
+ }
+
+ sb.Append("ASCII:");
+ sb.Append(Environment.NewLine);
+ sb.Append(data.ToAsciiString(true));
+ sb.Append(Environment.NewLine);
+ sb.Append("HEX:");
+ sb.Append(Environment.NewLine);
+ sb.Append(data.ToHexString(' '));
+ sb.Append(Environment.NewLine);
+ }
+
+ sb.Append("----------");
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/Mhf.Cli/Mhf.Cli.csproj b/Mhf.Cli/Mhf.Cli.csproj
new file mode 100644
index 0000000..7de7981
--- /dev/null
+++ b/Mhf.Cli/Mhf.Cli.csproj
@@ -0,0 +1,16 @@
+
+
+ Exe
+ netcoreapp3.0
+ Mhf.Cli
+ Monster Hunter Frontier Z Command Line Interface
+ Mhf Team
+ Mhf.Cli
+ $(Version)
+ Copyright © 2019 Mhf Team
+ 8
+
+
+
+
+
diff --git a/Mhf.Cli/Program.cs b/Mhf.Cli/Program.cs
new file mode 100644
index 0000000..ce58c07
--- /dev/null
+++ b/Mhf.Cli/Program.cs
@@ -0,0 +1,383 @@
+/*
+ * This file is part of Mhf.Cli
+ *
+ * Mhf.Server is a server implementation for the game "Monster Hunter Frontier Z".
+ * Copyright (C) 2019-2020 Mhf Team
+ *
+ * Github: https://github.com/sebastian-heinz/mhf-server
+ *
+ * Mhf.Server 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.
+ *
+ * Mhf.Server 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 Mhf.Server. If not, see .
+ */
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using Arrowgene.Services.Logging;
+using Mhf.Cli.Argument;
+using Mhf.Cli.Command;
+using Mhf.Cli.Command.Commands;
+using Mhf.Server.Common;
+
+namespace Mhf.Cli
+{
+ internal class Program
+ {
+ public const char CliSeparator = ' ';
+ public const char CliValueSeparator = '=';
+
+ private static void Main(string[] args)
+ {
+ Console.WriteLine("Program started");
+ Program program = new Program();
+ if (args.Length > 0)
+ {
+ program.RunArguments(args);
+ }
+ else
+ {
+ program.RunInteractive();
+ }
+
+ Console.WriteLine("Program ended");
+ }
+
+ private readonly CancellationTokenSource _cancellationTokenSource;
+ private readonly BlockingCollection _inputQueue;
+ private readonly Thread _consoleThread;
+ private readonly Dictionary _commands;
+ private readonly List _parameterConsumers;
+ private readonly ILogger _logger;
+ private readonly LogWriter _logWriter;
+ private readonly SwitchCommand _switchCommand;
+
+ private Program()
+ {
+ _logger = LogProvider.Logger(this);
+ _commands = new Dictionary();
+ _inputQueue = new BlockingCollection();
+ _parameterConsumers = new List();
+ _cancellationTokenSource = new CancellationTokenSource();
+ _consoleThread = new Thread(ReadConsoleThread);
+ _logWriter = new LogWriter();
+ _switchCommand = new SwitchCommand(_parameterConsumers);
+ Console.CancelKeyPress += ConsoleOnCancelKeyPress;
+ }
+
+ private void LoadCommands()
+ {
+ AddCommand(new ShowCommand());
+ AddCommand(new ServerCommand(_logWriter));
+ AddCommand(new HelpCommand(_commands));
+ AddCommand(new ExitCommand());
+ AddCommand(new DecryptCommand());
+ AddCommand(_switchCommand);
+ }
+
+ private void LoadGlobalParameterConsumer()
+ {
+ _parameterConsumers.Add(_logWriter);
+ }
+
+ private void RunArguments(string[] arguments)
+ {
+ if (arguments.Length <= 0)
+ {
+ _logger.Error("Invalid input");
+ return;
+ }
+
+ LoadCommands();
+ LoadGlobalParameterConsumer();
+ ShowCopyright();
+ _logger.Info("Argument Mode");
+ _logger.Info("Press `e'-key to exit.");
+
+ ProcessArguments(arguments);
+
+ ConsoleKeyInfo keyInfo = Console.ReadKey();
+ while (keyInfo.Key != ConsoleKey.E)
+ {
+ keyInfo = Console.ReadKey();
+ }
+
+ ShutdownCommands();
+ }
+
+ private void RunInteractive()
+ {
+ LoadCommands();
+ LoadGlobalParameterConsumer();
+ ShowCopyright();
+
+ _logger.Info("Interactive Mode");
+
+ _consoleThread.IsBackground = true;
+ _consoleThread.Name = "Console Thread";
+ _consoleThread.Start();
+
+ while (!_cancellationTokenSource.Token.IsCancellationRequested)
+ {
+ string line;
+ try
+ {
+ line = _inputQueue.Take(_cancellationTokenSource.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ line = null;
+ }
+
+ if (line == null)
+ {
+ // Ctrl+Z, Ctrl+C or error
+ break;
+ }
+
+ string[] arguments = Util.ParseTextArguments(line, CliSeparator, '"');
+ if (arguments.Length <= 0)
+ {
+ _logger.Error($"Invalid input: '{line}'. Type 'help' for a list of available commands.");
+ continue;
+ }
+
+ CommandResultType result = ProcessArguments(arguments);
+ if (result == CommandResultType.Exit)
+ {
+ break;
+ }
+
+ if (result == CommandResultType.Continue)
+ {
+ continue;
+ }
+
+ if (result == CommandResultType.Completed)
+ {
+ _logger.Info("Command Completed");
+ continue;
+ }
+ }
+
+ StopReadConsoleThread();
+ ShutdownCommands();
+ }
+
+ private CommandResultType ProcessArguments(string[] arguments)
+ {
+ ConsoleParameter parameter = ParseParameter(arguments);
+
+ if (!_commands.ContainsKey(parameter.Key))
+ {
+ _logger.Error(
+ $"Command: '{parameter.Key}' not available. Type `help' for a list of available commands.");
+ return CommandResultType.Continue;
+ }
+
+ IConsoleCommand consoleCommand = _commands[parameter.Key];
+ if (consoleCommand != _switchCommand)
+ {
+ _switchCommand.Handle(parameter);
+ }
+
+ return consoleCommand.Handle(parameter);
+ }
+
+ ///
+ /// Parses the input text into arguments and switches.
+ ///
+ private ConsoleParameter ParseParameter(string[] args)
+ {
+ if (args.Length <= 0)
+ {
+ _logger.Error("Invalid input. Type 'help' for a list of available commands.");
+ return null;
+ }
+
+ string paramKey = args[0];
+ int cmdLength = args.Length - 1;
+ string[] newArguments = new string[cmdLength];
+ if (cmdLength > 0)
+ {
+ Array.Copy(args, 1, newArguments, 0, cmdLength);
+ }
+
+ args = newArguments;
+
+ ConsoleParameter parameter = new ConsoleParameter(paramKey);
+ foreach (string arg in args)
+ {
+ int count = CountChar(arg, CliValueSeparator);
+ if (count == 1)
+ {
+ string[] keyValue = arg.Split(CliValueSeparator);
+ if (keyValue.Length == 2)
+ {
+ string key = keyValue[0];
+ string value = keyValue[1];
+ if (key.StartsWith('-'))
+ {
+ if (key.Length <= 2 || parameter.SwitchMap.ContainsKey(key))
+ {
+ _logger.Error($"Invalid switch key: '{key}' is empty or duplicated.");
+ continue;
+ }
+
+ parameter.SwitchMap.Add(key, value);
+ continue;
+ }
+
+ if (key.Length <= 0 || parameter.ArgumentMap.ContainsKey(key))
+ {
+ _logger.Error($"Invalid argument key: '{key}' is empty or duplicated.");
+ continue;
+ }
+
+ parameter.ArgumentMap.Add(key, value);
+ continue;
+ }
+ }
+
+ if (arg.StartsWith('-'))
+ {
+ string switchStr = arg;
+ if (switchStr.Length <= 2 || parameter.Switches.Contains(switchStr))
+ {
+ _logger.Error($"Invalid switch: '{switchStr}' is empty or duplicated.");
+ continue;
+ }
+
+ parameter.Switches.Add(switchStr);
+ continue;
+ }
+
+ if (arg.Length <= 0 || parameter.Switches.Contains(arg))
+ {
+ _logger.Error($"Invalid argument: '{arg}' is empty or duplicated.");
+ continue;
+ }
+
+ parameter.Arguments.Add(arg);
+ }
+
+ return parameter;
+ }
+
+ private int CountChar(string str, char chr)
+ {
+ int count = 0;
+ foreach (char c in str)
+ {
+ if (c == chr)
+ {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+
+ private void ReadConsoleThread()
+ {
+ while (!_cancellationTokenSource.Token.IsCancellationRequested)
+ {
+ if (Console.ReadKey().Key != ConsoleKey.Enter)
+ {
+ continue;
+ }
+
+ _logWriter.Pause();
+ Console.WriteLine("Enter Command:");
+ string line = Console.ReadLine();
+ try
+ {
+ _inputQueue.Add(line, _cancellationTokenSource.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ // Ignored
+ }
+
+ _logWriter.Continue();
+ }
+ }
+
+ private void StopReadConsoleThread()
+ {
+ if (_consoleThread != null
+ && _consoleThread.IsAlive
+ && Thread.CurrentThread != _consoleThread
+ )
+ {
+ try
+ {
+ _consoleThread.Interrupt();
+ }
+ catch (Exception)
+ {
+ // Ignored
+ }
+
+ if (!_consoleThread.Join(TimeSpan.FromMilliseconds(500)))
+ {
+ try
+ {
+ _consoleThread.Abort();
+ }
+ catch (Exception)
+ {
+ // Ignored
+ }
+ }
+ }
+ }
+
+ private void ConsoleOnCancelKeyPress(object sender, ConsoleCancelEventArgs e)
+ {
+ _cancellationTokenSource.Cancel();
+ }
+
+ private void AddCommand(IConsoleCommand command)
+ {
+ _commands.Add(command.Key, command);
+ }
+
+ private void ShutdownCommands()
+ {
+ foreach (IConsoleCommand command in _commands.Values)
+ {
+ command.Shutdown();
+ }
+ }
+
+ private void ShowCopyright()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append(Environment.NewLine);
+ sb.Append(Environment.NewLine);
+ sb.Append("Mhf.Cli Copyright (C) 2019-2020 Mhf Team");
+ sb.Append(Environment.NewLine);
+ sb.Append("This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.");
+ sb.Append(Environment.NewLine);
+ sb.Append("This is free software, and you are welcome to redistribute it");
+ sb.Append(Environment.NewLine);
+ sb.Append("under certain conditions; type `show c' for details.");
+ sb.Append(Environment.NewLine);
+ sb.Append(Environment.NewLine);
+ _logger.Info(sb.ToString());
+ }
+ }
+}
diff --git a/Mhf.Server/Common/BufferProvider.cs b/Mhf.Server/Common/BufferProvider.cs
new file mode 100644
index 0000000..b1271b8
--- /dev/null
+++ b/Mhf.Server/Common/BufferProvider.cs
@@ -0,0 +1,19 @@
+using Arrowgene.Services.Buffers;
+
+namespace Mhf.Server.Common
+{
+ public class BufferProvider
+ {
+ private static readonly IBufferProvider Provider = new StreamBuffer();
+
+ public static IBuffer Provide()
+ {
+ return Provider.Provide();
+ }
+
+ public static IBuffer Provide(byte[] data)
+ {
+ return Provider.Provide(data);
+ }
+ }
+}
diff --git a/Mhf.Server/Common/Crc/Crc32.cs b/Mhf.Server/Common/Crc/Crc32.cs
new file mode 100644
index 0000000..3a502d2
--- /dev/null
+++ b/Mhf.Server/Common/Crc/Crc32.cs
@@ -0,0 +1,121 @@
+// Copyright (c) Damien Guard. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+
+namespace Mhf.Server.Common.Crc
+{
+ ///
+ /// Implements a 32-bit CRC hash algorithm compatible with Zip etc.
+ ///
+ ///
+ /// Crc32 should only be used for backward compatibility with older file formats
+ /// and algorithms. It is not secure enough for new applications.
+ /// If you need to call multiple times for the same data either use the HashAlgorithm
+ /// interface or remember that the result of one Compute call needs to be ~ (XOR) before
+ /// being passed in as the seed for the next Compute call.
+ ///
+ public sealed class Crc32 : HashAlgorithm
+ {
+ public const UInt32 DefaultPolynomial = 0xedb88320u;
+ public const UInt32 DefaultSeed = 0xffffffffu;
+
+ static UInt32[] defaultTable;
+
+ readonly UInt32 seed;
+ readonly UInt32[] table;
+ UInt32 hash;
+
+ public Crc32()
+ : this(DefaultPolynomial, DefaultSeed)
+ {
+ }
+
+ public Crc32(UInt32 polynomial, UInt32 seed)
+ {
+ if (!BitConverter.IsLittleEndian)
+ throw new PlatformNotSupportedException("Not supported on Big Endian processors");
+
+ table = InitializeTable(polynomial);
+ this.seed = hash = seed;
+ }
+
+ public override void Initialize()
+ {
+ hash = seed;
+ }
+
+ protected override void HashCore(byte[] array, int ibStart, int cbSize)
+ {
+ hash = CalculateHash(table, hash, array, ibStart, cbSize);
+ }
+
+ protected override byte[] HashFinal()
+ {
+ var hashBuffer = UInt32ToBigEndianBytes(~hash);
+ HashValue = hashBuffer;
+ return hashBuffer;
+ }
+
+ public override int HashSize { get { return 32; } }
+
+ public static UInt32 Compute(byte[] buffer)
+ {
+ return Compute(DefaultSeed, buffer);
+ }
+
+ public static UInt32 Compute(UInt32 seed, byte[] buffer)
+ {
+ return Compute(DefaultPolynomial, seed, buffer);
+ }
+
+ public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer)
+ {
+ return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length);
+ }
+
+ static UInt32[] InitializeTable(UInt32 polynomial)
+ {
+ if (polynomial == DefaultPolynomial && defaultTable != null)
+ return defaultTable;
+
+ var createTable = new UInt32[256];
+ for (var i = 0; i < 256; i++)
+ {
+ var entry = (UInt32)i;
+ for (var j = 0; j < 8; j++)
+ if ((entry & 1) == 1)
+ entry = (entry >> 1) ^ polynomial;
+ else
+ entry = entry >> 1;
+ createTable[i] = entry;
+ }
+
+ if (polynomial == DefaultPolynomial)
+ defaultTable = createTable;
+
+ return createTable;
+ }
+
+ static UInt32 CalculateHash(UInt32[] table, UInt32 seed, IList buffer, int start, int size)
+ {
+ var hash = seed;
+ for (var i = start; i < start + size; i++)
+ hash = (hash >> 8) ^ table[buffer[i] ^ hash & 0xff];
+ return hash;
+ }
+
+ static byte[] UInt32ToBigEndianBytes(UInt32 uint32)
+ {
+ var result = BitConverter.GetBytes(uint32);
+
+ if (BitConverter.IsLittleEndian)
+ Array.Reverse(result);
+
+ return result;
+ }
+ }
+}
diff --git a/Mhf.Server/Common/CryptoRandom.cs b/Mhf.Server/Common/CryptoRandom.cs
new file mode 100644
index 0000000..342a944
--- /dev/null
+++ b/Mhf.Server/Common/CryptoRandom.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Security.Cryptography;
+
+namespace Mhf.Server.Common
+{
+/*
+ * Original version by Stephen Toub and Shawn Farkas.
+ * Random pool and thread safety added by Markus Olsson (freakcode.com).
+ *
+ * Original source: http://msdn.microsoft.com/en-us/magazine/cc163367.aspx
+ *
+ * Some benchmarks (2009-03-18):
+ *
+ * Results produced by calling Next() 1 000 000 times on my machine (dual core 3Ghz)
+ *
+ * System.Random completed in 20.4993 ms (avg 0 ms) (first: 0.3454 ms)
+ * CryptoRandom with pool completed in 132.2408 ms (avg 0.0001 ms) (first: 0.025 ms)
+ * CryptoRandom without pool completed in 2 sec 587.708 ms (avg 0.0025 ms) (first: 1.4142 ms)
+ *
+ * |---------------------|------------------------------------|
+ * | Implementation | Slowdown compared to System.Random |
+ * |---------------------|------------------------------------|
+ * | System.Random | 0 |
+ * | CryptoRand w pool | 6,6x |
+ * | CryptoRand w/o pool | 19,5x |
+ * |---------------------|------------------------------------|
+ *
+ * ent (http://www.fourmilab.ch/) results for 16mb of data produced by this class:
+ *
+ * > Entropy = 7.999989 bits per byte.
+ * >
+ * > Optimum compression would reduce the size of this 16777216 byte file by 0 percent.
+ * >
+ * > Chi square distribution for 16777216 samples is 260.64,
+ * > and randomly would exceed this value 50.00 percent of the times.
+ * >
+ * > Arithmetic mean value of data bytes is 127.4974 (127.5 = random).
+ * > Monte Carlo value for Pi is 3.141838823 (error 0.01 percent).
+ * > Serial correlation coefficient is 0.000348 (totally uncorrelated = 0.0).
+ *
+ * your mileage may vary ;)
+ *
+ */
+
+ ///
+ /// A random number generator based on the RNGCryptoServiceProvider.
+ /// Adapted from the "Tales from the CryptoRandom" article in MSDN Magazine (September 2007)
+ /// but with explicit guarantee to be thread safe. Note that this implementation also includes
+ /// an optional (enabled by default) random buffer which provides a significant speed boost as
+ /// it greatly reduces the amount of calls into unmanaged land.
+ ///
+ public class CryptoRandom : Random
+ {
+ private RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider();
+
+ private byte[] _buffer;
+
+ private int _bufferPosition;
+
+ ///
+ /// Gets a value indicating whether this instance has random pool enabled.
+ ///
+ ///
+ /// true if this instance has random pool enabled; otherwise, false .
+ ///
+ public bool IsRandomPoolEnabled { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class with.
+ /// Using this overload will enable the random buffer pool.
+ ///
+ public CryptoRandom() : this(true)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// This method will disregard whatever value is passed as seed and it's only implemented
+ /// in order to be fully backwards compatible with .
+ /// Using this overload will enable the random buffer pool.
+ ///
+ /// The ignored seed.
+ [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "ignoredSeed",
+ Justification = "Cannot remove this parameter as we implement the full API of System.Random")]
+ public CryptoRandom(int ignoredSeed) : this(true)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with
+ /// optional random buffer.
+ ///
+ /// set to true to enable the random pool buffer for increased performance.
+ public CryptoRandom(bool enableRandomPool)
+ {
+ IsRandomPoolEnabled = enableRandomPool;
+ }
+
+ private void InitBuffer()
+ {
+ if (IsRandomPoolEnabled)
+ {
+ if (_buffer == null || _buffer.Length != 512)
+ _buffer = new byte[512];
+ }
+ else
+ {
+ if (_buffer == null || _buffer.Length != 4)
+ _buffer = new byte[4];
+ }
+
+ _rng.GetBytes(_buffer);
+ _bufferPosition = 0;
+ }
+
+ ///
+ /// Returns a nonnegative random number.
+ ///
+ ///
+ /// A 32-bit signed integer greater than or equal to zero and less than .
+ ///
+ public override int Next()
+ {
+ // Mask away the sign bit so that we always return nonnegative integers
+ return (int) GetRandomUInt32() & 0x7FFFFFFF;
+ }
+
+ ///
+ /// Returns a nonnegative random number less than the specified maximum.
+ ///
+ /// The exclusive upper bound of the random number to be generated. must be greater than or equal to zero.
+ ///
+ /// A 32-bit signed integer greater than or equal to zero, and less than ; that is, the range of return values ordinarily includes zero but not . However, if equals zero, is returned.
+ ///
+ ///
+ /// is less than zero.
+ ///
+ public override int Next(int maxValue)
+ {
+ if (maxValue < 0)
+ throw new ArgumentOutOfRangeException("maxValue");
+
+ return Next(0, maxValue);
+ }
+
+ ///
+ /// Returns a random number within a specified range.
+ ///
+ /// The inclusive lower bound of the random number returned.
+ /// The exclusive upper bound of the random number returned. must be greater than or equal to .
+ ///
+ /// A 32-bit signed integer greater than or equal to and less than ; that is, the range of return values includes but not . If equals , is returned.
+ ///
+ ///
+ /// is greater than .
+ ///
+ public override int Next(int minValue, int maxValue)
+ {
+ if (minValue > maxValue)
+ throw new ArgumentOutOfRangeException("minValue");
+
+ if (minValue == maxValue)
+ return minValue;
+
+ long diff = maxValue - minValue;
+
+ while (true)
+ {
+ uint rand = GetRandomUInt32();
+
+ long max = 1 + (long) uint.MaxValue;
+ long remainder = max % diff;
+
+ if (rand < max - remainder)
+ return (int) (minValue + (rand % diff));
+ }
+ }
+
+ ///
+ /// Returns a random number between 0.0 and 1.0.
+ ///
+ ///
+ /// A double-precision floating point number greater than or equal to 0.0, and less than 1.0.
+ ///
+ public override double NextDouble()
+ {
+ return GetRandomUInt32() / (1.0 + uint.MaxValue);
+ }
+
+ ///
+ /// Fills the elements of a specified array of bytes with random numbers.
+ ///
+ /// An array of bytes to contain random numbers.
+ ///
+ /// is null.
+ ///
+ public override void NextBytes(byte[] buffer)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+
+ lock (this)
+ {
+ if (IsRandomPoolEnabled && _buffer == null)
+ InitBuffer();
+
+ // Can we fit the requested number of bytes in the buffer?
+ if (IsRandomPoolEnabled && _buffer.Length <= buffer.Length)
+ {
+ int count = buffer.Length;
+
+ EnsureRandomBuffer(count);
+
+ Buffer.BlockCopy(_buffer, _bufferPosition, buffer, 0, count);
+
+ _bufferPosition += count;
+ }
+ else
+ {
+ // Draw bytes directly from the RNGCryptoProvider
+ _rng.GetBytes(buffer);
+ }
+ }
+ }
+
+ ///
+ /// Gets one random unsigned 32bit integer in a thread safe manner.
+ ///
+ private uint GetRandomUInt32()
+ {
+ lock (this)
+ {
+ EnsureRandomBuffer(4);
+
+ uint rand = BitConverter.ToUInt32(_buffer, _bufferPosition);
+
+ _bufferPosition += 4;
+
+ return rand;
+ }
+ }
+
+ ///
+ /// Ensures that we have enough bytes in the random buffer.
+ ///
+ /// The number of required bytes.
+ private void EnsureRandomBuffer(int requiredBytes)
+ {
+ if (_buffer == null)
+ InitBuffer();
+
+ if (requiredBytes > _buffer.Length)
+ throw new ArgumentOutOfRangeException("requiredBytes", "cannot be greater than random buffer");
+
+ if ((_buffer.Length - _bufferPosition) < requiredBytes)
+ InitBuffer();
+ }
+ }
+}
diff --git a/Mhf.Server/Common/Instance/IInstance.cs b/Mhf.Server/Common/Instance/IInstance.cs
new file mode 100644
index 0000000..e2fed41
--- /dev/null
+++ b/Mhf.Server/Common/Instance/IInstance.cs
@@ -0,0 +1,7 @@
+namespace Mhf.Server.Common.Instance
+{
+ public interface IInstance
+ {
+ uint InstanceId { get; set; }
+ }
+}
diff --git a/Mhf.Server/Common/Instance/InstanceGenerator.cs b/Mhf.Server/Common/Instance/InstanceGenerator.cs
new file mode 100644
index 0000000..fe25742
--- /dev/null
+++ b/Mhf.Server/Common/Instance/InstanceGenerator.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+
+namespace Mhf.Server.Common.Instance
+{
+ ///
+ /// Provides Unique Ids for instancing.
+ ///
+ public class InstanceGenerator
+ {
+ private readonly object _lock;
+ private uint _currentId;
+
+ private readonly Dictionary _instances;
+
+ public InstanceGenerator()
+ {
+ _lock = new object();
+ _currentId = 0;
+ _instances = new Dictionary();
+ }
+
+ public void AssignInstance(IInstance instance)
+ {
+ uint id;
+ lock (_lock)
+ {
+ id = _currentId;
+ _currentId++;
+ }
+
+ _instances.Add(id, instance);
+ instance.InstanceId = id;
+ }
+
+ public T CreateInstance() where T : IInstance, new()
+ {
+ uint id;
+ lock (_lock)
+ {
+ id = _currentId;
+ _currentId++;
+ }
+
+ T instance = new T();
+ _instances.Add(id, instance);
+ instance.InstanceId = id;
+ return instance;
+ }
+
+ public IInstance GetInstance(uint id)
+ {
+ if (!_instances.ContainsKey(id))
+ {
+ return null;
+ }
+
+ return _instances[id];
+ }
+ }
+}
diff --git a/Mhf.Server/Common/MhfUpDat.cs b/Mhf.Server/Common/MhfUpDat.cs
new file mode 100644
index 0000000..90f49c5
--- /dev/null
+++ b/Mhf.Server/Common/MhfUpDat.cs
@@ -0,0 +1,77 @@
+using System;
+using System.IO;
+using System.Text;
+using Mhf.Server.Common.Crc;
+
+namespace Mhf.Server.Common
+{
+ ///
+ /// Generates MHFUP.DAT file of all files from specified root directory.
+ /// The file has entry ended by a LineFeed (LF / 0x0A)
+ ///
+ public class MhfUpDat
+ {
+ private readonly DirectoryInfo _rootDirectoryInfo;
+
+ public MhfUpDat(string rootDirectoryPath)
+ {
+ _rootDirectoryInfo = new DirectoryInfo(rootDirectoryPath);
+ if (!_rootDirectoryInfo.Exists)
+ {
+ throw new FileNotFoundException("Folder does not exist", rootDirectoryPath);
+ }
+ }
+
+ public string GetUpdateEntry(string filePath)
+ {
+ return GetUpdateEntry(new FileInfo(filePath));
+ }
+
+ public string GetUpdateEntry(FileInfo fileInfo)
+ {
+ if (!fileInfo.Exists)
+ {
+ return null;
+ }
+
+ fileInfo.Refresh();
+ byte[] file = Util.ReadFile(fileInfo.FullName);
+ uint crc32 = Crc32.Compute(file);
+ DateTime lastWriteTime = fileInfo.LastWriteTime;
+ long lastWriteFileTime = lastWriteTime.ToFileTime();
+ string lastWriteFileTimeHex = $"{lastWriteFileTime:X16}";
+ string fileTimeHex1 = lastWriteFileTimeHex.Substring(8);
+ string fileTimeHex2 = lastWriteFileTimeHex.Substring(0, 8);
+ return $"{crc32:X8},{fileTimeHex1},{fileTimeHex2},{GetFileNameEntry(fileInfo)},{file.Length},0";
+ }
+
+ public string CreateMhfUpDat()
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (FileInfo fileInfo in _rootDirectoryInfo.GetFiles("*", SearchOption.AllDirectories))
+ {
+ sb.Append($"{GetUpdateEntry(fileInfo)}/n");
+ }
+
+ return sb.ToString();
+ }
+
+ public void SaveMhfUpDat(string destinationFilePath)
+ {
+ string mhfUpDat = CreateMhfUpDat();
+ Util.WriteFile(Encoding.UTF8.GetBytes(mhfUpDat), destinationFilePath);
+ }
+
+ private string GetFileNameEntry(FileInfo fileInfo)
+ {
+ string relativeDirectory = Util.RelativeDirectory(_rootDirectoryInfo.FullName, fileInfo.DirectoryName);
+ if (string.IsNullOrEmpty(relativeDirectory))
+ {
+ return $"exe\\{fileInfo.Name}";
+ }
+
+ string path = Path.Combine(relativeDirectory, fileInfo.Name);
+ return path;
+ }
+ }
+}
diff --git a/Mhf.Server/Common/Middleware/IMiddleware.cs b/Mhf.Server/Common/Middleware/IMiddleware.cs
new file mode 100644
index 0000000..8d0b13d
--- /dev/null
+++ b/Mhf.Server/Common/Middleware/IMiddleware.cs
@@ -0,0 +1,7 @@
+namespace Mhf.Server.Common.Middleware
+{
+ public interface IMiddleware
+ {
+ void Handle(T user, TReq request, TRes response, MiddlewareDelegate next);
+ }
+}
diff --git a/Mhf.Server/Common/Middleware/Middleware.cs b/Mhf.Server/Common/Middleware/Middleware.cs
new file mode 100644
index 0000000..8f66cdf
--- /dev/null
+++ b/Mhf.Server/Common/Middleware/Middleware.cs
@@ -0,0 +1,16 @@
+using Arrowgene.Services.Logging;
+
+namespace Mhf.Server.Common.Middleware
+{
+ public abstract class Middleware : IMiddleware
+ {
+ protected Middleware()
+ {
+ Logger = LogProvider.Logger(this);
+ }
+
+ protected ILogger Logger { get; }
+
+ public abstract void Handle(T client, TReq message, TRes response, MiddlewareDelegate next);
+ }
+}
diff --git a/Mhf.Server/Common/Middleware/MiddlewareDelegate.cs b/Mhf.Server/Common/Middleware/MiddlewareDelegate.cs
new file mode 100644
index 0000000..036876a
--- /dev/null
+++ b/Mhf.Server/Common/Middleware/MiddlewareDelegate.cs
@@ -0,0 +1,4 @@
+namespace Mhf.Server.Common.Middleware
+{
+ public delegate void MiddlewareDelegate(T user, TReq request, TRes response);
+}
diff --git a/Mhf.Server/Common/Middleware/MiddlewareStack.cs b/Mhf.Server/Common/Middleware/MiddlewareStack.cs
new file mode 100644
index 0000000..70552af
--- /dev/null
+++ b/Mhf.Server/Common/Middleware/MiddlewareStack.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Mhf.Server.Common.Middleware
+{
+ ///
+ /// Implementation of a middleware
+ ///
+ public class MiddlewareStack
+ {
+ private MiddlewareDelegate _middlewareDelegate;
+
+ public MiddlewareStack(MiddlewareDelegate kernel)
+ {
+ _middlewareDelegate = kernel;
+ }
+
+ public void Start(T user, TReq request, TRes response)
+ {
+ _middlewareDelegate(user, request, response);
+ }
+
+ public MiddlewareStack Use(
+ Func, MiddlewareDelegate> middleware)
+ {
+ _middlewareDelegate = middleware(_middlewareDelegate);
+ return this;
+ }
+
+ public MiddlewareStack Use(IMiddleware middleware)
+ {
+ return Use(next => (user, request, response) => middleware.Handle(user, request, response, next));
+ }
+ }
+}
diff --git a/Mhf.Server/Common/Util.cs b/Mhf.Server/Common/Util.cs
new file mode 100644
index 0000000..653ba77
--- /dev/null
+++ b/Mhf.Server/Common/Util.cs
@@ -0,0 +1,695 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mhf.Server.Common
+{
+ public class Util
+ {
+ public static readonly CryptoRandom Random = new CryptoRandom();
+
+ private static readonly Random RandomNum = new Random();
+
+ public static int GetRandomNumber(int min, int max)
+ {
+ lock (RandomNum)
+ {
+ return RandomNum.Next(min, max);
+ }
+ }
+
+ public static long GetUnixTime(DateTime dateTime)
+ {
+ return ((DateTimeOffset) dateTime).ToUnixTimeSeconds();
+ }
+
+ public static string PathDifferenceEnd(string directoryInfo1, string directoryInfo2, bool unRoot)
+ {
+ return PathDifference(new DirectoryInfo(directoryInfo1), new DirectoryInfo(directoryInfo2), unRoot);
+ }
+
+ public static string PathDifferenceEnd(FileSystemInfo directoryInfo1, FileSystemInfo directoryInfo2,
+ bool unRoot)
+ {
+ string result;
+ if (directoryInfo1.FullName == directoryInfo2.FullName)
+ {
+ result = directoryInfo1.FullName;
+ }
+ else if (directoryInfo1.FullName.EndsWith(directoryInfo2.FullName))
+ {
+ result = directoryInfo1.FullName.Split(new[] {directoryInfo2.FullName},
+ StringSplitOptions.RemoveEmptyEntries)[0];
+ }
+ else if (directoryInfo2.FullName.EndsWith(directoryInfo1.FullName))
+ {
+ result = directoryInfo2.FullName.Split(new[] {directoryInfo1.FullName},
+ StringSplitOptions.RemoveEmptyEntries)[0];
+ }
+ else
+ {
+ result = "";
+ }
+
+ if (unRoot)
+ {
+ result = UnRootPath(result);
+ }
+
+ return result;
+ }
+
+
+ public static string PathDifference(string directoryInfo1, string directoryInfo2, bool unRoot)
+ {
+ return PathDifference(new DirectoryInfo(directoryInfo1), new DirectoryInfo(directoryInfo2), unRoot);
+ }
+
+ public static string PathDifference(FileSystemInfo directoryInfo1, FileSystemInfo directoryInfo2, bool unRoot)
+ {
+ string result;
+ if (directoryInfo1.FullName == directoryInfo2.FullName)
+ {
+ result = "";
+ }
+ else if (directoryInfo1.FullName.StartsWith(directoryInfo2.FullName))
+ {
+ result = directoryInfo1.FullName.Split(new[] {directoryInfo2.FullName},
+ StringSplitOptions.RemoveEmptyEntries)[0];
+ }
+ else if (directoryInfo2.FullName.StartsWith(directoryInfo1.FullName))
+ {
+ result = directoryInfo2.FullName.Split(new[] {directoryInfo1.FullName},
+ StringSplitOptions.RemoveEmptyEntries)[0];
+ }
+ else
+ {
+ result = "";
+ }
+
+ if (unRoot)
+ {
+ result = UnRootPath(result);
+ }
+
+ return result;
+ }
+
+ public static string UnRootPath(string path)
+ {
+ // https://stackoverflow.com/questions/53102/why-does-path-combine-not-properly-concatenate-filenames-that-start-with-path-di
+ if (Path.IsPathRooted(path))
+ {
+ path = path.TrimStart(Path.DirectorySeparatorChar);
+ path = path.TrimStart(Path.AltDirectorySeparatorChar);
+ }
+
+ return path;
+ }
+
+ public static AssemblyName GetAssemblyName(string name)
+ {
+ Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
+ foreach (var assembly in assemblies)
+ {
+ AssemblyName assemblyName = assembly.GetName();
+ if (assemblyName.Name == name)
+ {
+ return assemblyName;
+ }
+ }
+
+ return null;
+ }
+
+ public static Version GetAssemblyVersion(string name)
+ {
+ AssemblyName assemblyName = GetAssemblyName(name);
+ if (assemblyName != null)
+ {
+ return assemblyName.Version;
+ }
+
+ return null;
+ }
+
+ public static string GetAssemblyVersionString(string name)
+ {
+ Version version = GetAssemblyVersion(name);
+ if (version != null)
+ {
+ return version.ToString();
+ }
+
+ return null;
+ }
+
+ public static byte[] ReadFile(string source)
+ {
+ if (!File.Exists(source))
+ {
+ throw new Exception(string.Format("'{0}' does not exist or is not a file", source));
+ }
+
+ return File.ReadAllBytes(source);
+ }
+
+ public static string ReadFileText(string source)
+ {
+ if (!File.Exists(source))
+ {
+ throw new Exception($"'{source}' does not exist or is not a file");
+ }
+
+ return File.ReadAllText(source);
+ }
+
+ public static void WriteFile(byte[] content, string destination)
+ {
+ if (content != null)
+ {
+ File.WriteAllBytes(destination, content);
+ }
+ else
+ {
+ throw new Exception($"Content of '{destination}' is null");
+ }
+ }
+
+ public static List GetFiles(DirectoryInfo directoryInfo, string[] extensions, bool recursive)
+ {
+ if (recursive)
+ {
+ List filteredFiles = GetFiles(directoryInfo, extensions);
+ DirectoryInfo[] directoryInfos = directoryInfo.GetDirectories("*", SearchOption.TopDirectoryOnly);
+ foreach (DirectoryInfo dInfo in directoryInfos)
+ {
+ List files = GetFiles(dInfo, extensions, true);
+ filteredFiles.AddRange(files);
+ }
+
+ return filteredFiles;
+ }
+
+ return GetFiles(directoryInfo, extensions);
+ }
+
+ public static List GetFiles(DirectoryInfo directoryInfo, string[] extensions)
+ {
+ List filteredFiles = new List();
+ FileInfo[] files = directoryInfo.GetFiles("*.*", SearchOption.TopDirectoryOnly);
+ foreach (FileInfo file in files)
+ {
+ if (extensions != null)
+ {
+ foreach (string extension in extensions)
+ {
+ if (file.Extension.EndsWith(extension, StringComparison.OrdinalIgnoreCase))
+ {
+ filteredFiles.Add(file);
+ break;
+ }
+ }
+ }
+ else
+ {
+ filteredFiles.Add(file);
+ }
+ }
+
+ return filteredFiles;
+ }
+
+ public static List GetFolders(DirectoryInfo directoryInfo, string[] extensions, bool recursive)
+ {
+ if (recursive)
+ {
+ List result = new List();
+ List filteredDirectories = GetFolders(directoryInfo, extensions);
+ result.AddRange(filteredDirectories);
+ foreach (DirectoryInfo directory in filteredDirectories)
+ {
+ List directories = GetFolders(directory, extensions, true);
+ result.AddRange(directories);
+ }
+
+ return result;
+ }
+
+ return GetFolders(directoryInfo, extensions);
+ }
+
+ public static List GetFolders(DirectoryInfo directoryInfo, string[] extensions)
+ {
+ List filteredDirectories = new List();
+ DirectoryInfo[] directories = directoryInfo.GetDirectories("*", SearchOption.TopDirectoryOnly);
+ foreach (DirectoryInfo directory in directories)
+ {
+ if (extensions != null)
+ {
+ foreach (string extension in extensions)
+ {
+ if (directory.Name.EndsWith(extension, StringComparison.OrdinalIgnoreCase))
+ {
+ filteredDirectories.Add(directory);
+ break;
+ }
+ }
+ }
+ else
+ {
+ filteredDirectories.Add(directory);
+ }
+ }
+
+ return filteredDirectories;
+ }
+
+
+ public static DirectoryInfo EnsureDirectory(string directory)
+ {
+ return Directory.CreateDirectory(directory);
+ }
+
+ ///
+ /// The directory of the executing assembly.
+ /// This might not be the location where the .dll files are located.
+ ///
+ ///
+ public static string ExecutingDirectory()
+ {
+ string path = Assembly.GetEntryAssembly().CodeBase;
+ Uri uri = new Uri(path);
+ string directory = Path.GetDirectoryName(uri.LocalPath);
+ return directory;
+ }
+
+ ///
+ /// The relative directory of the executing assembly.
+ /// This might not be the location where the .dll files are located.
+ ///
+ public static string RelativeExecutingDirectory()
+ {
+ return RelativeDirectory(Environment.CurrentDirectory, ExecutingDirectory());
+ }
+
+ ///
+ /// Directory of Common.dll
+ /// This is expected to contain ressource files.
+ ///
+ public static string CommonDirectory()
+ {
+ string location = typeof(Util).GetTypeInfo().Assembly.Location;
+ Uri uri = new Uri(location);
+ string directory = Path.GetDirectoryName(uri.LocalPath);
+ return directory;
+ }
+
+ ///
+ /// Relative Directory of Common.dll.
+ /// This is expected to contain ressource files.
+ ///
+ public static string RelativeCommonDirectory()
+ {
+ return RelativeDirectory(Environment.CurrentDirectory, CommonDirectory());
+ }
+
+ public static string CreateMd5(string input)
+ {
+ MD5 md5 = MD5.Create();
+ byte[] inputBytes = Encoding.ASCII.GetBytes(input);
+ byte[] hashBytes = md5.ComputeHash(inputBytes);
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < hashBytes.Length; i++)
+ {
+ sb.Append(hashBytes[i].ToString("X2"));
+ }
+
+ return sb.ToString().ToLower();
+ }
+
+ public static string RelativeDirectory(string fromDirectory, string toDirectory)
+ {
+ return RelativeDirectory(fromDirectory, toDirectory, toDirectory, Path.DirectorySeparatorChar);
+ }
+
+ public static string RelativeDirectory(string fromDirectory, string toDirectory, string defaultDirectory)
+ {
+ return RelativeDirectory(fromDirectory, toDirectory, defaultDirectory, Path.DirectorySeparatorChar);
+ }
+
+ ///
+ /// Returns a directory that is relative.
+ ///
+ /// The directory to navigate from.
+ /// The directory to reach.
+ /// A directory to return on failure.
+ ///
+ /// The relative directory or the defaultDirectory on failure.
+ public static string RelativeDirectory(string fromDirectory, string toDirectory, string defaultDirectory,
+ char directorySeparator)
+ {
+ string result;
+
+ if (fromDirectory.EndsWith("\\") || fromDirectory.EndsWith("/"))
+ {
+ fromDirectory = fromDirectory.Remove(fromDirectory.Length - 1);
+ }
+
+ if (toDirectory.EndsWith("\\") || toDirectory.EndsWith("/"))
+ {
+ toDirectory = toDirectory.Remove(toDirectory.Length - 1);
+ }
+
+ if (toDirectory.StartsWith(fromDirectory))
+ {
+ result = toDirectory.Substring(fromDirectory.Length);
+ if (result.StartsWith("\\") || result.StartsWith("/"))
+ {
+ result = result.Substring(1, result.Length - 1);
+ }
+
+ if (result != "")
+ {
+ result += directorySeparator;
+ }
+ }
+ else
+ {
+ string[] fromDirs = fromDirectory.Split(':', '\\', '/');
+ string[] toDirs = toDirectory.Split(':', '\\', '/');
+ if (fromDirs.Length <= 0 || toDirs.Length <= 0 || fromDirs[0] != toDirs[0])
+ {
+ return defaultDirectory;
+ }
+
+ int offset = 1;
+ for (; offset < fromDirs.Length; offset++)
+ {
+ if (toDirs.Length <= offset)
+ {
+ break;
+ }
+
+ if (fromDirs[offset] != toDirs[offset])
+ {
+ break;
+ }
+ }
+
+ StringBuilder relativeBuilder = new StringBuilder();
+ for (int i = 0; i < fromDirs.Length - offset; i++)
+ {
+ relativeBuilder.Append("..");
+ relativeBuilder.Append(directorySeparator);
+ }
+
+ for (int i = offset; i < toDirs.Length - 1; i++)
+ {
+ relativeBuilder.Append(toDirs[i]);
+ relativeBuilder.Append(directorySeparator);
+ }
+
+ result = relativeBuilder.ToString();
+ }
+
+ result = DirectorySeparator(result, directorySeparator);
+ return result;
+ }
+
+ public static string DirectorySeparator(string path)
+ {
+ return DirectorySeparator(path, Path.DirectorySeparatorChar);
+ }
+
+ public static string DirectorySeparator(string path, char directorySeparator)
+ {
+ if (directorySeparator != '\\')
+ {
+ path = path.Replace('\\', directorySeparator);
+ }
+
+ if (directorySeparator != '/')
+ {
+ path = path.Replace('/', directorySeparator);
+ }
+
+ return path;
+ }
+
+ public static string GenerateSessionKey(int desiredLength)
+ {
+ StringBuilder sessionKey = new StringBuilder();
+ using (RNGCryptoServiceProvider cryptoProvider = new RNGCryptoServiceProvider())
+ {
+ byte[] random = new byte[1];
+ int length = 0;
+ while (length < desiredLength)
+ {
+ cryptoProvider.GetBytes(random);
+ char c = (char) random[0];
+ if ((Char.IsDigit(c) || Char.IsLetter(c)) && random[0] < 127)
+ {
+ length++;
+ sessionKey.Append(c);
+ }
+ }
+ }
+
+ return sessionKey.ToString();
+ }
+
+ public static byte[] GenerateKey(int desiredLength)
+ {
+ byte[] random = new byte[desiredLength];
+ using (RNGCryptoServiceProvider cryptoProvider = new RNGCryptoServiceProvider())
+ {
+ cryptoProvider.GetNonZeroBytes(random);
+ }
+
+ return random;
+ }
+
+ ///
+ /// Removes entries from a collection.
+ /// The input lists are not modified, instead a new collection is returned.
+ ///
+ public static TList SubtractList(TList entries, params TItem[] excepts)
+ where TList : ICollection, new()
+ {
+ TList result = new TList();
+ foreach (TItem entry in entries)
+ {
+ result.Add(entry);
+ }
+
+ foreach (TItem except in excepts)
+ {
+ result.Remove(except);
+ }
+
+ return result;
+ }
+
+ public static byte[] FromHexString(string hexString)
+ {
+ if ((hexString.Length & 1) != 0)
+ {
+ throw new ArgumentException("Input must have even number of characters");
+ }
+ byte[] ret = new byte[hexString.Length/2];
+ for (int i = 0; i < ret.Length; i++)
+ {
+ int high = hexString[i*2];
+ int low = hexString[i*2+1];
+ high = (high & 0xf) + ((high & 0x40) >> 6) * 9;
+ low = (low & 0xf) + ((low & 0x40) >> 6) * 9;
+
+ ret[i] = (byte)((high << 4) | low);
+ }
+
+ return ret;
+ }
+
+ public static string ToHexString(byte[] data, char? seperator = null)
+ {
+ StringBuilder sb = new StringBuilder();
+ int len = data.Length;
+ for (int i = 0; i < len; i++)
+ {
+ sb.Append(data[i].ToString("X2"));
+ if (seperator != null && i < len - 1)
+ {
+ sb.Append(seperator);
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ public static string ToAsciiString(byte[] data, bool spaced)
+ {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < data.Length; i++)
+ {
+ char c = '.';
+ if (data[i] >= 'A' && data[i] <= 'Z') c = (char) data[i];
+ if (data[i] >= 'a' && data[i] <= 'z') c = (char) data[i];
+ if (data[i] >= '0' && data[i] <= '9') c = (char) data[i];
+ if (spaced && i != 0)
+ {
+ sb.Append(" ");
+ }
+
+ sb.Append(c);
+ }
+
+ return sb.ToString();
+ }
+
+ public static string[] ParseTextArguments(string line, char delimiter, char textQualifier)
+ {
+ IList list = ParseTextList(line, delimiter, textQualifier);
+ int count = list.Count;
+ string[] arguments = new string[count];
+ for (int i = 0; i < count; i++)
+ {
+ arguments[i] = list[i];
+ }
+
+ return arguments;
+ }
+
+ public static IEnumerable ParseTextEnumerable(string line, char delimiter, char textQualifier)
+ {
+ if (string.IsNullOrWhiteSpace(line))
+ yield break;
+
+ char prevChar = '\0';
+ char nextChar = '\0';
+ char currentChar = '\0';
+ bool inString = false;
+
+ StringBuilder token = new StringBuilder();
+
+ for (int i = 0; i < line.Length; i++)
+ {
+ currentChar = line[i];
+
+ if (i > 0)
+ prevChar = line[i - 1];
+ else
+ prevChar = '\0';
+
+ if (i + 1 < line.Length)
+ nextChar = line[i + 1];
+ else
+ nextChar = '\0';
+
+ if (currentChar == textQualifier && (prevChar == '\0' || prevChar == delimiter) && !inString)
+ {
+ inString = true;
+ continue;
+ }
+
+ if (currentChar == textQualifier && (nextChar == '\0' || nextChar == delimiter) && inString)
+ {
+ inString = false;
+ continue;
+ }
+
+ if (currentChar == delimiter && !inString)
+ {
+ yield return token.ToString();
+ token = token.Remove(0, token.Length);
+ continue;
+ }
+
+ token = token.Append(currentChar);
+ yield return token.ToString();
+ }
+ }
+
+ public static IList ParseTextList(string line, char delimiter, char textQualifier)
+ {
+ IList collection = new List();
+ if (string.IsNullOrWhiteSpace(line))
+ return collection;
+
+ char prevChar = '\0';
+ char nextChar = '\0';
+ char currentChar = '\0';
+ bool inString = false;
+
+ StringBuilder token = new StringBuilder();
+
+ for (int i = 0; i < line.Length; i++)
+ {
+ currentChar = line[i];
+
+ if (i > 0)
+ prevChar = line[i - 1];
+ else
+ prevChar = '\0';
+
+ if (i + 1 < line.Length)
+ nextChar = line[i + 1];
+ else
+ nextChar = '\0';
+
+ if (currentChar == textQualifier && (prevChar == '\0' || prevChar == delimiter) && !inString)
+ {
+ inString = true;
+ continue;
+ }
+
+ if (currentChar == textQualifier && (nextChar == '\0' || nextChar == delimiter) && inString)
+ {
+ inString = false;
+ continue;
+ }
+
+ if (currentChar == delimiter && !inString)
+ {
+ collection.Add(token.ToString());
+ token = token.Remove(0, token.Length);
+ continue;
+ }
+
+ token = token.Append(currentChar);
+ }
+
+ collection.Add(token.ToString());
+ return collection;
+ }
+
+ ///
+ /// Read a stream till the end and return the read bytes.
+ ///
+ public static async Task ReadAsync(Stream stream)
+ {
+ int bufferSize = 1024;
+ byte[] buffer = new byte[bufferSize];
+ byte[] result = new byte[0];
+ int offset = 0;
+ int read = 0;
+ while ((read = await stream.ReadAsync(buffer, 0, bufferSize)) > 0)
+ {
+ int newSize = offset + read;
+ byte[] temp = new byte[newSize];
+ Buffer.BlockCopy(result, 0, temp, 0, offset);
+ Buffer.BlockCopy(buffer, 0, temp, offset, read);
+ result = temp;
+ offset += read;
+ }
+
+ return result;
+ }
+
+ }
+}
diff --git a/Mhf.Server/Database/IDatabase.cs b/Mhf.Server/Database/IDatabase.cs
new file mode 100644
index 0000000..1b0993a
--- /dev/null
+++ b/Mhf.Server/Database/IDatabase.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using Mhf.Server.Model;
+
+namespace Mhf.Server.Database
+{
+ public interface IDatabase
+ {
+ void Execute(string sql);
+
+ ///
+ /// Return true if database was created, or false if not.
+ ///
+ bool CreateDatabase();
+
+ // Account
+ Account CreateAccount(string name, string mail, string hash);
+ Account SelectAccountById(int accountId);
+ Account SelectAccountByName(string accountName);
+ bool UpdateAccount(Account account);
+ bool DeleteAccount(int accountId);
+ }
+}
diff --git a/Mhf.Server/Database/MhfDatabaseBuilder.cs b/Mhf.Server/Database/MhfDatabaseBuilder.cs
new file mode 100644
index 0000000..a121ecd
--- /dev/null
+++ b/Mhf.Server/Database/MhfDatabaseBuilder.cs
@@ -0,0 +1,52 @@
+using System;
+using System.IO;
+using Arrowgene.Services.Logging;
+using Mhf.Server.Database.Sql;
+using Mhf.Server.Model;
+using Mhf.Server.Setting;
+
+namespace Mhf.Server.Database
+{
+ public class MhfDatabaseBuilder
+ {
+ private readonly ILogger _logger;
+
+ public MhfDatabaseBuilder()
+ {
+ _logger = LogProvider.Logger(this);
+ }
+
+ public IDatabase Build(DatabaseSetting settings)
+ {
+ IDatabase database = null;
+ switch (settings.Type)
+ {
+ case DatabaseType.SQLite:
+ database = PrepareSqlLiteDb(settings.SqLiteFolder);
+ break;
+ }
+
+ if (database == null)
+ {
+ _logger.Error("Database could not be created, exiting...");
+ Environment.Exit(1);
+ }
+
+ return database;
+ }
+
+ private MhfSqLiteDb PrepareSqlLiteDb(string sqLiteFolder)
+ {
+ string sqLitePath = Path.Combine(sqLiteFolder, $"db.v{MhfSqLiteDb.Version}.sqlite");
+ MhfSqLiteDb db = new MhfSqLiteDb(sqLitePath);
+ if (db.CreateDatabase())
+ {
+ ScriptRunner scriptRunner = new ScriptRunner(db);
+ scriptRunner.Run(Path.Combine(sqLiteFolder, "Script/schema_sqlite.sql"));
+ scriptRunner.Run(Path.Combine(sqLiteFolder, "Script/data_account.sql"));
+ }
+
+ return db;
+ }
+ }
+}
diff --git a/Mhf.Server/Database/Sql/Core/MhfSqlDb.cs b/Mhf.Server/Database/Sql/Core/MhfSqlDb.cs
new file mode 100644
index 0000000..e041560
--- /dev/null
+++ b/Mhf.Server/Database/Sql/Core/MhfSqlDb.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Data.Common;
+using Arrowgene.Services.Logging;
+using Mhf.Server.Logging;
+
+namespace Mhf.Server.Database.Sql.Core
+{
+ ///
+ /// Implementation of Mhf database operations.
+ ///
+ public abstract partial class MhfSqlDb : SqlDb
+ where TCon : DbConnection
+ where TCom : DbCommand
+ {
+ protected readonly MhfLogger Logger;
+
+
+ public MhfSqlDb()
+ {
+ Logger = LogProvider.Logger(this);
+ }
+
+ protected override void Exception(Exception ex)
+ {
+ Logger.Exception(ex);
+ }
+
+ }
+}
diff --git a/Mhf.Server/Database/Sql/Core/MhfSqlDbAccount.cs b/Mhf.Server/Database/Sql/Core/MhfSqlDbAccount.cs
new file mode 100644
index 0000000..b50d9ff
--- /dev/null
+++ b/Mhf.Server/Database/Sql/Core/MhfSqlDbAccount.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Data.Common;
+using Mhf.Server.Model;
+
+namespace Mhf.Server.Database.Sql.Core
+{
+ public abstract partial class MhfSqlDb : SqlDb
+ where TCon : DbConnection
+ where TCom : DbCommand
+ {
+ private const string SqlInsertAccount =
+ "INSERT INTO `account` (`name`, `normal_name`, `hash`, `mail`, `mail_verified`, `mail_verified_at`, `mail_token`, `password_token`, `state`, `last_login`, `created`) VALUES (@name, @normal_name, @hash, @mail, @mail_verified, @mail_verified_at, @mail_token, @password_token, @state, @last_login, @created);";
+
+ private const string SqlSelectAccountById =
+ "SELECT `id`, `name`, `normal_name`, `hash`, `mail`, `mail_verified`, `mail_verified_at`, `mail_token`, `password_token`, `state`, `last_login`, `created` FROM `account` WHERE `id`=@id;";
+
+ private const string SqlSelectAccountByName =
+ "SELECT `id`, `name`, `normal_name`, `hash`, `mail`, `mail_verified`, `mail_verified_at`, `mail_token`, `password_token`, `state`, `last_login`, `created` FROM `account` WHERE `name`=@name;";
+
+ private const string SqlUpdateAccount =
+ "UPDATE `account` SET `name`=@name, `normal_name`=@normal_name, `hash`=@hash, `mail`=@mail, `mail_verified`=@mail_verified, `mail_verified_at`=@mail_verified_at, `mail_token`=@mail_token, `password_token`=@password_token, `state`=@state, `last_login`=@last_login, `created`=@created WHERE `id`=@id;";
+
+ private const string SqlDeleteAccount =
+ "DELETE FROM `account` WHERE `id`=@id;";
+
+ public Account CreateAccount(string name, string mail, string hash)
+ {
+ Account account = new Account();
+ account.Name = name;
+ account.NormalName = name.ToLowerInvariant();
+ account.Mail = mail;
+ account.Hash = hash;
+ account.State = AccountStateType.User;
+ account.Created = DateTime.Now;
+ int rowsAffected = ExecuteNonQuery(SqlInsertAccount, command =>
+ {
+ AddParameter(command, "@name", account.Name);
+ AddParameter(command, "@normal_name", account.NormalName);
+ AddParameter(command, "@hash", account.Hash);
+ AddParameter(command, "@mail", account.Mail);
+ AddParameter(command, "@mail_verified", account.MailVerified);
+ AddParameter(command, "@mail_verified_at", account.MailVerifiedAt);
+ AddParameter(command, "@mail_token", account.MailToken);
+ AddParameter(command, "@password_token", account.PasswordToken);
+ AddParameterEnumInt32(command, "@state", account.State);
+ AddParameter(command, "@last_login", account.LastLogin);
+ AddParameter(command, "@created", account.Created);
+ }, out long autoIncrement);
+ if (rowsAffected <= NoRowsAffected || autoIncrement <= NoAutoIncrement)
+ {
+ return null;
+ }
+
+ account.Id = (int) autoIncrement;
+ return account;
+ }
+
+ public Account SelectAccountByName(string accountName)
+ {
+ Account account = null;
+ ExecuteReader(SqlSelectAccountByName,
+ command => { AddParameter(command, "@name", accountName); }, reader =>
+ {
+ if (reader.Read())
+ {
+ account = ReadAccount(reader);
+ }
+ });
+
+ return account;
+ }
+
+ public Account SelectAccountById(int accountId)
+ {
+ Account account = null;
+ ExecuteReader(SqlSelectAccountById, command => { AddParameter(command, "@id", accountId); }, reader =>
+ {
+ if (reader.Read())
+ {
+ account = ReadAccount(reader);
+ }
+ });
+ return account;
+ }
+
+ public bool UpdateAccount(Account account)
+ {
+ int rowsAffected = ExecuteNonQuery(SqlUpdateAccount, command =>
+ {
+ AddParameter(command, "@name", account.Name);
+ AddParameter(command, "@normal_name", account.NormalName);
+ AddParameter(command, "@hash", account.Hash);
+ AddParameter(command, "@mail", account.Mail);
+ AddParameter(command, "@mail_verified", account.MailVerified);
+ AddParameter(command, "@mail_verified_at", account.MailVerifiedAt);
+ AddParameter(command, "@mail_token", account.MailToken);
+ AddParameter(command, "@password_token", account.PasswordToken);
+ AddParameterEnumInt32(command, "@state", account.State);
+ AddParameter(command, "@last_login", account.LastLogin);
+ AddParameter(command, "@created", account.Created);
+ AddParameter(command, "@id", account.Id);
+ });
+ return rowsAffected > NoRowsAffected;
+ }
+
+ public bool DeleteAccount(int accountId)
+ {
+ int rowsAffected = ExecuteNonQuery(SqlDeleteAccount,
+ command => { AddParameter(command, "@id", accountId); });
+ return rowsAffected > NoRowsAffected;
+ }
+
+ private Account ReadAccount(DbDataReader reader)
+ {
+ Account account = new Account();
+ account.Id = GetInt32(reader, "id");
+ account.Name = GetString(reader, "name");
+ account.NormalName = GetString(reader, "normal_name");
+ account.Hash = GetString(reader, "hash");
+ account.Mail = GetString(reader, "mail");
+ account.MailVerified = GetBoolean(reader, "mail_verified");
+ account.MailVerifiedAt = GetDateTimeNullable(reader, "mail_verified_at");
+ account.MailToken = GetStringNullable(reader, "mail_token");
+ account.PasswordToken = GetStringNullable(reader, "password_token");
+ account.State = (AccountStateType) GetInt32(reader, "state");
+ account.LastLogin = GetDateTimeNullable(reader, "last_login");
+ account.Created = GetDateTime(reader, "created");
+ return account;
+ }
+ }
+}
diff --git a/Mhf.Server/Database/Sql/MhfMariaDb.cs b/Mhf.Server/Database/Sql/MhfMariaDb.cs
new file mode 100644
index 0000000..34c57a9
--- /dev/null
+++ b/Mhf.Server/Database/Sql/MhfMariaDb.cs
@@ -0,0 +1,55 @@
+using System;
+using MySql.Data.MySqlClient;
+using Mhf.Server.Database.Sql.Core;
+
+namespace Mhf.Server.Database.Sql
+{
+ ///
+ /// SQLite Mhf database.
+ ///
+ public class MhfMariaDb : MhfSqlDb, IDatabase
+ {
+ public const string MemoryDatabasePath = ":memory:";
+
+ private const string SelectAutoIncrement = "SELECT last_insert_rowid()";
+
+
+ private string _connectionString;
+
+ public bool CreateDatabase()
+ {
+ throw new NotImplementedException();
+ }
+
+ public MhfMariaDb(string host, short port, string user, string password, string database)
+ {
+ _connectionString = $"host={host};port={port};user id={user};password={password};database={database};";
+ }
+
+ protected override MySqlConnection Connection()
+ {
+ MySqlConnection connection = new MySqlConnection(_connectionString);
+ connection.Open();
+ return connection;
+ }
+
+ protected override MySqlCommand Command(string query, MySqlConnection connection)
+ {
+ MySqlCommand command = connection.CreateCommand();
+ command.CommandText = query;
+ return command;
+ }
+
+ protected override long AutoIncrement(MySqlConnection connection, MySqlCommand command)
+ {
+ return command.LastInsertedId;
+ }
+
+ public override int Upsert(string table, string[] columns, object[] values, string whereColumn,
+ object whereValue,
+ out long autoIncrement)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Mhf.Server/Database/Sql/MhfSqLiteDb.cs b/Mhf.Server/Database/Sql/MhfSqLiteDb.cs
new file mode 100644
index 0000000..167fcbd
--- /dev/null
+++ b/Mhf.Server/Database/Sql/MhfSqLiteDb.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Data.SQLite;
+using System.IO;
+using Mhf.Server.Database.Sql.Core;
+
+namespace Mhf.Server.Database.Sql
+{
+ ///
+ /// SQLite Mhf database.
+ ///
+ public class MhfSqLiteDb : MhfSqlDb, IDatabase
+ {
+ public const string MemoryDatabasePath = ":memory:";
+ public const int Version = 1;
+
+ private const string SelectAutoIncrement = "SELECT last_insert_rowid()";
+
+ private readonly string _databasePath;
+ private string _connectionString;
+
+ public MhfSqLiteDb(string databasePath)
+ {
+ _databasePath = databasePath;
+ Logger.Info($"Database Path: {_databasePath}");
+ }
+
+ public bool CreateDatabase()
+ {
+ if (_databasePath != MemoryDatabasePath && !File.Exists(_databasePath))
+ {
+ FileStream fs = File.Create(_databasePath);
+ fs.Close();
+ fs.Dispose();
+ Logger.Info($"Created new v{Version} database");
+ return true;
+ }
+
+ return false;
+ }
+
+ private string BuildConnectionString(string source)
+ {
+ SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder();
+ builder.DataSource = source;
+ builder.Version = 3;
+ builder.ForeignKeys = true;
+ // Set ADO.NET conformance flag https://system.data.sqlite.org/index.html/info/e36e05e299
+ builder.Flags = builder.Flags & SQLiteConnectionFlags.StrictConformance;
+
+ string connectionString = builder.ToString();
+ Logger.Info($"Connection String: {connectionString}");
+ return connectionString;
+ }
+
+ protected override SQLiteConnection Connection()
+ {
+ if (_connectionString == null)
+ {
+ _connectionString = BuildConnectionString(_databasePath);
+ }
+
+ SQLiteConnection connection = new SQLiteConnection(_connectionString);
+ return connection.OpenAndReturn();
+ }
+
+ protected override SQLiteCommand Command(string query, SQLiteConnection connection)
+ {
+ return new SQLiteCommand(query, connection);
+ }
+
+ ///
+ /// Thread Safe on Connection basis.
+ /// http://www.sqlite.org/c3ref/last_insert_rowid.html
+ ///
+ protected override long AutoIncrement(SQLiteConnection connection, SQLiteCommand command)
+ {
+ return connection.LastInsertRowId;
+ // long autoIncrement = NoAutoIncrement;
+ // ExecuteReader(SelectAutoIncrement, reader =>
+ // {
+ // if (reader.Read())
+ // {
+ // autoIncrement = reader.GetInt32(0);
+ // }
+ // });
+ // return autoIncrement;
+ }
+
+ public override int Upsert(string table, string[] columns, object[] values, string whereColumn,
+ object whereValue,
+ out long autoIncrement)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Mhf.Server/Database/Sql/ScriptRunner.cs b/Mhf.Server/Database/Sql/ScriptRunner.cs
new file mode 100644
index 0000000..096d74d
--- /dev/null
+++ b/Mhf.Server/Database/Sql/ScriptRunner.cs
@@ -0,0 +1,87 @@
+using System;
+using System.IO;
+using System.Text;
+using Arrowgene.Services.Logging;
+
+namespace Mhf.Server.Database.Sql
+{
+ public class ScriptRunner
+ {
+ private const string DEFAULT_DELIMITER = ";";
+
+ private readonly ILogger _logger;
+ private IDatabase _database;
+ private string delimiter = DEFAULT_DELIMITER;
+ private bool fullLineDelimiter = false;
+
+ /**
+ * Default constructor
+ */
+ public ScriptRunner(IDatabase database)
+ {
+ _database = database;
+ _logger = LogProvider.Logger(this);
+ }
+
+ public void Run(string path)
+ {
+ int index = 0;
+ try
+ {
+ string[] file = File.ReadAllLines(path);
+ StringBuilder command = null;
+ for (; index < file.Length; index++)
+ {
+ string line = file[index];
+ if (command == null)
+ {
+ command = new StringBuilder();
+ }
+
+ string trimmedLine = line.Trim();
+
+ if (trimmedLine.Length < 1)
+ {
+ // Do nothing
+ }
+ else if (trimmedLine.StartsWith("//") || trimmedLine.StartsWith("--"))
+ {
+ // Print comment
+ }
+ else if (!fullLineDelimiter && trimmedLine.EndsWith(delimiter)
+ || fullLineDelimiter && trimmedLine == delimiter)
+ {
+ command.Append(
+ line.Substring(0, line.LastIndexOf(delimiter, StringComparison.InvariantCulture)));
+ command.Append(" ");
+ _database.Execute(command.ToString());
+ command = null;
+ }
+ else
+ {
+ command.Append(line);
+ command.Append("\n");
+ }
+ }
+
+ if (command != null)
+ {
+ string cmd = command.ToString();
+ if (string.IsNullOrWhiteSpace(cmd))
+ {
+ //do nothing;
+ }
+ else
+ {
+ _database.Execute(cmd);
+ }
+ }
+ }
+ catch (Exception exception)
+ {
+ _logger.Error($"Sql error at Line: {index}");
+ _logger.Exception(exception);
+ }
+ }
+ }
+}
diff --git a/Mhf.Server/Database/Sql/SqlDb.cs b/Mhf.Server/Database/Sql/SqlDb.cs
new file mode 100644
index 0000000..c5a8685
--- /dev/null
+++ b/Mhf.Server/Database/Sql/SqlDb.cs
@@ -0,0 +1,286 @@
+using System;
+using System.Data;
+using System.Data.Common;
+
+namespace Mhf.Server.Database.Sql
+{
+ ///
+ /// Operations for SQL type databases.
+ ///
+ public abstract class SqlDb
+ where TCon : DbConnection
+ where TCom : DbCommand
+ {
+ public const int NoRowsAffected = 0;
+ public const long NoAutoIncrement = 0;
+
+ public SqlDb()
+ {
+ }
+
+ protected abstract TCon Connection();
+ protected abstract TCom Command(string query, TCon connection);
+ protected abstract long AutoIncrement(TCon connection, TCom command);
+
+ public abstract int Upsert(string table, string[] columns, object[] values, string whereColumn,
+ object whereValue, out long autoIncrement);
+
+ public int ExecuteNonQuery(string query, Action nonQueryAction)
+ {
+ try
+ {
+ using (TCon connection = Connection())
+ {
+ using (TCom command = Command(query, connection))
+ {
+ nonQueryAction(command);
+ int rowsAffected = command.ExecuteNonQuery();
+ return rowsAffected;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Exception(ex);
+ return NoRowsAffected;
+ }
+ }
+
+ public int ExecuteNonQuery(string query, Action nonQueryAction, out long autoIncrement)
+ {
+ try
+ {
+ using (TCon connection = Connection())
+ {
+ using (TCom command = Command(query, connection))
+ {
+ nonQueryAction(command);
+ int rowsAffected = command.ExecuteNonQuery();
+ autoIncrement = AutoIncrement(connection, command);
+ return rowsAffected;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Exception(ex);
+ autoIncrement = NoAutoIncrement;
+ return NoRowsAffected;
+ }
+ }
+
+ public void ExecuteReader(string query, Action nonQueryAction, Action readAction)
+ {
+ try
+ {
+ using (TCon connection = Connection())
+ {
+ using (TCom command = Command(query, connection))
+ {
+ nonQueryAction(command);
+ using (DbDataReader reader = command.ExecuteReader())
+ {
+ readAction(reader);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Exception(ex);
+ }
+ }
+
+ public void ExecuteReader(string query, Action readAction)
+ {
+ try
+ {
+ using (TCon connection = Connection())
+ {
+ using (TCom command = Command(query, connection))
+ {
+ using (DbDataReader reader = command.ExecuteReader())
+ {
+ readAction(reader);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Exception(ex);
+ }
+ }
+
+ public void Execute(string query)
+ {
+ try
+ {
+ using (TCon connection = Connection())
+ {
+ using (TCom command = Command(query, connection))
+ {
+ command.ExecuteNonQuery();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Exception(ex);
+ }
+ }
+
+ public string ServerVersion()
+ {
+ try
+ {
+ using (TCon connection = Connection())
+ {
+ return connection.ServerVersion;
+ }
+ }
+ catch (Exception ex)
+ {
+ Exception(ex);
+ return string.Empty;
+ }
+ }
+
+ protected virtual void Exception(Exception ex)
+ {
+ throw ex;
+ }
+
+ protected DbParameter Parameter(TCom command, string name, object value, DbType type)
+ {
+ DbParameter parameter = command.CreateParameter();
+ parameter.ParameterName = name;
+ parameter.Value = value;
+ parameter.DbType = type;
+ return parameter;
+ }
+
+ protected DbParameter Parameter(TCom command, string name, string value)
+ {
+ return Parameter(command, name, value, DbType.String);
+ }
+
+ protected void AddParameter(TCom command, string name, object value, DbType type)
+ {
+ DbParameter parameter = Parameter(command, name, value, type);
+ command.Parameters.Add(parameter);
+ }
+
+ protected void AddParameter(TCom command, string name, string value)
+ {
+ AddParameter(command, name, value, DbType.String);
+ }
+
+ protected void AddParameter(TCom command, string name, Int32 value)
+ {
+ AddParameter(command, name, value, DbType.Int32);
+ }
+
+ protected void AddParameter(TCom command, string name, float value)
+ {
+ AddParameter(command, name, value, DbType.Double);
+ }
+
+ protected void AddParameter(TCom command, string name, byte value)
+ {
+ AddParameter(command, name, value, DbType.Byte);
+ }
+
+ protected void AddParameter(TCom command, string name, UInt32 value)
+ {
+ AddParameter(command, name, value, DbType.UInt32);
+ }
+
+ protected void AddParameterEnumInt32(TCom command, string name, T value) where T : Enum
+ {
+ AddParameter(command, name, (Int32) (object) value, DbType.Int32);
+ }
+
+ protected void AddParameter(TCom command, string name, DateTime? value)
+ {
+ AddParameter(command, name, value, DbType.DateTime);
+ }
+
+ protected void AddParameter(TCom command, string name, DateTime value)
+ {
+ AddParameter(command, name, value, DbType.DateTime);
+ }
+
+ protected void AddParameter(TCom command, string name, bool value)
+ {
+ AddParameter(command, name, value, DbType.Boolean);
+ }
+
+ protected DateTime? GetDateTimeNullable(DbDataReader reader, int ordinal)
+ {
+ if (reader.IsDBNull(ordinal))
+ {
+ return null;
+ }
+
+ return reader.GetDateTime(ordinal);
+ }
+
+ protected string GetStringNullable(DbDataReader reader, int ordinal)
+ {
+ if (reader.IsDBNull(ordinal))
+ {
+ return null;
+ }
+
+ return reader.GetString(ordinal);
+ }
+
+ protected int GetInt32(DbDataReader reader, string column)
+ {
+ return reader.GetInt32(reader.GetOrdinal(column));
+ }
+
+ protected byte GetByte(DbDataReader reader, string column)
+ {
+ return reader.GetByte(reader.GetOrdinal(column));
+ }
+
+ protected short GetInt16(DbDataReader reader, string column)
+ {
+ return reader.GetInt16(reader.GetOrdinal(column));
+ }
+
+ protected float GetFloat(DbDataReader reader, string column)
+ {
+ return reader.GetFloat(reader.GetOrdinal(column));
+ }
+
+ protected string GetString(DbDataReader reader, string column)
+ {
+ return reader.GetString(reader.GetOrdinal(column));
+ }
+
+ protected bool GetBoolean(DbDataReader reader, string column)
+ {
+ return reader.GetBoolean(reader.GetOrdinal(column));
+ }
+
+ protected DateTime GetDateTime(DbDataReader reader, string column)
+ {
+ return reader.GetDateTime(reader.GetOrdinal(column));
+ }
+
+ protected DateTime? GetDateTimeNullable(DbDataReader reader, string column)
+ {
+ int ordinal = reader.GetOrdinal(column);
+ return GetDateTimeNullable(reader, ordinal);
+ }
+
+ protected string GetStringNullable(DbDataReader reader, string column)
+ {
+ int ordinal = reader.GetOrdinal(column);
+ return GetStringNullable(reader, ordinal);
+ }
+ }
+}
diff --git a/Mhf.Server/Files/Database/Script/data_account.sql b/Mhf.Server/Files/Database/Script/data_account.sql
new file mode 100644
index 0000000..0f2aa36
--- /dev/null
+++ b/Mhf.Server/Files/Database/Script/data_account.sql
@@ -0,0 +1,2 @@
+INSERT INTO `account` (id,name,normal_name,hash,mail,mail_verified,mail_verified_at,mail_token,password_token,state,last_login,created) VALUES
+(1,'admin','admin','$2a$10$q38QrceMiKLxI3rj6zGpHOQTqG61pEbu5wExo6bhV3s5srxbg11Oi','admin',0,NULL,NULL,NULL,1,NULL,'2019-11-09 20:23:33.2347542');
\ No newline at end of file
diff --git a/Mhf.Server/Files/Database/Script/schema_mariadb.sql b/Mhf.Server/Files/Database/Script/schema_mariadb.sql
new file mode 100644
index 0000000..2d28159
--- /dev/null
+++ b/Mhf.Server/Files/Database/Script/schema_mariadb.sql
@@ -0,0 +1,28 @@
+CREATE TABLE IF NOT EXISTS `setting` (
+ `key` VARCHAR(255) NOT NULL,
+ `value` VARCHAR(255) NOT NULL,
+ PRIMARY KEY (`key`)
+)
+ ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+CREATE TABLE IF NOT EXISTS `account` (
+ `id` INT(11) NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(17) NOT NULL,
+ `normal_name` VARCHAR(17) NOT NULL,
+ `hash` VARCHAR(255) NOT NULL,
+ `mail` VARCHAR(255) NOT NULL,
+ `mail_verified` TINYINT(1) NOT NULL,
+ `mail_verified_at` DATETIME DEFAULT NULL,
+ `mail_token` VARCHAR(255) DEFAULT NULL,
+ `password_token` VARCHAR(255) DEFAULT NULL,
+ `state` INT(11) NOT NULL,
+ `last_login` DATETIME DEFAULT NULL,
+ `created` DATETIME NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uq_account_name` (`name`),
+ UNIQUE KEY `uq_account_normal_name` (`normal_name`),
+ UNIQUE KEY `uq_account_mail` (`mail`)
+)
+ ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
\ No newline at end of file
diff --git a/Mhf.Server/Files/Database/Script/schema_sqlite.sql b/Mhf.Server/Files/Database/Script/schema_sqlite.sql
new file mode 100644
index 0000000..e1db521
--- /dev/null
+++ b/Mhf.Server/Files/Database/Script/schema_sqlite.sql
@@ -0,0 +1,23 @@
+CREATE TABLE IF NOT EXISTS `setting` (
+ `key` TEXT NOT NULL,
+ `value` TEXT NOT NULL,
+ PRIMARY KEY (`key`)
+);
+
+CREATE TABLE IF NOT EXISTS `account` (
+ `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` TEXT NOT NULL,
+ `normal_name` TEXT NOT NULL,
+ `hash` TEXT NOT NULL,
+ `mail` TEXT NOT NULL,
+ `mail_verified` INTEGER NOT NULL,
+ `mail_verified_at` DATETIME DEFAULT NULL,
+ `mail_token` TEXT DEFAULT NULL,
+ `password_token` TEXT DEFAULT NULL,
+ `state` INTEGER NOT NULL,
+ `last_login` DATETIME DEFAULT NULL,
+ `created` DATETIME NOT NULL,
+ CONSTRAINT `uq_account_name` UNIQUE (`name`),
+ CONSTRAINT `uq_account_normal_name` UNIQUE (`normal_name`),
+ CONSTRAINT `uq_account_mail` UNIQUE (`mail`)
+);
\ No newline at end of file
diff --git a/Mhf.Server/Files/mhf.pfx b/Mhf.Server/Files/mhf.pfx
new file mode 100644
index 0000000..32a95ff
Binary files /dev/null and b/Mhf.Server/Files/mhf.pfx differ
diff --git a/Mhf.Server/Logging/MhfLogPacket.cs b/Mhf.Server/Logging/MhfLogPacket.cs
new file mode 100644
index 0000000..3e3d883
--- /dev/null
+++ b/Mhf.Server/Logging/MhfLogPacket.cs
@@ -0,0 +1,46 @@
+using System;
+using Mhf.Server.Packet;
+
+namespace Mhf.Server.Logging
+{
+ public class MhfLogPacket : MhfPacket
+ {
+ public MhfLogPacket(string clientIdentity, MhfPacket packet, MhfLogType logType)
+ : base(packet.Header, packet.Data.Clone())
+ {
+ LogType = logType;
+ TimeStamp = DateTime.Now;
+ ClientIdentity = clientIdentity;
+ }
+
+ public string ClientIdentity { get; }
+ public MhfLogType LogType { get; }
+ public DateTime TimeStamp { get; }
+ public string Hex => Data.ToHexString(' ');
+ public string Ascii => Data.ToAsciiString(true);
+
+ public string ToLogText()
+ {
+ String log = $"{ClientIdentity} Packet Log";
+ log += Environment.NewLine;
+ log += "----------";
+ log += Environment.NewLine;
+ log += $"[{TimeStamp:HH:mm:ss}][Typ:{LogType}]";
+ log += Environment.NewLine;
+ log += $"[Id:0x{Id:X2}|{Id}][BodyLen:{Data.Size}][{PacketIdName}]";
+ log += Environment.NewLine;
+ log += Header.ToLogText();
+ log += Environment.NewLine;
+ log += "ASCII:";
+ log += Environment.NewLine;
+ log += Ascii;
+ log += Environment.NewLine;
+ log += "HEX:";
+ log += Environment.NewLine;
+ log += Hex;
+ log += Environment.NewLine;
+ log += "----------";
+ return log;
+ }
+ }
+}
diff --git a/Mhf.Server/Logging/MhfLogType.cs b/Mhf.Server/Logging/MhfLogType.cs
new file mode 100644
index 0000000..acb0db5
--- /dev/null
+++ b/Mhf.Server/Logging/MhfLogType.cs
@@ -0,0 +1,10 @@
+namespace Mhf.Server.Logging
+{
+ public enum MhfLogType
+ {
+ PacketIn = 1,
+ PacketOut = 2,
+ PacketUnhandled = 3,
+ PacketError = 4
+ }
+}
diff --git a/Mhf.Server/Logging/MhfLogger.cs b/Mhf.Server/Logging/MhfLogger.cs
new file mode 100644
index 0000000..987f558
--- /dev/null
+++ b/Mhf.Server/Logging/MhfLogger.cs
@@ -0,0 +1,195 @@
+using System;
+using Arrowgene.Services.Logging;
+using Arrowgene.Services.Networking.Tcp;
+using Mhf.Server.Model;
+using Mhf.Server.Packet;
+using Mhf.Server.Setting;
+
+namespace Mhf.Server.Logging
+{
+ public class MhfLogger : Logger
+ {
+ private MhfSetting _setting;
+
+ public MhfLogger() : this(null)
+ {
+ }
+
+ public MhfLogger(string identity, string zone = null) : base(identity, zone)
+ {
+ }
+
+ public override void Initialize(string identity, string zone, object configuration)
+ {
+ base.Initialize(identity, zone, configuration);
+ _setting = configuration as MhfSetting;
+ if (_setting == null)
+ {
+ Error("Couldn't apply MhfLogger configuration");
+ }
+ }
+
+ public void Info(MhfClient client, string message, params object[] args)
+ {
+ Write(LogLevel.Info, null, $"{client.Identity} {message}", args);
+ }
+
+ public void Info(MhfConnection connection, string message, params object[] args)
+ {
+ MhfClient client = connection.Client;
+ if (client != null)
+ {
+ Info(client, message, args);
+ return;
+ }
+
+ Write(LogLevel.Info, null, $"{connection.Identity} {message}", args);
+ }
+
+ public void Debug(MhfClient client, string message, params object[] args)
+ {
+ Write(LogLevel.Debug, null, $"{client.Identity} {message}", args);
+ }
+
+ public void Error(MhfClient client, string message, params object[] args)
+ {
+ Write(LogLevel.Error, null, $"{client.Identity} {message}", args);
+ }
+
+ public void Error(MhfConnection connection, string message, params object[] args)
+ {
+ MhfClient client = connection.Client;
+ if (client != null)
+ {
+ Error(client, message, args);
+ return;
+ }
+
+ Write(LogLevel.Error, null, $"{connection.Identity} {message}", args);
+ }
+
+ public void Exception(MhfClient client, Exception exception)
+ {
+ Write(LogLevel.Error, null, $"{client.Identity} {exception}");
+ }
+
+ public void Exception(MhfConnection connection, Exception exception)
+ {
+ MhfClient client = connection.Client;
+ if (client != null)
+ {
+ Exception(client, exception);
+ return;
+ }
+
+ Write(LogLevel.Error, null, $"{connection.Identity} {exception}");
+ }
+
+ public void Info(ITcpSocket socket, string message, params object[] args)
+ {
+ Write(LogLevel.Info, null, $"[{socket.Identity}] {message}", args);
+ }
+
+ public void Debug(ITcpSocket socket, string message, params object[] args)
+ {
+ Write(LogLevel.Debug, null, $"[{socket.Identity}] {message}", args);
+ }
+
+ public void Error(ITcpSocket socket, string message, params object[] args)
+ {
+ Write(LogLevel.Error, null, $"[{socket.Identity}] {message}", args);
+ }
+
+ public void Exception(ITcpSocket socket, Exception exception)
+ {
+ Write(LogLevel.Error, null, $"[{socket.Identity}] {exception}");
+ }
+
+ public void LogIncomingPacket(MhfClient client, MhfPacket packet)
+ {
+ if (_setting.LogIncomingPackets)
+ {
+ MhfLogPacket logPacket = new MhfLogPacket(client.Identity, packet, MhfLogType.PacketIn);
+ WritePacket(logPacket);
+ }
+ }
+
+ public void LogIncomingPacket(MhfConnection connection, MhfPacket packet)
+ {
+ MhfClient client = connection.Client;
+ if (client != null)
+ {
+ LogIncomingPacket(client, packet);
+ return;
+ }
+
+ if (!_setting.LogIncomingPackets)
+ {
+ return;
+ }
+
+ MhfLogPacket logPacket = new MhfLogPacket(connection.Identity, packet, MhfLogType.PacketIn);
+ WritePacket(logPacket);
+ }
+
+ public void LogUnknownIncomingPacket(MhfClient client, MhfPacket packet)
+ {
+ if (_setting.LogUnknownIncomingPackets)
+ {
+ MhfLogPacket logPacket = new MhfLogPacket(client.Identity, packet, MhfLogType.PacketUnhandled);
+ WritePacket(logPacket);
+ }
+ }
+
+ public void LogUnknownIncomingPacket(MhfConnection connection, MhfPacket packet)
+ {
+ MhfClient client = connection.Client;
+ if (client != null)
+ {
+ LogUnknownIncomingPacket(client, packet);
+ return;
+ }
+
+ if (!_setting.LogIncomingPackets)
+ {
+ return;
+ }
+
+ MhfLogPacket logPacket =
+ new MhfLogPacket(connection.Identity, packet, MhfLogType.PacketUnhandled);
+ WritePacket(logPacket);
+ }
+
+ public void LogOutgoingPacket(MhfClient client, MhfPacket packet)
+ {
+ if (_setting.LogOutgoingPackets)
+ {
+ MhfLogPacket logPacket = new MhfLogPacket(client.Identity, packet, MhfLogType.PacketOut);
+ WritePacket(logPacket);
+ }
+ }
+
+ public void LogOutgoingPacket(MhfConnection connection, MhfPacket packet)
+ {
+ MhfClient client = connection.Client;
+ if (client != null)
+ {
+ LogOutgoingPacket(client, packet);
+ return;
+ }
+
+ if (!_setting.LogIncomingPackets)
+ {
+ return;
+ }
+
+ MhfLogPacket logPacket = new MhfLogPacket(connection.Identity, packet, MhfLogType.PacketOut);
+ WritePacket(logPacket);
+ }
+
+ private void WritePacket(MhfLogPacket packet)
+ {
+ Write(LogLevel.Info, packet, packet.ToLogText());
+ }
+ }
+}
diff --git a/Mhf.Server/Mhf.Server.csproj b/Mhf.Server/Mhf.Server.csproj
new file mode 100644
index 0000000..f271834
--- /dev/null
+++ b/Mhf.Server/Mhf.Server.csproj
@@ -0,0 +1,25 @@
+
+
+ netstandard2.1
+ Mhf.Server
+ Monster Hunter Frontier Z Server
+ Mhf Team
+ Mhf.Server
+ $(Version)
+ Copyright © 2019 Mhf Team
+ 8
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+ Files\%(RecursiveDir)%(Filename)%(Extension)
+
+
+
diff --git a/Mhf.Server/MhfServer.cs b/Mhf.Server/MhfServer.cs
new file mode 100644
index 0000000..27318c9
--- /dev/null
+++ b/Mhf.Server/MhfServer.cs
@@ -0,0 +1,132 @@
+/*
+ * This file is part of Mhf.Server
+ *
+ * Mhf.Server is a server implementation for the game "Monster Hunter Frontier Z".
+ * Copyright (C) 2019-2020 Mhf Team
+ *
+ * Github: https://github.com/sebastian-heinz/mhf-server
+ *
+ * Mhf.Server 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.
+ *
+ * Mhf.Server 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 Mhf.Server. If not, see .
+ */
+
+using Arrowgene.Services.Logging;
+using Arrowgene.Services.Networking.Tcp.Server.AsyncEvent;
+using Mhf.Server.Common.Instance;
+using Mhf.Server.Database;
+using Mhf.Server.Logging;
+using Mhf.Server.Model;
+using Mhf.Server.Packet;
+using Mhf.Server.PacketHandler;
+using Mhf.Server.Setting;
+using Mhf.Server.Web;
+using Mhf.Server.Web.Server.Kestrel;
+using Mhf.Server.WebMiddlewares;
+using Mhf.Server.WebRoutes;
+using Microsoft.Extensions.FileProviders;
+
+namespace Mhf.Server
+{
+ public class MhfServer
+ {
+ public MhfSetting Setting { get; }
+ public PacketRouter Router { get; }
+ public ClientLookup Clients { get; }
+ public IDatabase Database { get; }
+ public InstanceGenerator Instances { get; }
+
+ public IFileProvider WebFileProvider { get; }
+
+ private readonly QueueConsumer _authConsumer;
+ private readonly QueueConsumer _lobbyConsumer;
+ private readonly AsyncEventServer _authServer;
+ private readonly AsyncEventServer _lobbyServer;
+ private readonly WebServer _webServer;
+ private readonly MhfLogger _logger;
+
+ public MhfServer(MhfSetting setting)
+ {
+ Setting = new MhfSetting(setting);
+
+ LogProvider.Configure(Setting);
+ _logger = LogProvider.Logger(this);
+
+ Instances = new InstanceGenerator();
+ Clients = new ClientLookup();
+ Router = new PacketRouter();
+ Database = new MhfDatabaseBuilder().Build(Setting.DatabaseSetting);
+
+ _authConsumer = new QueueConsumer(Setting, Setting.ServerSocketSettings);
+ _authConsumer.ClientDisconnected += AuthClientDisconnected;
+ _authServer = new AsyncEventServer(
+ Setting.ListenIpAddress,
+ Setting.AuthServerPort,
+ _authConsumer,
+ Setting.ServerSocketSettings
+ );
+
+ _lobbyConsumer= new QueueConsumer(Setting, Setting.ServerSocketSettings);
+ _lobbyConsumer.ClientDisconnected += LobbyClientDisconnected;
+ _lobbyServer = new AsyncEventServer(
+ Setting.ListenIpAddress,
+ Setting.LobbyServerPort,
+ _lobbyConsumer,
+ Setting.ServerSocketSettings
+ );
+
+ _webServer = new WebServer(Setting, new KestrelWebServer(Setting));
+
+ WebFileProvider = new PhysicalFileProvider(Setting.WebSetting.WebFolder);
+
+ LoadPacketHandler();
+ LoadWebRoutes();
+ }
+
+ private void AuthClientDisconnected(MhfConnection client)
+ {
+ }
+
+ private void LobbyClientDisconnected(MhfConnection client)
+ {
+ }
+
+ public void Start()
+ {
+ _authServer.Start();
+ _webServer.Start();
+ }
+
+ public void Stop()
+ {
+ _authServer.Stop();
+ _webServer.Stop();
+ }
+
+ private void LoadPacketHandler()
+ {
+ _authConsumer.AddHandler(new MsgHeadHandler(this));
+ }
+
+ private void LoadWebRoutes()
+ {
+ _webServer.AddRoute(new IndexRoute());
+ _webServer.AddRoute(new AuthLauncherLoginRoute(WebFileProvider));
+ _webServer.AddRoute(new AuthLauncherStartRoute(WebFileProvider));
+ _webServer.AddRoute(new LauncherIndexRoute(WebFileProvider));
+ _webServer.AddRoute(new MhfFileRoute(WebFileProvider));
+
+ // Middleware - Order Matters
+ _webServer.AddMiddleware(new StaticFileMiddleware("", WebFileProvider));
+ }
+ }
+}
diff --git a/Mhf.Server/Model/Account.cs b/Mhf.Server/Model/Account.cs
new file mode 100644
index 0000000..4a445b3
--- /dev/null
+++ b/Mhf.Server/Model/Account.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Mhf.Server.Model
+{
+ public class Account
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public string NormalName { get; set; }
+ public string Hash { get; set; }
+ public string Mail { get; set; }
+ public string MailToken { get; set; }
+ public string PasswordToken { get; set; }
+ public bool MailVerified { get; set; }
+ public DateTime? MailVerifiedAt { get; set; }
+ public AccountStateType State { get; set; }
+ public DateTime? LastLogin { get; set; }
+ public DateTime Created { get; set; }
+
+ public Account()
+ {
+ Id = -1;
+ }
+ }
+}
diff --git a/Mhf.Server/Model/AccountStateType.cs b/Mhf.Server/Model/AccountStateType.cs
new file mode 100644
index 0000000..3d6915b
--- /dev/null
+++ b/Mhf.Server/Model/AccountStateType.cs
@@ -0,0 +1,11 @@
+namespace Mhf.Server.Model
+{
+ public enum AccountStateType
+ {
+ Banned = 0,
+ None = 0,
+ User = 1,
+ GameMaster = 50,
+ Admin = 100
+ }
+}
diff --git a/Mhf.Server/Model/ClientLookup.cs b/Mhf.Server/Model/ClientLookup.cs
new file mode 100644
index 0000000..47feeeb
--- /dev/null
+++ b/Mhf.Server/Model/ClientLookup.cs
@@ -0,0 +1,71 @@
+using System.Collections.Generic;
+
+ namespace Mhf.Server.Model
+{
+ public class ClientLookup
+ {
+ private readonly List _clients;
+
+ private readonly object _lock = new object();
+
+ public ClientLookup()
+ {
+ _clients = new List();
+ }
+
+ ///
+ /// Returns all Clients.
+ ///
+ public List GetAll()
+ {
+ lock (_lock)
+ {
+ return new List(_clients);
+ }
+ }
+
+ ///
+ /// Adds a Client.
+ ///
+ public void Add(MhfClient client)
+ {
+ if (client == null)
+ {
+ return;
+ }
+
+ lock (_lock)
+ {
+ _clients.Add(client);
+ }
+ }
+
+ ///
+ /// Removes the Client from all lists and lookup tables.
+ ///
+ public void Remove(MhfClient client)
+ {
+ lock (_lock)
+ {
+ _clients.Remove(client);
+ }
+ }
+
+ ///
+ /// Returns a Client by AccountId if it exists.
+ ///
+ public MhfClient GetByAccountId(int accountId)
+ {
+ List clients = GetAll();
+ foreach (MhfClient client in clients)
+ {
+ if (client.Account.Id == accountId)
+ {
+ return client;
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/Mhf.Server/Model/DatabaseType.cs b/Mhf.Server/Model/DatabaseType.cs
new file mode 100644
index 0000000..62c25f4
--- /dev/null
+++ b/Mhf.Server/Model/DatabaseType.cs
@@ -0,0 +1,7 @@
+namespace Mhf.Server.Model
+{
+ public enum DatabaseType
+ {
+ SQLite,
+ }
+}
diff --git a/Mhf.Server/Model/ISender.cs b/Mhf.Server/Model/ISender.cs
new file mode 100644
index 0000000..247c6b9
--- /dev/null
+++ b/Mhf.Server/Model/ISender.cs
@@ -0,0 +1,9 @@
+using Mhf.Server.Packet;
+
+namespace Mhf.Server.Model
+{
+ public interface ISender
+ {
+ void Send(MhfPacket packet);
+ }
+}
diff --git a/Mhf.Server/Model/MhfClient.cs b/Mhf.Server/Model/MhfClient.cs
new file mode 100644
index 0000000..db25a3e
--- /dev/null
+++ b/Mhf.Server/Model/MhfClient.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Diagnostics;
+using Arrowgene.Services.Logging;
+using Mhf.Server.Logging;
+using Mhf.Server.Packet;
+
+namespace Mhf.Server.Model
+{
+ [DebuggerDisplay("{Identity,nq}")]
+ public class MhfClient : ISender
+ {
+ private readonly MhfLogger _logger;
+
+ public MhfClient()
+ {
+ _logger = LogProvider.Logger(this);
+ Creation = DateTime.Now;
+ Identity = "";
+ }
+
+ public DateTime Creation { get; }
+ public string Identity { get; private set; }
+ public Account Account { get; set; }
+ public MhfConnection Connection { get; set; }
+
+
+ public void Send(MhfPacket packet)
+ {
+ Connection.Send(packet);
+ }
+
+ public void UpdateIdentity()
+ {
+ Identity = "";
+ if (Account != null)
+ {
+ Identity += $"[Acc:{Account.Id}:{Account.Name}]";
+ return;
+ }
+
+ if (Connection != null)
+ {
+ Identity += $"[Con:{Connection.Identity}]";
+ }
+ }
+
+ public void Close()
+ {
+ Connection?.Socket.Close();
+ }
+ }
+}
diff --git a/Mhf.Server/Model/MhfConnection.cs b/Mhf.Server/Model/MhfConnection.cs
new file mode 100644
index 0000000..6772bdf
--- /dev/null
+++ b/Mhf.Server/Model/MhfConnection.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using Arrowgene.Services.Logging;
+using Arrowgene.Services.Networking.Tcp;
+using Mhf.Server.Logging;
+using Mhf.Server.Packet;
+
+namespace Mhf.Server.Model
+{
+ public class MhfConnection : ISender
+ {
+ private readonly MhfLogger _logger;
+
+ public MhfConnection(ITcpSocket clientSocket, PacketFactory packetFactory)
+ {
+ _logger = LogProvider.Logger(this);
+ Socket = clientSocket;
+ PacketFactory = packetFactory;
+ Client = null;
+ }
+
+ public string Identity => Socket.Identity;
+ public ITcpSocket Socket { get; }
+ public PacketFactory PacketFactory { get; }
+ public MhfClient Client { get; set; }
+
+ public List Receive(byte[] data)
+ {
+ List packets;
+ try
+ {
+ packets = PacketFactory.Read(data);
+ }
+ catch (Exception ex)
+ {
+ _logger.Exception(this, ex);
+ packets = new List();
+ }
+
+ return packets;
+ }
+
+ public void Send(MhfPacket packet)
+ {
+ byte[] data;
+ try
+ {
+ data = PacketFactory.Write(packet);
+ }
+ catch (Exception ex)
+ {
+ _logger.Exception(this, ex);
+ return;
+ }
+
+ // _logger.LogOutgoingPacket(this, packet);
+ Socket.Send(data);
+ }
+ }
+}
diff --git a/Mhf.Server/Packet/ClientHandler.cs b/Mhf.Server/Packet/ClientHandler.cs
new file mode 100644
index 0000000..dfb8782
--- /dev/null
+++ b/Mhf.Server/Packet/ClientHandler.cs
@@ -0,0 +1,13 @@
+using Mhf.Server.Model;
+
+namespace Mhf.Server.Packet
+{
+ public abstract class ClientHandler : Handler, IClientHandler
+ {
+ protected ClientHandler(MhfServer server) : base(server)
+ {
+ }
+
+ public abstract void Handle(MhfClient client, MhfPacket packet);
+ }
+}
diff --git a/Mhf.Server/Packet/ClientHandlerDeserializer.cs b/Mhf.Server/Packet/ClientHandlerDeserializer.cs
new file mode 100644
index 0000000..9e825ae
--- /dev/null
+++ b/Mhf.Server/Packet/ClientHandlerDeserializer.cs
@@ -0,0 +1,22 @@
+using Mhf.Server.Model;
+
+namespace Mhf.Server.Packet
+{
+ public abstract class ClientHandlerDeserializer : ClientHandler
+ {
+ private readonly IPacketDeserializer _deserializer;
+
+ protected ClientHandlerDeserializer(MhfServer server, IPacketDeserializer deserializer) : base(server)
+ {
+ _deserializer = deserializer;
+ }
+
+ public override void Handle(MhfClient client, MhfPacket requestPacket)
+ {
+ T request = _deserializer.Deserialize(requestPacket);
+ HandleRequest(client, request);
+ }
+
+ public abstract void HandleRequest(MhfClient client, T request);
+ }
+}
diff --git a/Mhf.Server/Packet/ConnectionHandler.cs b/Mhf.Server/Packet/ConnectionHandler.cs
new file mode 100644
index 0000000..2322562
--- /dev/null
+++ b/Mhf.Server/Packet/ConnectionHandler.cs
@@ -0,0 +1,13 @@
+using Mhf.Server.Model;
+
+namespace Mhf.Server.Packet
+{
+ public abstract class ConnectionHandler : Handler, IConnectionHandler
+ {
+ protected ConnectionHandler(MhfServer server) : base(server)
+ {
+ }
+
+ public abstract void Handle(MhfConnection client, MhfPacket packet);
+ }
+}
diff --git a/Mhf.Server/Packet/Handler.cs b/Mhf.Server/Packet/Handler.cs
new file mode 100644
index 0000000..d842dc4
--- /dev/null
+++ b/Mhf.Server/Packet/Handler.cs
@@ -0,0 +1,30 @@
+using Arrowgene.Services.Logging;
+using Mhf.Server.Database;
+using Mhf.Server.Logging;
+using Mhf.Server.Model;
+using Mhf.Server.Setting;
+
+namespace Mhf.Server.Packet
+{
+ public abstract class Handler : IHandler
+ {
+ protected Handler(MhfServer server)
+ {
+ Logger = LogProvider.Logger(this);
+ Server = server;
+ Router = server.Router;
+ Database = server.Database;
+ Settings = server.Setting;
+ Clients = server.Clients;
+ }
+
+ public abstract ushort Id { get; }
+ public virtual int ExpectedSize => QueueConsumer.NoExpectedSize;
+ protected MhfServer Server { get; }
+ protected MhfSetting Settings { get; }
+ protected MhfLogger Logger { get; }
+ protected PacketRouter Router { get; }
+ protected ClientLookup Clients { get; }
+ protected IDatabase Database { get; }
+ }
+}
diff --git a/Mhf.Server/Packet/IClientHandler.cs b/Mhf.Server/Packet/IClientHandler.cs
new file mode 100644
index 0000000..9dd455f
--- /dev/null
+++ b/Mhf.Server/Packet/IClientHandler.cs
@@ -0,0 +1,9 @@
+using Mhf.Server.Model;
+
+namespace Mhf.Server.Packet
+{
+ public interface IClientHandler : IHandler
+ {
+ void Handle(MhfClient client, MhfPacket packet);
+ }
+}
diff --git a/Mhf.Server/Packet/IConnectionHandler.cs b/Mhf.Server/Packet/IConnectionHandler.cs
new file mode 100644
index 0000000..3289831
--- /dev/null
+++ b/Mhf.Server/Packet/IConnectionHandler.cs
@@ -0,0 +1,9 @@
+using Mhf.Server.Model;
+
+namespace Mhf.Server.Packet
+{
+ public interface IConnectionHandler : IHandler
+ {
+ void Handle(MhfConnection connection, MhfPacket packet);
+ }
+}
diff --git a/Mhf.Server/Packet/IHandler.cs b/Mhf.Server/Packet/IHandler.cs
new file mode 100644
index 0000000..e6b2ab3
--- /dev/null
+++ b/Mhf.Server/Packet/IHandler.cs
@@ -0,0 +1,8 @@
+namespace Mhf.Server.Packet
+{
+ public interface IHandler
+ {
+ ushort Id { get; }
+ int ExpectedSize { get; }
+ }
+}
diff --git a/Mhf.Server/Packet/IPacketDeserializer.cs b/Mhf.Server/Packet/IPacketDeserializer.cs
new file mode 100644
index 0000000..c30db91
--- /dev/null
+++ b/Mhf.Server/Packet/IPacketDeserializer.cs
@@ -0,0 +1,7 @@
+namespace Mhf.Server.Packet
+{
+ public interface IPacketDeserializer
+ {
+ T Deserialize(MhfPacket packet);
+ }
+}
diff --git a/Mhf.Server/Packet/IPacketSerializer.cs b/Mhf.Server/Packet/IPacketSerializer.cs
new file mode 100644
index 0000000..d70c03d
--- /dev/null
+++ b/Mhf.Server/Packet/IPacketSerializer.cs
@@ -0,0 +1,7 @@
+namespace Mhf.Server.Packet
+{
+ public interface IPacketSerializer
+ {
+ MhfPacket Serialize(T obj);
+ }
+}
diff --git a/Mhf.Server/Packet/MhfPacket.cs b/Mhf.Server/Packet/MhfPacket.cs
new file mode 100644
index 0000000..6d8d63b
--- /dev/null
+++ b/Mhf.Server/Packet/MhfPacket.cs
@@ -0,0 +1,56 @@
+using System;
+using Arrowgene.Services.Buffers;
+
+namespace Mhf.Server.Packet
+{
+ public class MhfPacket
+ {
+ public static string GetPacketIdName(ushort id)
+ {
+ if (Enum.IsDefined(typeof(PacketId), id))
+ {
+ PacketId authPacketId = (PacketId) id;
+ return authPacketId.ToString();
+ }
+
+ return null;
+ }
+
+ private string _packetIdName;
+
+ public MhfPacket(ushort id, IBuffer buffer)
+ {
+ Header = new PacketHeader(id);
+ Data = buffer;
+ }
+
+ public MhfPacket(PacketHeader header, IBuffer buffer)
+ {
+ Header = header;
+ Data = buffer;
+ }
+
+ public IBuffer Data { get; }
+ public ushort Id => Header.Id;
+ public PacketHeader Header { get; }
+
+ public string PacketIdName
+ {
+ get
+ {
+ if (_packetIdName != null)
+ {
+ return _packetIdName;
+ }
+
+ _packetIdName = GetPacketIdName(Id);
+ if (_packetIdName == null)
+ {
+ _packetIdName = $"ID_NOT_DEFINED_{Id}";
+ }
+
+ return _packetIdName;
+ }
+ }
+ }
+}
diff --git a/Mhf.Server/Packet/PacketFactory.cs b/Mhf.Server/Packet/PacketFactory.cs
new file mode 100644
index 0000000..b67a1da
--- /dev/null
+++ b/Mhf.Server/Packet/PacketFactory.cs
@@ -0,0 +1,400 @@
+using System;
+using System.Collections.Generic;
+using Arrowgene.Services.Buffers;
+using Arrowgene.Services.Logging;
+using Mhf.Server.Common;
+using Mhf.Server.Logging;
+using Mhf.Server.Setting;
+
+namespace Mhf.Server.Packet
+{
+ public class PacketFactory
+ {
+ public const int PacketHeaderSize = 14;
+ public const int InitPacketSize = 8;
+ public const uint CryptoInit = 995117;
+
+ public static byte[] EncryptKey = new byte[]
+ {
+ 0x90, 0x51, 0x26, 0x25, 0x04, 0xBF, 0xCF, 0x4C, 0x92, 0x02, 0x52, 0x7A, 0x70, 0x1A, 0x41, 0x88, 0x8C, 0xC2,
+ 0xCE, 0xB8, 0xF6, 0x57, 0x7E, 0xBA, 0x83, 0x63, 0x2C, 0x24, 0x9A, 0x67, 0x86, 0x0C, 0xBE, 0x72, 0xFD, 0xB6,
+ 0x7B, 0x79, 0xB0, 0x22, 0x5A, 0x60, 0x5C, 0x4F, 0x49, 0xE2, 0x0E, 0xF5, 0x3A, 0x81, 0xAE, 0x11, 0x6B, 0xF0,
+ 0xA1, 0x01, 0xE8, 0x65, 0x8D, 0x5B, 0xDC, 0xCC, 0x93, 0x18, 0xB3, 0xAB, 0x77, 0xF7, 0x8E, 0xEC, 0xEF, 0x05,
+ 0x00, 0xCA, 0x4E, 0xA7, 0xBC, 0xB5, 0x10, 0xC6, 0x6C, 0xC0, 0xC4, 0xE5, 0x87, 0x3F, 0xC1, 0x82, 0x29, 0x96,
+ 0x45, 0x73, 0x07, 0xCB, 0x43, 0xF9, 0xF3, 0x08, 0x89, 0xD0, 0x99, 0x6A, 0x3B, 0x37, 0x19, 0xD4, 0x40, 0xEA,
+ 0xD7, 0x85, 0x16, 0x66, 0x1E, 0x9C, 0x39, 0xBB, 0xEE, 0x4A, 0x03, 0x8A, 0x36, 0x2D, 0x13, 0x1D, 0x56, 0x48,
+ 0xC7, 0x0D, 0x59, 0xB2, 0x44, 0xA3, 0xFE, 0x8B, 0x32, 0x1B, 0x84, 0xA0, 0x2E, 0x62, 0x17, 0x42, 0xB9, 0x9B,
+ 0x2B, 0x75, 0xD8, 0x1C, 0x3C, 0x4D, 0x76, 0x27, 0x6E, 0x28, 0xD3, 0x33, 0xC3, 0x21, 0xAF, 0x34, 0x23, 0xDD,
+ 0x68, 0x9F, 0xF1, 0xAD, 0xE1, 0xB4, 0xE7, 0xA6, 0x74, 0x15, 0x4B, 0xFA, 0x3D, 0x5F, 0x7C, 0xDA, 0x2F, 0x0A,
+ 0xE3, 0x7D, 0xC8, 0xB7, 0x12, 0x6F, 0x9E, 0xA9, 0x14, 0x53, 0x97, 0x8F, 0x64, 0xF4, 0xF8, 0xA2, 0xA4, 0x2A,
+ 0xD2, 0x47, 0x9D, 0x71, 0xC5, 0xE9, 0x06, 0x98, 0x20, 0x54, 0x80, 0xAA, 0xF2, 0xAC, 0x50, 0xD6, 0x7F, 0xD9,
+ 0xC9, 0xCD, 0x69, 0x46, 0x6D, 0x30, 0xB1, 0x58, 0x0B, 0x55, 0xD1, 0x5D, 0xD5, 0xBD, 0x31, 0xDE, 0xA5, 0xE4,
+ 0x91, 0x0F, 0x61, 0x38, 0xDF, 0xA8, 0xE6, 0x3E, 0x1F, 0x35, 0xED, 0xDB, 0x94, 0xEB, 0x09, 0x5E, 0x95, 0xFB,
+ 0xFC, 0xE0, 0x78, 0xFF
+ };
+
+ public static byte[] DecryptKey = new byte[]
+ {
+ 0x48, 0x37, 0x09, 0x76, 0x04, 0x47, 0xCC, 0x5C, 0x61, 0xF8, 0xB3, 0xE0, 0x1F, 0x7F, 0x2E, 0xEB, 0x4E, 0x33,
+ 0xB8, 0x7A, 0xBC, 0xAB, 0x6E, 0x8C, 0x3F, 0x68, 0x0D, 0x87, 0x93, 0x7B, 0x70, 0xF2, 0xCE, 0x9D, 0x27, 0xA0,
+ 0x1B, 0x03, 0x02, 0x97, 0x99, 0x58, 0xC5, 0x90, 0x1A, 0x79, 0x8A, 0xB2, 0xDD, 0xE6, 0x86, 0x9B, 0x9F, 0xF3,
+ 0x78, 0x67, 0xED, 0x72, 0x30, 0x66, 0x94, 0xAE, 0xF1, 0x55, 0x6A, 0x0E, 0x8D, 0x5E, 0x82, 0x5A, 0xDB, 0xC7,
+ 0x7D, 0x2C, 0x75, 0xAC, 0x07, 0x95, 0x4A, 0x2B, 0xD4, 0x01, 0x0A, 0xBD, 0xCF, 0xE1, 0x7C, 0x15, 0xDF, 0x80,
+ 0x28, 0x3B, 0x2A, 0xE3, 0xF9, 0xAF, 0x29, 0xEC, 0x8B, 0x19, 0xC0, 0x39, 0x6F, 0x1D, 0xA2, 0xDA, 0x65, 0x34,
+ 0x50, 0xDC, 0x98, 0xB9, 0x0C, 0xC9, 0x21, 0x5B, 0xAA, 0x91, 0x96, 0x42, 0xFE, 0x25, 0x0B, 0x24, 0xB0, 0xB5,
+ 0x16, 0xD6, 0xD0, 0x31, 0x57, 0x18, 0x88, 0x6D, 0x1E, 0x54, 0x0F, 0x62, 0x77, 0x85, 0x10, 0x3A, 0x44, 0xBF,
+ 0x00, 0xEA, 0x08, 0x3E, 0xF6, 0xFA, 0x59, 0xBE, 0xCD, 0x64, 0x1C, 0x8F, 0x71, 0xC8, 0xBA, 0xA3, 0x89, 0x36,
+ 0xC3, 0x83, 0xC4, 0xE8, 0xA9, 0x4B, 0xEF, 0xBB, 0xD1, 0x41, 0xD3, 0xA5, 0x32, 0x9E, 0x26, 0xDE, 0x81, 0x40,
+ 0xA7, 0x4D, 0x23, 0xB7, 0x13, 0x8E, 0x17, 0x73, 0x4C, 0xE5, 0x20, 0x05, 0x51, 0x56, 0x11, 0x9C, 0x52, 0xCA,
+ 0x4F, 0x7E, 0xB6, 0xD8, 0x49, 0x5D, 0x3D, 0xD9, 0x12, 0x06, 0x63, 0xE2, 0xC6, 0x9A, 0x69, 0xE4, 0xD5, 0x6C,
+ 0x92, 0xD7, 0xB1, 0xF5, 0x3C, 0xA1, 0xE7, 0xEE, 0xFD, 0xA6, 0x2D, 0xB4, 0xE9, 0x53, 0xF0, 0xA8, 0x38, 0xCB,
+ 0x6B, 0xF7, 0x45, 0xF4, 0x74, 0x46, 0x35, 0xA4, 0xD2, 0x60, 0xC1, 0x2F, 0x14, 0x43, 0xC2, 0x5F, 0xAD, 0xFB,
+ 0xFC, 0x22, 0x84, 0xFF
+ };
+
+ public static byte[] SharedCryptKey = new byte[]
+ {
+ 0xDD, 0xA8, 0x5F, 0x1E, 0x57, 0xAF, 0xC0, 0xCC, 0x43, 0x35, 0x8F, 0xBB, 0x6F, 0xE6, 0xA1, 0xD6, 0x60, 0xB9,
+ 0x1A, 0xAE, 0x20, 0x49, 0x24, 0x81, 0x21, 0xFE, 0x86, 0x2B, 0x98, 0xB7, 0xB3, 0xD2, 0x91, 0x01, 0x3A, 0x4C,
+ 0x65, 0x92, 0x1C, 0xF4, 0xBE, 0xDD, 0xD9, 0x08, 0xE6, 0x81, 0x98, 0x1B, 0x8D, 0x60, 0xF3, 0x6F, 0xA1, 0x47,
+ 0x24, 0xF1, 0x53, 0x45, 0xC8, 0x7B, 0x88, 0x80, 0x4E, 0x36, 0xC3, 0x0D, 0xC9, 0xD6, 0x8B, 0x08, 0x19, 0x0B,
+ 0xA5, 0xC1, 0x11, 0x4C, 0x60, 0xF8, 0x5D, 0xFC, 0x15, 0x68, 0x7E, 0x32, 0xC0, 0x50, 0xAB, 0x64, 0x1F, 0x8A,
+ 0xD4, 0x08, 0x39, 0x7F, 0xC2, 0xFB, 0xBA, 0x6C, 0xF0, 0xE6, 0xB0, 0x31, 0x10, 0xC1, 0xBF, 0x75, 0x43, 0xBB,
+ 0x18, 0x04, 0x0D, 0xD1, 0x97, 0xF7, 0x23, 0x21, 0x83, 0x8B, 0xCA, 0x25, 0x2B, 0xA3, 0x03, 0x13, 0xEA, 0xAE,
+ 0xFE, 0xF0, 0xEB, 0xFD, 0x85, 0x57, 0x53, 0x65, 0x41, 0x2A, 0x40, 0x99, 0xC0, 0x94, 0x65, 0x7E, 0x7C, 0x93,
+ 0x82, 0xB0, 0xB3, 0xE5, 0xC0, 0x21, 0x09, 0x84, 0xD5, 0xEF, 0x9F, 0xD1, 0x7E, 0xDC, 0x4D, 0xF5, 0x7E, 0xCD,
+ 0x45, 0x3C, 0x7F, 0xF5, 0x59, 0x98, 0xC6, 0x55, 0xFC, 0x9F, 0xA3, 0xB7, 0x74, 0xEE, 0x31, 0x98, 0xE6, 0xB7,
+ 0xBE, 0x26, 0xF4, 0x3C, 0x76, 0xF1, 0x23, 0x7E, 0x02, 0x4E, 0x3C, 0xD1, 0xC7, 0x28, 0x23, 0x73, 0xC4, 0xD9,
+ 0x5E, 0x0D, 0xA1, 0x80, 0xA5, 0xAA, 0x26, 0x0A, 0xA3, 0x44, 0x82, 0x74, 0xE6, 0x3C, 0x44, 0x27, 0x51, 0x0D,
+ 0x5F, 0xC7, 0x9C, 0xD6, 0x63, 0x67, 0xA5, 0x27, 0x97, 0x38, 0xFB, 0x2D, 0xD3, 0xD6, 0x60, 0x25, 0x83, 0x4D,
+ 0x37, 0x5B, 0x40, 0x59, 0x11, 0x77, 0x51, 0x11, 0x14, 0x18, 0x07, 0x63, 0xB1, 0x34, 0x3D, 0xB8, 0x60, 0x13,
+ 0xC2, 0xE8, 0x13, 0x82
+ };
+
+ private readonly MhfLogger _logger;
+ private readonly MhfSetting _setting;
+
+ private int _position;
+ private IBuffer _buffer;
+ private PacketHeader _header;
+
+ private uint _readKeyRot;
+ private uint _sendKeyRot;
+
+ private uint _sentPackets;
+ private ushort _combinedCheck;
+
+ public PacketFactory(MhfSetting setting)
+ {
+ _logger = LogProvider.Logger(this);
+ _setting = setting;
+ _readKeyRot = CryptoInit;
+ _sendKeyRot = CryptoInit;
+ _sentPackets = 0;
+ _header = null;
+ Reset();
+ }
+
+ public byte[] Write(MhfPacket packet)
+ {
+ if (packet.Data.Size > ushort.MaxValue)
+ {
+ _logger.Error(
+ $"Packet Size: {packet.Data.Size} exceeds maximum size of {ushort.MaxValue} for PacketId: {packet.Id}");
+ return null;
+ }
+
+ byte[] data = packet.Data.GetAllBytes();
+
+
+ byte keyRotDelta = 2;
+ // Update the rolling key index.
+ if (keyRotDelta != 0)
+ {
+ _sendKeyRot = keyRotDelta * (_sendKeyRot + 1);
+ }
+
+ data = Encrypt(data, _sendKeyRot,
+ out ushort combinedCheck, out ushort check0,
+ out ushort check1, out ushort check2);
+
+ packet.Header.DataSize = (ushort)data.Length;
+ packet.Header.Pf0 = (byte) (((packet.Header.DataSize >> 12) & 0xF3) | 3);
+ packet.Header.KeyRotDelta = keyRotDelta;
+ packet.Header.CombinedCheck = 0;
+ packet.Header.Check0 = check0;
+ packet.Header.Check1 = check1;
+ packet.Header.Check2 = check2;
+
+ Console.WriteLine(packet.Header.ToLogText());
+
+ IBuffer buffer = BufferProvider.Provide();
+ buffer.WriteByte(packet.Header.Pf0);
+ buffer.WriteByte(packet.Header.KeyRotDelta);
+ buffer.WriteInt16(packet.Header.Id, Endianness.Big);
+ buffer.WriteInt16(packet.Header.DataSize, Endianness.Big);
+ buffer.WriteInt16(packet.Header.CombinedCheck, Endianness.Big);
+ buffer.WriteInt16(packet.Header.Check0, Endianness.Big);
+ buffer.WriteInt16(packet.Header.Check1, Endianness.Big);
+ buffer.WriteInt16(packet.Header.Check2, Endianness.Big);
+ buffer.WriteBytes(data);
+ byte[] final = buffer.GetAllBytes();
+ return final;
+ }
+
+ public List Read(byte[] data)
+ {
+ List packets = new List();
+ if (_buffer == null)
+ {
+ _buffer = BufferProvider.Provide(data);
+ }
+ else
+ {
+ _buffer.SetPositionEnd();
+ _buffer.WriteBytes(data);
+ }
+
+ _buffer.Position = _position;
+
+ bool read = true;
+ while (read)
+ {
+ read = false;
+
+ if (_header == null && _buffer.Size - _buffer.Position >= PacketHeaderSize)
+ {
+ byte pf0 = _buffer.ReadByte();
+ byte keyRotDelta = _buffer.ReadByte();
+ ushort id = _buffer.ReadUInt16(Endianness.Big);
+ ushort dataSize = _buffer.ReadUInt16(Endianness.Big);
+ ushort combinedCheck = _buffer.ReadUInt16(Endianness.Big);
+ ushort check0 = _buffer.ReadUInt16(Endianness.Big);
+ ushort check1 = _buffer.ReadUInt16(Endianness.Big);
+ ushort check2 = _buffer.ReadUInt16(Endianness.Big);
+ _header = new PacketHeader(id, pf0, keyRotDelta, dataSize, combinedCheck, check0, check1, check2);
+ Console.WriteLine(_header.ToLogText());
+ // Update the rolling key index.
+ if (_header.KeyRotDelta != 0)
+ {
+ _readKeyRot = _header.KeyRotDelta * (_readKeyRot + 1);
+ }
+ }
+
+ if (_header == null && _buffer.Size - _buffer.Position == InitPacketSize)
+ {
+ byte[] payload = _buffer.ReadBytes(InitPacketSize);
+ _logger.Debug($"Ignoring Data: {Util.ToHexString(payload, ' ')}");
+ }
+
+ if (_header != null && _buffer.Size - _buffer.Position >= _header.DataSize)
+ {
+ byte[] packetData = _buffer.ReadBytes(_header.DataSize);
+ packetData = Decrypt(packetData, _readKeyRot,
+ out ushort combinedCheck, out ushort check0,
+ out ushort check1, out ushort check2);
+
+ IBuffer buffer = BufferProvider.Provide(packetData);
+ MhfPacket packet = new MhfPacket(_header, buffer);
+ packets.Add(packet);
+ _header = null;
+ read = _buffer.Position != _buffer.Size;
+ }
+ }
+
+ if (_buffer.Position == _buffer.Size)
+ {
+ Reset();
+ }
+ else
+ {
+ _position = _buffer.Position;
+ }
+
+ return packets;
+ }
+
+ private void Reset()
+ {
+ _header = null;
+ _position = 0;
+ _buffer = null;
+ }
+
+
+ public void Test()
+ {
+ byte[] me = Util.FromHexString(
+ "010201011E952C4F6D66366B414C5765553248535635755DD6002F0C75306D68662D672E6A70000C6C302E6D682D672E6A7000143130362E3138352E342E36313A3533333130002175A5E90000005DD0AE99000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000028B3C424F44593E3C43455445523E3C53495A455F333E3C435F3E918D93FC8DB03490E7969C8DB09290AC82C582A882BD82DF82B58FAC9482663939399687947A957A81493C423E3C424F44593E91E631343789F18E906C8DD581758F9F82BF836C83528282E782CC92A790ED8FF38176936F988DD53C42523E3C424F44593E3C42523C424F44593E3C42523E3C424F44593C4C4546543E3C435F373E3C53495A5F323E8DA189F182C58DC58CE382C6C882E98EEB906C8DD581758F9F82BF6C835282A982E782CC92A790ED8FF3768A4A8DC381493C42523E3C424F443E3C42523E3C424F44593E3C4C45463E3C435F333E3C53495A455F323E81936F985E8DD58A4A8DC38AFA8AD4813C435F373E3C42523E3C424F44593E318C8E323093FA2890852920816020318C8E323793FA2890852983818393658369839383588A4A8E6E82DC82C5435F373E3C42523E3C424F44593E3C523E3C424F44593E91E631343789F1EB906C8DD582C582CD81418354815B6F815B918D93FC8DB0909482C9899EB682C481413C42523E3C424F44593E435F353E82A882BD82DF82B58FAC9482663939399687815E8A6C93BE9166DE90943130947B81413C42523E3C4244593E8F9F82BF9167834E834783586782F08343837883938367834E834758836782C682B582C4947A904D3C43373E82C882C73C42523E3C424F445993C195CA82C8964A8FDC82F092698A934982C982B2977088D382B582C48282E882DC82B781423C42523E3C424F593E3C42523E3C424F44593E836E83835E815B82CC8A46976C81418DC58C82CC8EEB906C8DD582C982BA82D08E89C182B582C482AD82BE82B382A28100000000000000000E2A64736D63000C00003D0000008181000000000000000000816A000000000000280000006900000000000021000000814900000000002F000000815E000000000000000000817B0000000000002600000095000000000000825000003100000000000082DA0000837B0000CE00DE00D9DE00837ADE0082D9814A837A814A00814A0000000082D700008378000000DE0082D6DE008377DE0082D6814A00814A8377814A0000000082C50000660000C300DE0082C4DE008365DE00C4814A8365814AC300814A81A7814AA7DE0089B3DE0089B3814A00000000D1000083720000CB00DE0082D0DE0071DE0082D0814A8371814ACB00814A00000082C7000083680000C400DE00C6DE008367DE0082C6814A8367814A00814A84B0DE0084B0814A84A5DE00A5814A0000000082CE0000836F000000DE0082CDDE00836EDE0094AADE00CD814A836E814ACA00814A94AA814A00000082C2DE00836400008363DE00C2814A8363814AC200DE00C200814AC3000082C1DE008362DE00AF00DE00C1814A8362814AAF00814A00000000D4000083750000CC00DE008394000000DE0082A4814A82A4DE008345DE0000DE0082A3DE0082D3DE008374DE0000814A0000000082C000008361000000DE0082BFDE008360DE0082BF814A60814AC100814A90E7814A90E7DE0000000082BE0000835F0000C000DE00BDDE00835EDE00975BDE0082BD814A5E814AC000814A975B814A00000000BC0000835D0000BF00DE0082BBDE005CDE0082BB814A835C814ABF00814A93DE008393814ADD00814ADD00DE008ADE00D800DE00D800814A838A814A00000082BA0000BE00DE0082B9DE005ADE0082B9814A835A814ABE00814A5B00000000000082B800008359000000DE0082B7DE008358DE0082B7814A58814ABD00814A0000000082B60000570000BC00DE0082B5DE008356DE00B5814A8356814ABC00814A00000000B4000083550000BB00DE0082B3DE0054DE0082B3814A8354814ABB00814A00000082B2000083530000BA00DE00B1DE008352DE0082B1814A8352814A00814A0000000082B000008351000000DE0082AFDE008350DE0082AF814A50814AB900814A8396DE008396814A00000082AE0000834F0000B800DE00ADDE00834EDE0082AD814A834E814A00814A0000000082AC0000834D000000DE0082ABDE00834CDE0082AB814A4C814AB700814A0000000082AA00004B0000B600DE008395DE00834ADE00A9DE0097CDDE008395814A834A814AA9814A97CD814AB600814A00000000F0000083920000A600000000000000ED0000838F0000DC000000838E000000000082EB0000838D0000DB000000A000008CFB00000000000082EA00008C0000DA0000000000000082E900008B0000D90000000000000082E800008A0000D80000000000000082E70000890000D70000000000000082E60000880000D6000000AE00000082E500008700000000000082E4000083860000000000AD00000082E300008385000000000082E2000083840000D400000000000082E100008383000000000000E0000083820000D300000000000000DF000083810000D20000004D00450000000082DE000083800000D100000000000082DD0000837E0000D000000000000082DC0000837D0000CF00000000000082D90000837A0000CE00000000000082D6000083770000CD00000000000082D3000083740000CC00000000000082D0000083710000CB00000000000082CD0000836E0000CA000000AA00000000000082CC0000836D00000000000000000082CB0000836C00000000000000000082CA0000836B00000000000000000082C90000836A000000000093F100000000000082C80000690000C50000000000000082C60000670000C400000084B0000084A5000000000082C4000083650000C3000000A7000089B300000000000082C20000630000C200000082C10000836200000000000000000082BF00008360000000000090E700000000000082BD00005E0000C0000000975B000000000000BB0000835C0000BF00000000000000B90000835A0000BE00000000000000B7000083580000BD00000000000000B5000083560000BC00000000000000B3000083540000BB00000000000000B1000083520000BA00000000000000AF000083500000B90000008396000000000082AD0000834E0000B800000000000082AB0000834C0000B700000000000082A90000834A0000B600000095000097CD00000000000082A80000490000B5000000AB00000082A700004800000000000082A6000083470000000000AA00000082A50000834600004800000000000082A4000083450000000000A900000082A300008344000000000082A2000083430000B200000000000082A100008342000000000000000000B1000000829F000082A000004000008341000000000000815B00005C0000815D0000816000002D0000007C0000B000000088EA000000000000000000825800000000000038000000570000000000003700000082560000000000360000008255000000000000000000825400000000000034000000530000000000003300000082520000000000320000008251000000000000DB0000837C0000CE00DF0082D9DF007ADF00837A818B82D9818BCE00818B00000082D8000083790000CD00DF00D6DF008377DF008377818B82D6818B00818B0000000082D500008376000000DF0082D3DF008374DF008374818BD3818BCC00818B0000000082D20000730000CB00DF0082D0DF008371DF0071818B82D0818BCB00818B00000000CF000083700000CA00DF0082CDDF006EDF00836E818B82CD818BCA00818BAADF0094AA814B00000000827900009A00005A0000007A00000083A4000000000082780000829900005900000000000083B20000845400008485000000000082770000829800005800000000000083B4000083D40000845600007E000084870000875D000000000000760000829700005700000077000000D6000084590000848A0000848B000000000082750000829600005600000000000083CB000083D200008758000000000082740000829500005500000000000083CA000081BE000000000000730000829400005400000074000000B1000083D10000845300008484000000000024000000530000007300000090000081E700008272000082930000000000827100008292000052000000000000846000008491000000000000700000829100005100000071000000000000826F0000829000005000000000000083AF000083CF00008451000082000000000000826E0000828F00000000006F000000819B000083AD0000CD0000844F00008480000081FC00005A000030000000824F0000000000006D0000828E00004E0000006E000000AB000083C50000DD00000082F1000093000000000000826C0000828D00000000006D00000083AA0000844D00007D000000000000826B0000828C00000000006C000000879800007C000000000000826A0000828B00004B00000000000083A8000083C80000844B00007B00000000000082690000828A00000000006A0000000000000082680000890000490000006900000083A700005400000000000082670000828800000000006800000083A50000844E00007E0000000000008265000082860000000000660000000000000082660000870000470000006700000000000000640000828500004500000065000000A3000083C3000084450000844600007500008476000081B80000000000006300008284000044000000640000000000004300000082830000630000005200008483000082620000818E000000000042000000826100008282000000000083C0000083A0000084420000720000848C0000848E000081F30000000000270000008166000000000000000000826000006100000082810000BF0000400000008197000084700000F0000084400000839F00000000000000000081680000000000002500000093000000000000000000006E616D0016000002000000937AFFFF97EAFFFF00FFFF0400000082B78B0182A9A801C6660182EBFC000000FFFF0200000073FFFF8F97FFFF0000FFFF03000000A2C401826D870282DBF3010000FFFF0000008266C2024100EF02826D870269A9024100EF020000FFFF04000000C46C0182A2C401826D870282B1970100FFFF0300000082DC380182BB8301B197010000FFFF0300000082BF7901BB830182B197010000FFFF02000000ADFFFF9390FFFF0000FFFF04000000CF170282A2C40182CF1702826D870200FFFF03000000965CFFFF82A9A80163FFFF0000FFFF0400000082B98701ADA00182EBFC0082B78B010000FFFF00000082CE4D0082BD7E01815BD201A2FFFF0000FFFF0500000082DC3801E80A0182D34401A700CB0182C8620100FFFF0400000082D5050282C27201B58F01815BD2010000FFFF04000000B1970182A9A80182A2C401826D870200FFFF0400000082C46C0182A2C4016D870282DBF3010000FFFF04000000D13700815BD20182BF790182ADA00100FFFF0200000090ABFFFF977EFFFF00FFFF0300000082C8620182BF7901B78B010000FFFF0400000082EBFC00E80A0182B19701826D87020000FFFF00000082C46C0182A2C40182DE3001DBF3010000FFFF0400000082C52A00A9A80182DC380182E70E010000FFFF00000082CF170282A2C40182B8A500E80A010000FFFF0400000082DC38016D870282A9A80182B78B010000FFFF0000004100EF028263D302826C910268AE02826D87028268AE0224005C02735302827164024100EF02827353026E7902827164020000FFFF06000000A8AE018B71FFFF976CFFFF918AFFFF6BFFFF8EBAFFFF0000FFFF02000000F2FFFF95A8FFFF0000FFFF04000000D2FFFF967BFFFF8C9BFFFF8E4FFFFF00FFFF0400000092D2FFFF967BFFFFB0FFFF8D4FFFFF0000FFFF04000000ACFFFF96ECFFFF8B60FFFF93BFFFFF00FFFF0300000082716402826C91027353020000FFFF040000009099FFFF59FFFF815BD20193BFFFFF0000FFFF00000092D2FFFF967BFFFF97C7FFFF4FFFFF0000FFFF0300000093A1FFFFAAFFFF9776FFFF0000FFFF040000005BD20190A3FFFF91D7FFFF94CDFFFF00FFFF050000008FACFFFF9388FFFF54FFFF91BEFFFF9859FFFF0000FFFF00000091E5FFFF9683FFFF0000FFFF0000008A6FFFFF90C1FFFF8DDCFFFF00FFFF020000009FAFFFFF92B0FFFF00FFFF020000009683FFFF96F2FFFF00FFFF0300000092AAFFFF9081FFFFABA4010000FFFF0300000082A9A801D505029859FFFF0000FFFF0100000079FFFF0000FFFF01000000817AFFFF00FFFF020000008941FFFF8C73FFFF00FFFF020000009675FFFF8B4EFFFF00FFFF0200000089BAFFFF979FFFFF00FFFF0600000082A8AE0182A4BD01DE3001905EFFFF979DFFFF8BB3FFFF00FFFF090000008269A9024100EF026F7002826AA0028268AE02826B99026B99028264C702827164020000FFFF0000008B43FFFF88E1FFFF82A2C40100FFFF040000008B43FFFF82BF7901AAE60082A2C4010000FFFF04000000ABA40182BF790182AAE60082A2C40100FFFF030000008B43FFFF8BB6FFFFA2C4010000FFFF0400000090B8FFFF5FFFFF9496FFFF8EE3FFFF0000FFFF000000926DFFFF8C62FFFF9278FFFFEA02010000FFFF0500000082BF7901A6B50182A8AE0182ADA00182EA020100FFFF0200000096DAFFFF88C3FFFF00FFFF0200000096BEFFFF96D3FFFF00FFFF05000000A700CB0182ABA401DF2B0182ADA00182E70E010000FFFF00000082EBFC00826D870282CF1702E80A010000FFFF02000000E19AFFFF61FFFF0000FFFF0300000082E70E01A2C4019561FFFF0000FFFF0200000092FFFF9273FFFF0000FFFF01000000EAFFFF0000FFFF0300000082BF79016D870282CE4D000000FFFF01000000FBFFFF0000FFFF0200000098FBFFFF43FFFF0000FFFF02000000936AFFFF45FFFF0000FFFF020000008CEEFFFF48FFFF0000FFFF02000000E6CBFFFFAFFFFF0000FFFF02000000E271FFFFBDFFFF0000FFFF0200000094F1FFFF6CFFFF0000FFFF030000009056FFFFBDFFFF96AFFFFF0000FFFF04000000C1FFFF8EEAFFFF9594FFFF978EFFFF00FFFF0500000082CE4D0082A9A801BF790182E61201826D87020000FFFF00000082BF790182E61201826D8702F6FFFF0000FFFF0500000082C66601E9060182B197019597FFFF9843FFFF00FFFF0400000082C6660182E90601B197018FECFFFF0000FFFF0300000000CB0182C8620182E906010000FFFF00000082A4BD01826D870282B1970100FFFF0200000096D1FFFF9382FFFF00FFFF030000008D95FFFF826D8702DA18000000FFFF040000009573FFFFC2FFFF9047FFFF96AFFFFF0000FFFF0000009484FFFF8F97FFFF0000FFFF00000088FAFFFF9484FFFF0000FFFF0000008BADFFFF8AADFFFF0000FFFF00000082E2200182E80A0182DC38016D87020000FFFF0300000082DC38016D870282B197010000FFFF03000000A8AE0182DF2B0182B197010000FFFF00000082BF7901826D870282B1970100FFFF0300000082BF7901826D8702DBF3010000FFFF0300000082BF79016D870282DA18000000FFFF04000000B9870182C2720182ADA00182B78B0100FFFF0300000024005C028264C7027730020000FFFF020000009484FFFF74FFFF0000FFFF020000009483FFFF74FFFF0000FFFF0300000082EA0201A2C40182D505020000FFFF04000000B78B0182D8FC0182E9060182DC380100FFFF0500000082ADA00182E80A01C6660182E80A0182B78B010000FFFF00000082D3440182A6B50182E70E01BF790182A8AE010000FFFF03000000B4B700815BD20196CBFFFF0000FFFF00000082B4B700815BD20182DF2B016D87020000FFFF0400000082B987016D870282B8A50082E80A010000FFFF00000082A8AE0182C8620182C95D015BD2010000FFFF030000008FE1FFFF51FFFF8ED2FFFF0000FFFF0400000065BD0282744C024300D802826AA00200FFFF0400000082D34401A700CB01C2720182ADA0010000FFFF05000000BF790182E22001826D870282B19701EBFC000000FFFF050000004200E00268AE02827353024300D8028267B50200FFFF040000004300D80282744C026D8702827353020000FFFF050000006F70028264C702826D87028268AE02005C020000FFFF0300000082D8FC01C95D0182B78B010000FFFF060000007544024100EF028266C2028268AE026D87024100EF020000FFFF03000000CE4D0082ACDD0082C862010000FFFF00000082D46700A700CB0182ACDD00C862010000FFFF05000000826F7002744C0224005C0224005C028278280200FFFF0200000090B8FFFF8974FFFF00FFFF020000008E80FFFF82CB550100FFFF020000008E45FFFF82B78B0100FFFF0100000095B3FFFF0000FFFF0000008998FFFF95A8FFFF0000FFFF00000082BF7901826D870282DBF301B197010000FFFF0400000082DBF301B1970182BF7901826D87020000FFFF00000082B3930182B987018E71FFFF00FFFF020000008941FFFF96D1FFFF00FFFF020000008BCAFFFF8BE0FFFF00FFFF0400000082A9A80182D50502B19701826D87020000FFFF04000000A9A80182D5050282B1970182BB830100FFFF0400000082A9A80182D505026D870282B197010000FFFF0600000000D8024100EF02826F70024300D8026E7902826C91020000FFFF0600000000D8024100EF02826F70024300D8026E7902826D87020000FFFF0600000000D8024100EF02826F7002826AA0026E7902826C91020000FFFF0600000000D8024100EF02826F7002826AA0026E7902826D87020000FFFF060000006AA0024100EF02826F70024300D8026E7902826C91020000FFFF060000006AA0024100EF02826F70024300D8026E7902826D87020000FFFF060000006AA0024100EF02826F7002826AA0026E7902826C91020000FFFF060000006AA0024100EF02826F7002826AA0026E7902826D87020000FFFF0900000066C2024100EF0282DF2B01826C910200EF0224005C02827353028264C7027164020000FFFF0A0000008266C20200EF0282DF2B0124005C0282744C026F7002826F7002826E7902827164027353020000FFFF0600000024005C0278280224005C02827353028264C7026C91020000FFFF0400000082B58F01B78B0182C46C0182DE30010000FFFF0000008264C702827544028264C7026D8702827353020000FFFF04000000A2C40182D72100826D870282C6660100FFFF0500000024005C028273530200EF028265BD028265BD020000FFFF00000082B78B0182BD7E0182C27201D344010000FFFF08000000826E790265BD028265BD028268AE024300D80268AE024100EF02826B99020000FFFF00000082A8AE0182D3440182A2C401B58F0182E2200182E906010000FFFF00000024005C0282744C02826F70026F7002826E7902827164028273530200FFFF0400000082B3930182DBF3015BD20182C666010000FFFF0500000000EF028263D302826C91028268AE026D87020000FFFF04000000A700CB01C7400082DD3401826D87020000FFFF000000A700CB0182C7400082DD3401BB83010000FFFF04000000A700CB01C7400082DD340182C95D010000FFFF000000A700CB0182C7400082DD3401C95D010000FFFF02000000895EFFFF63FFFF0000FFFF0400000082A4BD016D870282A6B50182A2C4010000FFFF0000008AC7FFFF979DFFFF0000FFFF00000082A9A801826D870282E80A0100FFFF030000008AD6FFFF8C57FFFFD2FFFF0000FFFF030000008A4AFFFFADFFFF8ED2FFFF0000FFFF04000000A8AE01926DFFFF82E70E0182B9870100FFFF0400000082A8AE0182B58F01E70E0182B987010000FFFF0200000090FFFF926DFFFF0000FFFF03000000D4FFFF88C0FFFF9577FFFF0000FFFF00000082E2200182E80A0182BF79016D87020000FFFF0300000082E22001ADA00182B4B7000000FFFF04000000D2FFFF967BFFFF8F74FFFF8D4FFFFF00FFFF0300000096D8FFFF967BFFFF63D3020000FFFF0400000096D8FFFF7BFFFF97B4FFFF8CC8FFFF0000FFFF0000008B7BFFFF89BAFFFF4100EF026F70020000FFFF040000008B7BFFFFBAFFFF8B50FFFF8EF7FFFF0000FFFF000000826C91028267B5028265BD025BD2018266C20282BF7901815BD201DE30010000FFFF06000000826C910267B5028265BD0282BF7901815BD201DE30010000FFFF040000008B43FFFFAED40082E9060182A2C4010000FFFF00000082ABA40182AED40082E90601A2C4010000FFFF0200000090B8FFFF96FFFF0000FFFF0200000088FAFFFF90FFFF0000FFFF020000009641FFFF50FFFF0000FFFF03000000945DFFFF8CFFFF898AFFFF0000FFFF0200000054FFFF93AAFFFF0000FFFF03000000DF2B0182ADA00182E70E010000FFFF00000093F7FFFF965FFFFF0000FFFF000000956EFFFF93FBFFFF0000FFFF00000082BE800082C2720182BF7901EDF70082A2C40182D344010000FFFF0000009857FFFF0000FFFF03000000C27201826D870282DA18000000FFFF0000008B68FFFF82E80A010000FFFF0000009181FFFF9852FFFF0000FFFF00000082A2C40182DC380182E70E01BF790182A8AE010000FFFF05000000A8AE0182C8620182C95D0182B78B01C666010000FFFF0200000088A4FFFF74FFFF0000FFFF020000008AE7FFFFCBFFFF0000FFFF0200000095D0FFFF5BFFFF0000FFFF020000009790FFFFF0FFFF0000FFFF0300000082B98701DE300182B58F010000FFFF030000004FFFFF82C2720182EBFC000000FFFF000000E3E8FFFF96E5FFFF0000FFFF00000093F7FFFF92D9FFFF0000FFFF00000090ABFFFF8AB4FFFF91D1FFFF00FFFF0500000082A8AE01815BD201AAE60082B8A50082DE30010000FFFF0000008EA9FFFF88D4FFFF0000FFFF00000093FBFFFF97D6FFFF0000FFFF0000009278FFFF9852FFFF0000FFFF0000008DD7FFFF96AFFFFF0000FFFF0000008B87FFFF96AFFFFF0000FFFF00000094D8FFFF91B0FFFF0000FFFF000000966BFFFF914EFFFF0000FFFF00000093ECFFFF914EFFFF0000FFFF0000008E4FFFFF8D91FFFF906CFFFF00FFFF0200000097BDFFFF904AFFFF00FFFF0200000096B2FFFF90B8FFFF00FFFF010000008FA8FFFF0000FFFF0000008FA9FFFF9577FFFF0000FFFF0000008F97FFFF8941FFFF0000FFFF0000008EE8FFFF88FAFFFF0000FFFF0000008D67FFFF96D1FFFF906CFFFF00FFFF020000009849FFFF8F95FFFF00FFFF020000008987FFFF8CF0FFFF00FFFF0400000082ADA00182EBFC006D870282DA18000000FFFF0300000000CB0182DF2B018CF6FFFF0000FFFF00000082A2C40182BD7E018CF6FFFF00FFFF040000009553FFFF8AD1FFFFC52A0082D467000000FFFF04000000E90601826D870282D8FC01826D870200FFFF020000008941FFFF9594FFFF00FFFF020000008941FFFF904FFFFF00FFFF0200000088FAFFFF8D73FFFF00FFFF0400000082BF7901826D8702BF7901826D87020000FFFF020000006AFFFF8DAAFFFF0000FFFF05000000A8AE018F95FFFF82AF9B018EEBFFFF6CFFFF0000FFFF0300000082BF79016D870296D1FFFF0000FFFF020000004EFFFF906CFFFF0000FFFF03000000ADA001826D870282C95D010000FFFF0000009099FFFF8959FFFF826F700200FFFF0200000093B6FFFF92E5FFFF00FFFF0200000095EFFFFF8C73FFFF00FFFF020000008BE0FFFF8BCAFFFF00FFFF0400000082A8AE0182C27201CF170282A2C4010000FFFF03000000BF790182ADA00182D137000000FFFF000000A700CB0182C8620182E9060100FFFF03000000A700CB0182CA5901B78B010000FFFF0200000093FBFFFFF1FFFF0000FFFF01000000E453FFFF00FFFF0200000090C2FFFF8AADFFFF00FFFF0200000096ECFFFF8AADFFFF00FFFF0200000095D6FFFF94E9FFFF00FFFF020000008FACFFFF95D6FFFF00FFFF0200000091E5FFFF95D6FFFF00FFFF0300000082D1370082C27201BF79010000FFFF0300000082A4BD016D870282BF79010000FFFF0200000070FFFF906CFFFF0000FFFF0200000073FFFF8ABFFFFF0000FFFF04000000B9870182ADA00182CD4C0182E70E0100FFFF0400000082744C02826D87026AA002826E79020000FFFF04000000BF7901826D870282A9A80182B78B0100FFFF0400000082CF1702826D8702C46C0182A2C4010000FFFF02000000D6FFFF8AEDFFFF0000FFFF04000000ABA401826D870282BD7E0182DC380100FFFF05000000826C91024100EF026D8702826AA002826E79020000FFFF00000082B78B0182C66601815BD201A9A801815BD2010000FFFF03000000BF7901815BD20182C666010000FFFF00000082AAE600826D870282B6AE00E220010000FFFF000000006D736700080000020000009675FFFF8B4EFFFF00FFFF030000008B43FFFF88E1FFFFA2C4010000FFFF040000008B43FFFFBF790182AAE60082A2C4010000FFFF00000082ABA40182BF790182AAE600A2C4010000FFFF030000008B43FFFFB6FFFF82A2C4010000FFFF04000000B8FFFF905FFFFF9496FFFF8EE3FFFF00FFFF04000000926DFFFF8C62FFFF78FFFF82EA02010000FFFF05000000BF790182A6B50182A8AE0182ADA001EA02010000FFFF0200000096DAFFFFC3FFFF0000FFFF0200000096BEFFFFD3FFFF0000FFFF05000000A700CB01ABA40182DF2B0182ADA00182E70E0100FFFF0400000082EBFC00826D8702CF170282E80A010000FFFF020000009AFFFF9561FFFF0000FFFF03000000E70E0182A2C4019561FFFF0000FFFF0000009492FFFF9273FFFF0000FFFF000000E6EAFFFF0000FFFF03000000BF7901826D870282CE4D000000FFFF00000098FBFFFF0000FFFF02000000FBFFFF9943FFFF0000FFFF020000006AFFFF8E45FFFF0000FFFF02000000EEFFFF9048FFFF0000FFFF02000000CBFFFF96AFFFFF0000FFFF0200000071FFFF91BDFFFF0000FFFF02000000F1FFFF906CFFFF0000FFFF0300000056FFFF95BDFFFF96AFFFFF0000FFFF00000093C1FFFF8EEAFFFF9594FFFF8EFFFF0000FFFF0500000082CE4D00A9A80182BF790182E61201826D870200FFFF0400000082BF790182E612016D87028CF6FFFF0000FFFF05000000C6660182E9060182B197019597FFFF43FFFF0000FFFF0400000082C66601E9060182B197018FECFFFF0000FFFF00000096D1FFFF9382FFFF0000FFFF0000008D95FFFF826D870282DA180000FFFF040000009573FFFF89C2FFFF47FFFF96AFFFFF0000FFFF0200000084FFFF8F97FFFF0000FFFF02000000FAFFFF9484FFFF0000FFFF02000000ADFFFF8AADFFFF0000FFFF04000000E2200182E80A0182DC3801826D870200FFFF0300000082DC3801826D8702B197010000FFFF0300000082A8AE01DF2B0182B197010000FFFF03000000BF7901826D870282DBF3010000FFFF00000082BF7901826D870282DA180000FFFF0400000082B9870182C27201ADA00182B78B010000FFFF03000000005C028264C702827730020000FFFF0000009484FFFF8F74FFFF0000FFFF0000009483FFFF8F74FFFF0000FFFF00000082EA020182A2C40182D5050200FFFF0400000082B78B0182D8FC01E9060182DC38010000FFFF05000000ADA00182E80A0182C6660182E80A01B78B010000FFFF0500000082D34401A6B50182E70E0182BF790182A8AE0100FFFF0300000082B4B700815BD201CBFFFF0000FFFF0400000082B4B7005BD20182DF2B01826D87020000FFFF00000082B98701826D870282B8A500E80A010000FFFF0400000082A8AE01C8620182C95D01815BD2010000FFFF0000008265BD0282744C024300D8026AA0020000FFFF0400000082D3440100CB0182C2720182ADA0010000FFFF00000082BF790182E22001826D8702B1970182EBFC000000FFFF0500000000E0028268AE02827353024300D80267B5020000FFFF040000004300D802744C02826D8702827353020000FFFF000000826F70028264C702826D870268AE0224005C020000FFFF03000000D8FC0182C95D0182B78B010000FFFF000000827544024100EF028266C20268AE02826D87024100EF020000FFFF00000082CE4D0082ACDD0082C8620100FFFF0400000082D46700A700CB01ACDD0082C862010000FFFF050000006F700282744C0224005C0224005C027828020000FFFF0200000090B8FFFF74FFFF0000FFFF0400000082BF79016D870282DBF30182B197010000FFFF00000088FAFFFF9790FFFF0000FFFF0000009641FFFF9550FFFF0000FFFF0000008B54FFFF93AAFFFF0000FFFF00000093F7FFFF965FFFFF0000FFFF000000956EFFFF93FBFFFF0000FFFF00000082BE800082C2720182BF7901EDF70082A2C40182D344010000FFFF0000009181FFFF9852FFFF0000FFFF00000082A2C40182DC380182E70E01BF790182A8AE010000FFFF05000000A8AE0182C8620182C95D0182B78B01C666010000FFFF0200000088A4FFFF74FFFF0000FFFF020000008AE7FFFFCBFFFF0000FFFF020000009790FFFFF0FFFF0000FFFF02000000E3E8FFFFE5FFFF0000FFFF0200000093F7FFFFD9FFFF0000FFFF0300000090ABFFFFB4FFFF91D1FFFF0000FFFF05000000A8AE01815BD20182AAE60082B8A500DE30010000FFFF020000008EA9FFFFD4FFFF0000FFFF0200000093FBFFFFD6FFFF0000FFFF020000009278FFFF52FFFF0000FFFF0200000097BDFFFF4AFFFF0000FFFF0200000096B2FFFFB8FFFF0000FFFF010000008FA8FFFF00FFFF020000008FA9FFFF9577FFFF00FFFF020000008F97FFFF8941FFFF00FFFF020000008EE8FFFF88FAFFFF00FFFF020000008987FFFF8CF0FFFF00FFFF0400000082E90601826D8702D8FC01826D87020000FFFF0200000041FFFF9594FFFF0000FFFF0200000041FFFF904FFFFF0000FFFF02000000FAFFFF8D73FFFF0000FFFF020000006AFFFF8DAAFFFF0000FFFF03000000BF7901826D870296D1FFFF0000FFFF00000093B6FFFF92E5FFFF0000FFFF00000095EFFFFF8C73FFFF0000FFFF00000093FBFFFF8EF1FFFF0000FFFF000000E453FFFF0000FFFF02000000C2FFFF8AADFFFF0000FFFF02000000ECFFFF8AADFFFF0000FFFF00000000104E200031736B30302445532E537457795159485A70547677724162734E6D685661334F4F6576467733617364325A3430632D00CA1100014E200000FD8D2F000000003DDFCC945DD4AD5CDDCA90020000000A00000004080A030902080507");
+
+
+ byte[] packetData = Util.FromHexString(
+ "444C54534B45595349474E3A313030007465737400736B3030242E546676664B2E487646356E6C317436624338555843536352766538524249704D4D4E395537524C786A6B2D0000");
+
+
+ byte[] packetData1 = Enc(packetData, (byte) (995118 & 0xFF));
+ Console.WriteLine("Enc " + Util.ToHexString(packetData1, ' '));
+
+ byte[] packetData2 = Encrypt(packetData, 2,
+ out ushort combinedCheck, out ushort check0,
+ out ushort check1, out ushort check2);
+ Console.WriteLine("Enc " + Util.ToHexString(packetData2, ' '));
+
+
+ // packetData = Decrypt(packetData);
+ // Console.WriteLine("Dec " + Util.ToHexString(packetData, ' '));
+ // Console.WriteLine("Dec " + Util.ToAsciiString(packetData, true));
+ }
+
+ private byte[] Decrypt(byte[] input, uint key, out ushort cc, out ushort c0, out ushort c1, out ushort c2)
+ {
+ uint size = (uint) input.Length;
+ byte[] output = new byte[size];
+
+ byte unkCryptkeyRotArg = (byte) ((key >> 1) % 999983);
+ uint unkDerivedCryptkeyRot = (uint) (size * (unkCryptkeyRotArg + 1));
+
+ byte sharedBufIdx = 1;
+ uint accumulator0 = 0;
+ uint accumulator1 = 0;
+ uint accumulator2 = 0;
+
+ for (int i = 0; i < size; i++)
+ {
+ // Do the decryption for this iteration
+ byte oldSharedBufIdx = sharedBufIdx;
+ byte tIdx = (byte) (input[i] ^ SharedCryptKey[sharedBufIdx]);
+ uint decKeyByte = DecryptKey[tIdx];
+ sharedBufIdx = (byte) ((unkDerivedCryptkeyRot >> 10) ^ decKeyByte);
+
+ // Update the checksum accumulators.
+ accumulator0 = (uint) (accumulator0 + (tIdx << (i & 7)));
+ accumulator1 = accumulator1 + decKeyByte;
+ accumulator2 = (uint) (accumulator2 + (oldSharedBufIdx * sharedBufIdx));
+
+ // Append the output.
+ output[i] = sharedBufIdx;
+
+ // Update the key pos for next iteration.
+ unkDerivedCryptkeyRot = 0x4FD * (unkDerivedCryptkeyRot + 1);
+ }
+
+ ushort combinedCheck = (ushort) (accumulator1 + (accumulator0 >> 1) + (accumulator2 >> 2));
+ ushort check0 = (ushort) (accumulator0 ^ (accumulator0 >> 16));
+ ushort check1 = (ushort) (accumulator1 ^ (accumulator1 >> 16));
+ ushort check2 = (ushort) (accumulator2 ^ (accumulator2 >> 16));
+
+ cc = combinedCheck;
+ c0 = check0;
+ c1 = check1;
+ c2 = check2;
+ return output;
+ }
+
+ private byte[] Encrypt(byte[] input, uint key, out ushort cc, out ushort c0, out ushort c1, out ushort c2)
+ {
+ uint size = (uint) input.Length;
+ byte[] output = new byte[size];
+
+ byte unkCryptkeyRotArg = (byte) ((key >> 1) % 999983);
+ uint unkDerivedCryptkeyRot = (uint) (size * (unkCryptkeyRotArg + 1));
+
+ byte sharedBufIdx = 1;
+ uint accumulator0 = 0;
+ uint accumulator1 = 0;
+ uint accumulator2 = 0;
+
+ for (int i = 0; i < size; i++)
+ {
+ // Do the encryption for this iteration
+ byte encKeyIdx = (byte) ((unkDerivedCryptkeyRot >> 10) ^ input[i]);
+ unkDerivedCryptkeyRot = 0x4FD * (unkDerivedCryptkeyRot + 1);
+ byte encKeyByte = EncryptKey[encKeyIdx];
+
+ // Update the checksum accumulators.
+ accumulator2 = (uint) (accumulator2 + (sharedBufIdx * input[i]));
+ accumulator1 = accumulator1 + encKeyIdx;
+ accumulator0 = (uint) (accumulator0 + (encKeyByte << (i & 7)));
+
+ // Append the output.
+ output[i] = (byte) (SharedCryptKey[sharedBufIdx] ^ encKeyByte);
+
+ // Update the shared_buf_idx for the next iteration.
+ sharedBufIdx = input[i];
+ }
+
+ ushort combinedCheck = (ushort) (accumulator1 + (accumulator0 >> 1) + (accumulator2 >> 2));
+ ushort check0 = (ushort) (accumulator0 ^ (accumulator0 >> 16));
+ ushort check1 = (ushort) (accumulator1 ^ (accumulator1 >> 16));
+ ushort check2 = (ushort) (accumulator2 ^ (accumulator2 >> 16));
+
+ cc = combinedCheck;
+ c0 = check0;
+ c1 = check1;
+ c2 = check2;
+ return output;
+ }
+
+ byte[] Enc(byte[] input, byte a5)
+ {
+ byte v12;
+ uint v13;
+ uint v14;
+ uint inputSize = (uint) input.Length;
+ byte outputIndex = 0;
+ byte inputIndex = 0;
+ uint v5 = inputSize * (a5 + 1u);
+ uint v6 = 0;
+ uint v7 = 0;
+ uint v8 = 0;
+ uint v9 = 0;
+ byte v10 = 1;
+ uint v19 = 0;
+ uint v18 = 0;
+ uint v17 = 0;
+ byte[] output = new byte[inputSize];
+ if (inputSize > 0)
+ {
+ do
+ {
+ v12 = input[inputIndex];
+ v19 += (uint) v10 * input[inputIndex];
+ v13 = (byte) ((v5 >> 10) ^ input[inputIndex]);
+ v18 += v13;
+ v13 = ReplaceByte(0, v13, EncryptKey[v13]);
+ v14 = (uint) ((byte) v13 << ((int) v9 & 7));
+ v9++;
+ v5 = 1277 * (v5 + 1);
+ v17 += v14;
+ output[outputIndex] = (byte) (SharedCryptKey[v10] ^ v13);
+ inputIndex = (byte) (outputIndex + 1);
+ v10 = v12;
+ ++outputIndex;
+ } while (v9 < inputSize);
+
+ v6 = v19;
+ v7 = v18;
+ v8 = v17;
+ }
+
+ uint a6 = v6 ^ (v6 >> 16);
+ uint a4 = v7 ^ (v7 >> 16);
+ uint result = (uint) (v7 + ((int) v8 >> 1) + ((int) v6 >> 2));
+ uint a2 = v8 ^ (v8 >> 16);
+ ushort check3 = (ushort) a6;
+ ushort check2 = (ushort) a4;
+ ushort check1 = (ushort) a2;
+ ushort res = (ushort) result;
+ byte resB = (byte) result;
+
+ return output;
+ }
+
+ uint ReplaceByte(uint index, uint value, byte replaceByte)
+ {
+ var shiftBits = 8 * index;
+ var mask = ~(0xff << (int) shiftBits);
+ return (uint) (value & mask | (replaceByte << (int) shiftBits));
+ }
+ }
+}
diff --git a/Mhf.Server/Packet/PacketHeader.cs b/Mhf.Server/Packet/PacketHeader.cs
new file mode 100644
index 0000000..a12ef04
--- /dev/null
+++ b/Mhf.Server/Packet/PacketHeader.cs
@@ -0,0 +1,39 @@
+namespace Mhf.Server.Packet
+{
+ public class PacketHeader
+ {
+ public PacketHeader(ushort id) : this(id, 0, 0, 0, 0, 0, 0, 0)
+ {
+ }
+
+ public PacketHeader(ushort id, byte pf0, byte keyRot, ushort dataSize, ushort combinedCheck, ushort check0,
+ ushort check1,
+ ushort check2)
+ {
+ Id = id;
+ Pf0 = pf0;
+ KeyRotDelta = keyRot;
+ DataSize = dataSize;
+ CombinedCheck = combinedCheck;
+ Check0 = check0;
+ Check1 = check1;
+ Check2 = check2;
+ }
+
+ public ushort Id { get; set; }
+ public byte Pf0 { get; set; }
+ public byte KeyRotDelta { get; set; }
+ public ushort DataSize { get; set; }
+ public ushort CombinedCheck { get; set; }
+ public ushort Check0 { get; set; }
+ public ushort Check1 { get; set; }
+ public ushort Check2 { get; set; }
+
+
+ public string ToLogText()
+ {
+ return
+ $"[Pf0:0x{Pf0:X2}|KeyRotDelta:0x{KeyRotDelta:X2}|Id:0x{Id:X2}|DataSize:0x{DataSize:X2}|CombinedCheck:0x{CombinedCheck:X2}|Chk0:0x{Check0:X2}|Chk1:0x{Check1:X2}|Chk02:0x{Check2:X2}]";
+ }
+ }
+}
diff --git a/Mhf.Server/Packet/PacketId.cs b/Mhf.Server/Packet/PacketId.cs
new file mode 100644
index 0000000..827021f
--- /dev/null
+++ b/Mhf.Server/Packet/PacketId.cs
@@ -0,0 +1,438 @@
+namespace Mhf.Server.Packet
+{
+ public enum PacketId : ushort
+ {
+ MSG_HEAD = 0,
+ MSG_SYS_reserve01 = 1,
+ MSG_SYS_reserve02 = 2,
+ MSG_SYS_reserve03 = 3,
+ MSG_SYS_reserve04 = 4,
+ MSG_SYS_reserve05 = 5,
+ MSG_SYS_reserve06 = 6,
+ MSG_SYS_reserve07 = 7,
+ MSG_SYS_ADD_OBJECT = 8,
+ MSG_SYS_DEL_OBJECT = 9,
+ MSG_SYS_DISP_OBJECT = 10,
+ MSG_SYS_HIDE_OBJECT = 11,
+ MSG_SYS_reserve0C = 12,
+ MSG_SYS_reserve0D = 13,
+ MSG_SYS_reserve0E = 14,
+ MSG_SYS_EXTEND_THRESHOLD = 15,
+ MSG_SYS_END = 16,
+ MSG_SYS_NOP = 17,
+ MSG_SYS_ACK = 18,
+ MSG_SYS_TERMINAL_LOG = 19,
+ MSG_SYS_LOGIN = 20,
+ MSG_SYS_LOGOUT = 21,
+ MSG_SYS_SET_STATUS = 22,
+ MSG_SYS_PING = 23,
+ MSG_SYS_CAST_BINARY = 24,
+ MSG_SYS_HIDE_CLIENT = 25,
+ MSG_SYS_TIME = 26,
+ MSG_SYS_CASTED_BINARY = 27,
+ MSG_SYS_GET_FILE = 28,
+ MSG_SYS_ISSUE_LOGKEY = 29,
+ MSG_SYS_RECORD_LOG = 30,
+ MSG_SYS_ECHO = 31,
+ MSG_SYS_CREATE_STAGE = 32,
+ MSG_SYS_STAGE_DESTRUCT = 33,
+ MSG_SYS_ENTER_STAGE = 34,
+ MSG_SYS_BACK_STAGE = 35,
+ MSG_SYS_MOVE_STAGE = 36,
+ MSG_SYS_LEAVE_STAGE = 37,
+ MSG_SYS_LOCK_STAGE = 38,
+ MSG_SYS_UNLOCK_STAGE = 39,
+ MSG_SYS_RESERVE_STAGE = 40,
+ MSG_SYS_UNRESERVE_STAGE = 41,
+ MSG_SYS_SET_STAGE_PASS = 42,
+ MSG_SYS_WAIT_STAGE_BINARY = 43,
+ MSG_SYS_SET_STAGE_BINARY = 44,
+ MSG_SYS_GET_STAGE_BINARY = 45,
+ MSG_SYS_ENUMERATE_CLIENT = 46,
+ MSG_SYS_ENUMERATE_STAGE = 47,
+ MSG_SYS_CREATE_MUTEX = 48,
+ MSG_SYS_CREATE_OPEN_MUTEX = 49,
+ MSG_SYS_DELETE_MUTEX = 50,
+ MSG_SYS_OPEN_MUTEX = 51,
+ MSG_SYS_CLOSE_MUTEX = 52,
+ MSG_SYS_CREATE_SEMAPHORE = 53,
+ MSG_SYS_CREATE_ACQUIRE_SEMAPHORE = 54,
+ MSG_SYS_DELETE_SEMAPHORE = 55,
+ MSG_SYS_ACQUIRE_SEMAPHORE = 56,
+ MSG_SYS_RELEASE_SEMAPHORE = 57,
+ MSG_SYS_LOCK_GLOBAL_SEMA = 58,
+ MSG_SYS_UNLOCK_GLOBAL_SEMA = 59,
+ MSG_SYS_CHECK_SEMAPHORE = 60,
+ MSG_SYS_OPERATE_REGISTER = 61,
+ MSG_SYS_LOAD_REGISTER = 62,
+ MSG_SYS_NOTIFY_REGISTER = 63,
+ MSG_SYS_CREATE_OBJECT = 64,
+ MSG_SYS_DELETE_OBJECT = 65,
+ MSG_SYS_POSITION_OBJECT = 66,
+ MSG_SYS_ROTATE_OBJECT = 67,
+ MSG_SYS_DUPLICATE_OBJECT = 68,
+ MSG_SYS_SET_OBJECT_BINARY = 69,
+ MSG_SYS_GET_OBJECT_BINARY = 70,
+ MSG_SYS_GET_OBJECT_OWNER = 71,
+ MSG_SYS_UPDATE_OBJECT_BINARY = 72,
+ MSG_SYS_CLEANUP_OBJECT = 73,
+ MSG_SYS_reserve4A = 74,
+ MSG_SYS_reserve4B = 75,
+ MSG_SYS_reserve4C = 76,
+ MSG_SYS_reserve4D = 77,
+ MSG_SYS_reserve4E = 78,
+ MSG_SYS_reserve4F = 79,
+ MSG_SYS_INSERT_USER = 80,
+ MSG_SYS_DELETE_USER = 81,
+ MSG_SYS_SET_USER_BINARY = 82,
+ MSG_SYS_GET_USER_BINARY = 83,
+ MSG_SYS_NOTIFY_USER_BINARY = 84,
+ MSG_SYS_reserve55 = 85,
+ MSG_SYS_reserve56 = 86,
+ MSG_SYS_reserve57 = 87,
+ MSG_SYS_UPDATE_RIGHT = 88,
+ MSG_SYS_AUTH_QUERY = 89,
+ MSG_SYS_AUTH_DATA = 90,
+ MSG_SYS_AUTH_TERMINAL = 91,
+ MSG_SYS_reserve5C = 92,
+ MSG_SYS_RIGHTS_RELOAD = 93,
+ MSG_SYS_reserve5E = 94,
+ MSG_SYS_reserve5F = 95,
+ MSG_MHF_SAVEDATA = 96,
+ MSG_MHF_LOADDATA = 97,
+ MSG_MHF_LIST_MEMBER = 98,
+ MSG_MHF_OPR_MEMBER = 99,
+ MSG_MHF_ENUMERATE_DIST_ITEM = 100,
+ MSG_MHF_APPLY_DIST_ITEM = 101,
+ MSG_MHF_ACQUIRE_DIST_ITEM = 102,
+ MSG_MHF_GET_DIST_DESCRIPTION = 103,
+ MSG_MHF_SEND_MAIL = 104,
+ MSG_MHF_READ_MAIL = 105,
+ MSG_MHF_LIST_MAIL = 106,
+ MSG_MHF_OPRT_MAIL = 107,
+ MSG_MHF_LOAD_FAVORITE_QUEST = 108,
+ MSG_MHF_SAVE_FAVORITE_QUEST = 109,
+ MSG_MHF_REGISTER_EVENT = 110,
+ MSG_MHF_RELEASE_EVENT = 111,
+ MSG_MHF_TRANSIT_MESSAGE = 112,
+ MSG_SYS_reserve71 = 113,
+ MSG_SYS_reserve72 = 114,
+ MSG_SYS_reserve73 = 115,
+ MSG_SYS_reserve74 = 116,
+ MSG_SYS_reserve75 = 117,
+ MSG_SYS_reserve76 = 118,
+ MSG_SYS_reserve77 = 119,
+ MSG_SYS_reserve78 = 120,
+ MSG_SYS_reserve79 = 121,
+ MSG_SYS_reserve7A = 122,
+ MSG_SYS_reserve7B = 123,
+ MSG_SYS_reserve7C = 124,
+ MSG_CA_EXCHANGE_ITEM = 125,
+ MSG_SYS_reserve7E = 126,
+ MSG_MHF_PRESENT_BOX = 127,
+ MSG_MHF_SERVER_COMMAND = 128,
+ MSG_MHF_SHUT_CLIENT = 129,
+ MSG_MHF_ANNOUNCE = 130,
+ MSG_MHF_SET_LOGINWINDOW = 131,
+ MSG_SYS_TRANS_BINARY = 132,
+ MSG_SYS_COLLECT_BINARY = 133,
+ MSG_SYS_GET_STATE = 134,
+ MSG_SYS_SERIALIZE = 135,
+ MSG_SYS_ENUMLOBBY = 136,
+ MSG_SYS_ENUMUSER = 137,
+ MSG_SYS_INFOKYSERVER = 138,
+ MSG_MHF_GET_CA_UNIQUE_ID = 139,
+ MSG_MHF_SET_CA_ACHIEVEMENT = 140,
+ MSG_MHF_CARAVAN_MY_SCORE = 141,
+ MSG_MHF_CARAVAN_RANKING = 142,
+ MSG_MHF_CARAVAN_MY_RANK = 143,
+ MSG_MHF_CREATE_GUILD = 144,
+ MSG_MHF_OPERATE_GUILD = 145,
+ MSG_MHF_OPERATE_GUILD_MEMBER = 146,
+ MSG_MHF_INFO_GUILD = 147,
+ MSG_MHF_ENUMERATE_GUILD = 148,
+ MSG_MHF_UPDATE_GUILD = 149,
+ MSG_MHF_ARRANGE_GUILD_MEMBER = 150,
+ MSG_MHF_ENUMERATE_GUILD_MEMBER = 151,
+ MSG_MHF_ENUMERATE_CAMPAIGN = 152,
+ MSG_MHF_STATE_CAMPAIGN = 153,
+ MSG_MHF_APPLY_CAMPAIGN = 154,
+ MSG_MHF_ENUMERATE_ITEM = 155,
+ MSG_MHF_ACQUIRE_ITEM = 156,
+ MSG_MHF_TRANSFER_ITEM = 157,
+ MSG_MHF_MERCENARY_HUNTDATA = 158,
+ MSG_MHF_ENTRY_ROOKIE_GUILD = 159,
+ MSG_MHF_ENUMERATE_QUEST = 160,
+ MSG_MHF_ENUMERATE_EVENT = 161,
+ MSG_MHF_ENUMERATE_PRICE = 162,
+ MSG_MHF_ENUMERATE_RANKING = 163,
+ MSG_MHF_ENUMERATE_ORDER = 164,
+ MSG_MHF_ENUMERATE_SHOP = 165,
+ MSG_MHF_GET_EXTRA_INFO = 166,
+ MSG_MHF_UPDATE_INTERIOR = 167,
+ MSG_MHF_ENUMERATE_HOUSE = 168,
+ MSG_MHF_UPDATE_HOUSE = 169,
+ MSG_MHF_LOAD_HOUSE = 170,
+ MSG_MHF_OPERATE_WAREHOUSE = 171,
+ MSG_MHF_ENUMERATE_WAREHOUSE = 172,
+ MSG_MHF_UPDATE_WAREHOUSE = 173,
+ MSG_MHF_ACQUIRE_TITLE = 174,
+ MSG_MHF_ENUMERATE_TITLE = 175,
+ MSG_MHF_ENUMERATE_GUILD_ITEM = 176,
+ MSG_MHF_UPDATE_GUILD_ITEM = 177,
+ MSG_MHF_ENUMERATE_UNION_ITEM = 178,
+ MSG_MHF_UPDATE_UNION_ITEM = 179,
+ MSG_MHF_CREATE_JOINT = 180,
+ MSG_MHF_OPERATE_JOINT = 181,
+ MSG_MHF_INFO_JOINT = 182,
+ MSG_MHF_UPDATE_GUILD_ICON = 183,
+ MSG_MHF_INFO_FESTA = 184,
+ MSG_MHF_ENTRY_FESTA = 185,
+ MSG_MHF_CHARGE_FESTA = 186,
+ MSG_MHF_ACQUIRE_FESTA = 187,
+ MSG_MHF_STATE_FESTA_U = 188,
+ MSG_MHF_STATE_FESTA_G = 189,
+ MSG_MHF_ENUMERATE_FESTA_MEMBER = 190,
+ MSG_MHF_VOTE_FESTA = 191,
+ MSG_MHF_ACQUIRE_CAFE_ITEM = 192,
+ MSG_MHF_UPDATE_CAFEPOINT = 193,
+ MSG_MHF_CHECK_DAILY_CAFEPOINT = 194,
+ MSG_MHF_GET_COG_INFO = 195,
+ MSG_MHF_CHECK_MONTHLY_ITEM = 196,
+ MSG_MHF_ACQUIRE_MONTHLY_ITEM = 197,
+ MSG_MHF_CHECK_WEEKLY_STAMP = 198,
+ MSG_MHF_EXCHANGE_WEEKLY_STAMP = 199,
+ MSG_MHF_CREATE_MERCENARY = 200,
+ MSG_MHF_SAVE_MERCENARY = 201,
+ MSG_MHF_READ_MERCENARY_W = 202,
+ MSG_MHF_READ_MERCENARY_M = 203,
+ MSG_MHF_CONTRACT_MERCENARY = 204,
+ MSG_MHF_ENUMERATE_MERCENARY_LOG = 205,
+ MSG_MHF_ENUMERATE_GUACOT = 206,
+ MSG_MHF_UPDATE_GUACOT = 207,
+ MSG_MHF_INFO_TOURNAMENT = 208,
+ MSG_MHF_ENTRY_TOURNAMENT = 209,
+ MSG_MHF_ENTER_TOURNAMENT_QUEST = 210,
+ MSG_MHF_ACQUIRE_TOURNAMENT = 211,
+ MSG_MHF_GET_ACHIEVEMENT = 212,
+ MSG_MHF_RESET_ACHIEVEMENT = 213,
+ MSG_MHF_ADD_ACHIEVEMENT = 214,
+ MSG_MHF_PAYMENT_ACHIEVEMENT = 215,
+ MSG_MHF_DISPLAYED_ACHIEVEMENT = 216,
+ MSG_MHF_INFO_SCENARIO_COUNTER = 217,
+ MSG_MHF_SAVE_SCENARIO_DATA = 218,
+ MSG_MHF_LOAD_SCENARIO_DATA = 219,
+ MSG_MHF_GET_BBS_SNS_STATUS = 220,
+ MSG_MHF_APPLY_BBS_ARTICLE = 221,
+ MSG_MHF_GET_ETC_POINTS = 222,
+ MSG_MHF_UPDATE_ETC_POINT = 223,
+ MSG_MHF_GET_MYHOUSE_INFO = 224,
+ MSG_MHF_UPDATE_MYHOUSE_INFO = 225,
+ MSG_MHF_GET_WEEKLY_SCHEDULE = 226,
+ MSG_MHF_ENUMERATE_INV_GUILD = 227,
+ MSG_MHF_OPERATION_INV_GUILD = 228,
+ MSG_MHF_STAMPCARD_STAMP = 229,
+ MSG_MHF_STAMPCARD_PRIZE = 230,
+ MSG_MHF_UNRESERVE_SRG = 231,
+ MSG_MHF_LOAD_PLATE_DATA = 232,
+ MSG_MHF_SAVE_PLATE_DATA = 233,
+ MSG_MHF_LOAD_PLATE_BOX = 234,
+ MSG_MHF_SAVE_PLATE_BOX = 235,
+ MSG_MHF_READ_GUILDCARD = 236,
+ MSG_MHF_UPDATE_GUILDCARD = 237,
+ MSG_MHF_READ_BEAT_LEVEL = 238,
+ MSG_MHF_UPDATE_BEAT_LEVEL = 239,
+ MSG_MHF_READ_BEAT_LEVEL_ALL_RANKING = 240,
+ MSG_MHF_READ_BEAT_LEVEL_MY_RANKING = 241,
+ MSG_MHF_READ_LAST_WEEK_BEAT_RANKING = 242,
+ MSG_MHF_ACCEPT_READ_REWARD = 243,
+ MSG_MHF_GET_ADDITIONAL_BEAT_REWARD = 244,
+ MSG_MHF_GET_FIXED_SEIBATU_RANKING_TABLE = 245,
+ MSG_MHF_GET_BBS_USER_STATUS = 246,
+ MSG_MHF_KICK_EXPORT_FORCE = 247,
+ MSG_MHF_GET_BREAK_SEIBATU_LEVEL_REWARD = 248,
+ MSG_MHF_GET_WEEKLY_SEIBATU_RANKING_REWARD = 249,
+ MSG_MHF_GET_EARTH_STATUS = 250,
+ MSG_MHF_LOAD_PARTNER = 251,
+ MSG_MHF_SAVE_PARTNER = 252,
+ MSG_MHF_GET_GUILD_MISSION_LIST = 253,
+ MSG_MHF_GET_GUILD_MISSION_RECORD = 254,
+ MSG_MHF_ADD_GUILD_MISSION_COUNT = 255,
+ MSG_MHF_SET_GUILD_MISSION_TARGET = 256,
+ MSG_MHF_CANCEL_GUILD_MISSION_TARGET = 257,
+ MSG_MHF_LOAD_OTOMO_AIROU = 258,
+ MSG_MHF_SAVE_OTOMO_AIROU = 259,
+ MSG_MHF_ENUMERATE_GUILD_TRESURE = 260,
+ MSG_MHF_ENUMERATE_AIROULIST = 261,
+ MSG_MHF_REGIST_GUILD_TRESURE = 262,
+ MSG_MHF_ACQUIRE_GUILD_TRESURE = 263,
+ MSG_MHF_OPERATE_GUILD_TRESURE_REPORT = 264,
+ MSG_MHF_GET_GUILD_TRESURE_SOUVENIR = 265,
+ MSG_MHF_ACQUIRE_GUILD_TRESURE_SOUVENIR = 266,
+ MSG_MHF_ENUMERATE_FESTA_INTERMEDIATE_PRIZE = 267,
+ MSG_MHF_ACQUIRE_FESTA_INTERMEDIATE_PRIZE = 268,
+ MSG_MHF_LOAD_DECO_MYSET = 269,
+ MSG_MHF_SAVE_DECO_MYSET = 270,
+ MSG_MHF_reserve010F = 271,
+ MSG_MHF_LOAD_GUILD_COOKING = 272,
+ MSG_MHF_REGIST_GUILD_COOKING = 273,
+ MSG_MHF_LOAD_GUILD_ADVENTURE = 274,
+ MSG_MHF_REGIST_GUILD_ADVENTURE = 275,
+ MSG_MHF_ACQUIRE_GUILD_ADVENTURE = 276,
+ MSG_MHF_CHARGE_GUILD_ADVENTURE = 277,
+ MSG_MHF_LOAD_LEGEND_DISPATCH = 278,
+ MSG_MHF_LOAD_HUNTER_NAVI = 279,
+ MSG_MHF_SAVE_HUNTER_NAVI = 280,
+ MSG_MHF_REGIST_SPABI_TIME = 281,
+ MSG_MHF_GET_GUILD_WEEKLY_BONUS_MASTER = 282,
+ MSG_MHF_GET_GUILD_WEEKLY_BONUS_ACTIVE_COUNT = 283,
+ MSG_MHF_ADD_GUILD_WEEKLY_BONUS_EXCEPTIONAL_USER = 284,
+ MSG_MHF_GET_TOWER_INFO = 285,
+ MSG_MHF_POST_TOWER_INFO = 286,
+ MSG_MHF_GET_GEM_INFO = 287,
+ MSG_MHF_POST_GEM_INFO = 288,
+ MSG_MHF_GET_EARTH_VALUE = 289,
+ MSG_MHF_DEBUG_POST_VALUE = 290,
+ MSG_MHF_GET_PAPER_DATA = 291,
+ MSG_MHF_GET_NOTICE = 292,
+ MSG_MHF_POST_NOTICE = 293,
+ MSG_MHF_GET_BOOST_TIME = 294,
+ MSG_MHF_POST_BOOST_TIME = 295,
+ MSG_MHF_GET_BOOST_TIME_LIMIT = 296,
+ MSG_MHF_POST_BOOST_TIME_LIMIT = 297,
+ MSG_MHF_ENUMERATE_FESTA_PERSONAL_PRIZE = 298,
+ MSG_MHF_ACQUIRE_FESTA_PERSONAL_PRIZE = 299,
+ MSG_MHF_GET_RAND_FROM_TABLE = 300,
+ MSG_MHF_GET_CAFE_DURATION = 301,
+ MSG_MHF_GET_CAFE_DURATION_BONUS_INFO = 302,
+ MSG_MHF_RECEIVE_CAFE_DURATION_BONUS = 303,
+ MSG_MHF_POST_CAFE_DURATION_BONUS_RECEIVED = 304,
+ MSG_MHF_GET_GACHA_POINT = 305,
+ MSG_MHF_USE_GACHA_POINT = 306,
+ MSG_MHF_EXCHANGE_FPOINT_2_ITEM = 307,
+ MSG_MHF_EXCHANGE_ITEM_2_FPOINT = 308,
+ MSG_MHF_GET_FPOINT_EXCHANGE_LIST = 309,
+ MSG_MHF_PLAY_STEPUP_GACHA = 310,
+ MSG_MHF_RECEIVE_GACHA_ITEM = 311,
+ MSG_MHF_GET_STEPUP_STATUS = 312,
+ MSG_MHF_PLAY_FREE_GACHA = 313,
+ MSG_MHF_GET_TINY_BIN = 314,
+ MSG_MHF_POST_TINY_BIN = 315,
+ MSG_MHF_GET_SENYU_DAILY_COUNT = 316,
+ MSG_MHF_GET_GUILD_TARGET_MEMBER_NUM = 317,
+ MSG_MHF_GET_BOOST_RIGHT = 318,
+ MSG_MHF_START_BOOST_TIME = 319,
+ MSG_MHF_POST_BOOST_TIME_QUEST_RETURN = 320,
+ MSG_MHF_GET_BOX_GACHA_INFO = 321,
+ MSG_MHF_PLAY_BOX_GACHA = 322,
+ MSG_MHF_RESET_BOX_GACHA_INFO = 323,
+ MSG_MHF_GET_SEIBATTLE = 324,
+ MSG_MHF_POST_SEIBATTLE = 325,
+ MSG_MHF_GET_RYOUDAMA = 326,
+ MSG_MHF_POST_RYOUDAMA = 327,
+ MSG_MHF_GET_TENROUIRAI = 328,
+ MSG_MHF_POST_TENROUIRAI = 329,
+ MSG_MHF_POST_GUILD_SCOUT = 330,
+ MSG_MHF_CANCEL_GUILD_SCOUT = 331,
+ MSG_MHF_ANSWER_GUILD_SCOUT = 332,
+ MSG_MHF_GET_GUILD_SCOUT_LIST = 333,
+ MSG_MHF_GET_GUILD_MANAGE_RIGHT = 334,
+ MSG_MHF_SET_GUILD_MANAGE_RIGHT = 335,
+ MSG_MHF_PLAY_NORMAL_GACHA = 336,
+ MSG_MHF_GET_DAILY_MISSION_MASTER = 337,
+ MSG_MHF_GET_DAILY_MISSION_PERSONAL = 338,
+ MSG_MHF_SET_DAILY_MISSION_PERSONAL = 339,
+ MSG_MHF_GET_GACHA_PLAY_HISTORY = 340,
+ MSG_MHF_GET_REJECT_GUILD_SCOUT = 341,
+ MSG_MHF_SET_REJECT_GUILD_SCOUT = 342,
+ MSG_MHF_GET_CA_ACHIEVEMENT_HIST = 343,
+ MSG_MHF_SET_CA_ACHIEVEMENT_HIST = 344,
+ MSG_MHF_GET_KEEP_LOGIN_BOOST_STATUS = 345,
+ MSG_MHF_USE_KEEP_LOGIN_BOOST = 346,
+ MSG_MHF_GET_UD_SCHEDULE = 347,
+ MSG_MHF_GET_UD_INFO = 348,
+ MSG_MHF_GET_KIJU_INFO = 349,
+ MSG_MHF_SET_KIJU = 350,
+ MSG_MHF_ADD_UD_POINT = 351,
+ MSG_MHF_GET_UD_MY_POINT = 352,
+ MSG_MHF_GET_UD_TOTAL_POINT_INFO = 353,
+ MSG_MHF_GET_UD_BONUS_QUEST_INFO = 354,
+ MSG_MHF_GET_UD_SELECTED_COLOR_INFO = 355,
+ MSG_MHF_GET_UD_MONSTER_POINT = 356,
+ MSG_MHF_GET_UD_DAILY_PRESENT_LIST = 357,
+ MSG_MHF_GET_UD_NORMA_PRESENT_LIST = 358,
+ MSG_MHF_GET_UD_RANKING_REWARD_LIST = 359,
+ MSG_MHF_ACQUIRE_UD_ITEM = 360,
+ MSG_MHF_GET_REWARD_SONG = 361,
+ MSG_MHF_USE_REWARD_SONG = 362,
+ MSG_MHF_ADD_REWARD_SONG_COUNT = 363,
+ MSG_MHF_GET_UD_RANKING = 364,
+ MSG_MHF_GET_UD_MY_RANKING = 365,
+ MSG_MHF_ACQUIRE_MONTHLY_REWARD = 366,
+ MSG_MHF_GET_UD_GUILD_MAP_INFO = 367,
+ MSG_MHF_GENERATE_UD_GUILD_MAP = 368,
+ MSG_MHF_GET_UD_TACTICS_POINT = 369,
+ MSG_MHF_ADD_UD_TACTICS_POINT = 370,
+ MSG_MHF_GET_UD_TACTICS_RANKING = 371,
+ MSG_MHF_GET_UD_TACTICS_REWARD_LIST = 372,
+ MSG_MHF_GET_UD_TACTICS_LOG = 373,
+ MSG_MHF_GET_EQUIP_SKIN_HIST = 374,
+ MSG_MHF_UPDATE_EQUIP_SKIN_HIST = 375,
+ MSG_MHF_GET_UD_TACTICS_FOLLOWER = 376,
+ MSG_MHF_SET_UD_TACTICS_FOLLOWER = 377,
+ MSG_MHF_GET_UD_SHOP_COIN = 378,
+ MSG_MHF_USE_UD_SHOP_COIN = 379,
+ MSG_MHF_GET_ENHANCED_MINIDATA = 380,
+ MSG_MHF_SET_ENHANCED_MINIDATA = 381,
+ MSG_MHF_SEX_CHANGER = 382,
+ MSG_MHF_GET_LOBBY_CROWD = 383,
+ MSG_SYS_reserve180 = 384,
+ MSG_MHF_GUILD_HUNTDATA = 385,
+ MSG_MHF_ADD_KOURYOU_POINT = 386,
+ MSG_MHF_GET_KOURYOU_POINT = 387,
+ MSG_MHF_EXCHANGE_KOURYOU_POINT = 388,
+ MSG_MHF_GET_UD_TACTICS_BONUS_QUEST = 389,
+ MSG_MHF_GET_UD_TACTICS_FIRST_QUEST_BONUS = 390,
+ MSG_MHF_GET_UD_TACTICS_REMAINING_POINT = 391,
+ MSG_SYS_reserve188 = 392,
+ MSG_MHF_LOAD_PLATE_MYSET = 393,
+ MSG_MHF_SAVE_PLATE_MYSET = 394,
+ MSG_SYS_reserve18B = 395,
+ MSG_MHF_GET_RESTRICTION_EVENT = 396,
+ MSG_MHF_SET_RESTRICTION_EVENT = 397,
+ MSG_SYS_reserve18E = 398,
+ MSG_SYS_reserve18F = 399,
+ MSG_MHF_GET_TREND_WEAPON = 400,
+ MSG_MHF_UPDATE_USE_TREND_WEAPON_LOG = 401,
+ MSG_SYS_reserve192 = 402,
+ MSG_SYS_reserve193 = 403,
+ MSG_SYS_reserve194 = 404,
+ MSG_MHF_SAVE_RENGOKU_DATA = 405,
+ MSG_MHF_LOAD_RENGOKU_DATA = 406,
+ MSG_MHF_GET_RENGOKU_BINARY = 407,
+ MSG_MHF_ENUMERATE_RENGOKU_RANKING = 408,
+ MSG_MHF_GET_RENGOKU_RANKING_RANK = 409,
+ MSG_MHF_ACQUIRE_EXCHANGE_SHOP = 410,
+ MSG_SYS_reserve19B = 411,
+ MSG_MHF_SAVE_MEZFES_DATA = 412,
+ MSG_MHF_LOAD_MEZFES_DATA = 413,
+ MSG_SYS_reserve19E = 414,
+ MSG_SYS_reserve19F = 415,
+ MSG_MHF_UPDATE_FORCE_GUILD_RANK = 416,
+ MSG_MHF_RESET_TITLE = 417,
+ MSG_SYS_reserve202 = 418,
+ MSG_SYS_reserve203 = 419,
+ MSG_SYS_reserve204 = 420,
+ MSG_SYS_reserve205 = 421,
+ MSG_SYS_reserve206 = 422,
+ MSG_SYS_reserve207 = 423,
+ MSG_SYS_reserve208 = 424,
+ MSG_SYS_reserve209 = 425,
+ MSG_SYS_reserve20A = 426,
+ MSG_SYS_reserve20B = 427,
+ MSG_SYS_reserve20C = 428,
+ MSG_SYS_reserve20D = 429,
+ MSG_SYS_reserve20E = 430,
+ MSG_SYS_reserve20F = 431,
+ }
+}
diff --git a/Mhf.Server/Packet/PacketResponse.cs b/Mhf.Server/Packet/PacketResponse.cs
new file mode 100644
index 0000000..e1f7596
--- /dev/null
+++ b/Mhf.Server/Packet/PacketResponse.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using Arrowgene.Services.Buffers;
+using Mhf.Server.Model;
+
+namespace Mhf.Server.Packet
+{
+ public abstract class PacketResponse
+ {
+ private readonly List _receiver;
+ private MhfPacket _packet;
+
+ public PacketResponse(ushort id)
+ {
+ _receiver = new List();
+ Id = id;
+ }
+
+ public List Receiver => new List(_receiver);
+ public ushort Id { get; }
+
+ protected abstract IBuffer ToBuffer();
+
+ public MhfPacket ToPacket()
+ {
+ if (_packet == null)
+ {
+ _packet = new MhfPacket(Id, ToBuffer());
+ }
+
+ return _packet;
+ }
+
+ public void AddReceiver(params ISender[] receiver)
+ {
+ _receiver.AddRange(receiver);
+ }
+
+ public void AddReceiver(IEnumerable receiver)
+ {
+ _receiver.AddRange(receiver);
+ }
+
+ public void CleatReceivers()
+ {
+ _receiver.Clear();
+ }
+ }
+}
diff --git a/Mhf.Server/Packet/PacketRouter.cs b/Mhf.Server/Packet/PacketRouter.cs
new file mode 100644
index 0000000..418f81d
--- /dev/null
+++ b/Mhf.Server/Packet/PacketRouter.cs
@@ -0,0 +1,92 @@
+using System.Collections.Generic;
+using Arrowgene.Services.Buffers;
+using Arrowgene.Services.Logging;
+using Mhf.Server.Logging;
+using Mhf.Server.Model;
+
+namespace Mhf.Server.Packet
+{
+ public class PacketRouter
+ {
+ private readonly MhfLogger _logger;
+
+ public PacketRouter()
+ {
+ _logger = LogProvider.Logger(this);
+ }
+
+ ///
+ /// Send a packet to a client.
+ ///
+ public void Send(ISender client, MhfPacket packet)
+ {
+ client.Send(packet);
+ }
+
+ ///
+ /// Send a packet to a client.
+ ///
+ public void Send(ISender client, ushort id, IBuffer data)
+ {
+ MhfPacket packet = new MhfPacket(id, data);
+ Send(client, packet);
+ }
+
+ ///
+ /// Send a packet to multiple clients.
+ ///
+ /// clients to exclude
+ public void Send(List clients, MhfPacket packet, params ISender[] excepts)
+ {
+ clients = GetClients(clients, excepts);
+ foreach (MhfClient client in clients)
+ {
+ Send(client, packet);
+ }
+ }
+
+ ///
+ /// Send a packet to multiple clients.
+ ///
+ /// clients to exclude
+ public void Send(List clients, ushort id, IBuffer data, params ISender[] excepts)
+ {
+ Send(clients, new MhfPacket(id, data), excepts);
+ }
+
+ ///
+ /// Send a specific packet response.
+ ///
+ public void Send(PacketResponse response)
+ {
+ foreach (ISender client in response.Receiver)
+ {
+ Send(client, response.ToPacket());
+ }
+ }
+
+ ///
+ /// Send a specific packet response.
+ ///
+ public void Send(PacketResponse response, params ISender[] clients)
+ {
+ response.AddReceiver(clients);
+ Send(response);
+ }
+
+ private List GetClients(List clients, params ISender[] excepts)
+ {
+ if (excepts.Length == 0)
+ {
+ return clients;
+ }
+
+ foreach (ISender except in excepts)
+ {
+ clients.Remove(except);
+ }
+
+ return clients;
+ }
+ }
+}
diff --git a/Mhf.Server/PacketHandler/MsgHeadHandler.cs b/Mhf.Server/PacketHandler/MsgHeadHandler.cs
new file mode 100644
index 0000000..2dc2eaa
--- /dev/null
+++ b/Mhf.Server/PacketHandler/MsgHeadHandler.cs
@@ -0,0 +1,27 @@
+using Mhf.Server.Model;
+using Mhf.Server.Packet;
+using Mhf.Server.PacketResponses;
+
+namespace Mhf.Server.PacketHandler
+{
+ public class MsgHeadHandler : ConnectionHandler
+ {
+ public MsgHeadHandler(MhfServer server) : base(server)
+ {
+ }
+
+ public override ushort Id => (ushort) PacketId.MSG_HEAD;
+
+ public override void Handle(MhfConnection connection, MhfPacket packet)
+ {
+ string keySign = packet.Data.ReadCString();
+ string userId = packet.Data.ReadCString();
+ string sessionKey = packet.Data.ReadCString();
+
+ Logger.Info($"Auth Request: KeySign:{keySign} UserId:{userId} SessionKey:{sessionKey}");
+
+ MsgHeadResponse response = new MsgHeadResponse();
+ Router.Send(response, connection);
+ }
+ };
+}
diff --git a/Mhf.Server/PacketResponses/MsgHeadResponse.cs b/Mhf.Server/PacketResponses/MsgHeadResponse.cs
new file mode 100644
index 0000000..336e375
--- /dev/null
+++ b/Mhf.Server/PacketResponses/MsgHeadResponse.cs
@@ -0,0 +1,42 @@
+using Arrowgene.Services.Buffers;
+using Mhf.Server.Common;
+using Mhf.Server.Packet;
+
+namespace Mhf.Server.PacketResponses
+{
+ public class MsgHeadResponse : PacketResponse
+ {
+ public MsgHeadResponse() : base((ushort) PacketId.MSG_HEAD)
+ {
+ }
+
+ protected override IBuffer ToBuffer()
+ {
+ string fileChecksumHost = "l0.mhf-g.jp";
+ string fileHost = "u0.mhf-g.jp";
+ string lobbyConnection = "127.0.0.1:53310";
+
+ IBuffer res = BufferProvider.Provide();
+ res.SetPositionStart();
+ res.WriteByte(1); //unk_hostname_count
+ res.WriteByte(2); //entrance_server_count
+ res.WriteByte(1); //character_count
+ res.WriteByte(1); //character_count
+ res.WriteBytes(new byte[]
+ {
+ 0x13, 0x50, 0xDE, 0xED, 0x44, 0x6C, 0x58, 0x46, 0x7A, 0x77, 0x73, 0x75, 0x42, 0x30, 0x41, 0x45, 0x34,
+ 0x75, 0x77, 0x41, 0x5D, 0xCF, 0x28, 0x85
+ });
+ res.WriteByte((byte) (fileHost.Length + 1)); //str len + 00
+ res.WriteCString(fileHost);
+ res.WriteByte((byte) (fileChecksumHost.Length + 1)); //str len + 00
+ res.WriteCString(fileChecksumHost);
+ res.WriteByte((byte) (lobbyConnection.Length + 1)); //str len + 00
+ res.WriteCString(lobbyConnection);
+ string d =
+ "2A14BEE7000100075DCC8E68000000017370656E676500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000002643C424F44593E3C43454E5445523E3C53495A455F333E3C435F343E8CC397B48EED82AA82A893BE81493C42523E3C424F44593E817591E6333989F189CC95508EEB897190ED8145905E90E081768A4A8DC33C42523E3C424F44593E3C42523E3C424F44593E3C4C4546543E3C435F333E3C53495A455F323E817990ED89CC82CC8FCD8A4A8DC38AFA8AD4817A3C435F373E3C42523E3C424F44593E31318C8E313393FA289085292081602031318C8E323093FA289085298381839383658369839383588A4A8E6E82DC82C53C42523E3C424F44593E3C42523E3C424F44593E3C42523E3C424F44593E3C435F343E81A18CC397B48EED82AA82A893BE81493C435F373E3C42523E3C424F44593E3C53495A455F323E8CC397B48EED82CC838283938358835E815B82F08EEB97C282B582C48A6C93BE82C582AB82E98C7D8C82837C83438393836782F0919D97CA81423C42523E3C424F44593E3C42523E3C424F44593E3C435F343E81A1834E8347835883678F89834E838A8341837B815B8369835882F0919D97CA81493C435F373E3C42523E3C424F44593E90ED89CC82CC8FCD82C58A6C93BE82C582AB82E9834E8347835883678F89834E838A8341837B815B8369835882AA3C42523E3C424F44593E92CA8FED82E682E882E03C435F353E353030837C8343839383678341836283768149203C435F373E3C42523E3C424F44593E3C42523E3C424F44593E91BD82AD82CC8EED97DE82CC8C7D8C8290ED834E83478358836782F0834E838A834182B581413C42523E3C424F44593E8F89834E838A8341837B815B8369835882F082BD82AD82B382F18A6C93BE82B582E682A48149002A14BEE70000000E2A64736D6300080C00003D000000818100000000000029000000816A0000000000002800000081690000000000002100000081490000000000002F000000815E0000000000002B000000817B00000000000026000000819500000000000082500000310000000000000082DA0000837B0000CE00DE0082D9DE00837ADE0082D9814A837A814ACE00814A0000000082D7000083780000CD00DE0082D6DE008377DE0082D6814ACD00814A8377814A0000000082C5000083660000C300DE0082C4DE008365DE0082C4814A8365814AC300814A81A7814A81A7DE0089B3DE0089B3814A0000000082D1000083720000CB00DE0082D0DE008371DE0082D0814A8371814ACB00814A0000000082C7000083680000C400DE0082C6DE008367DE0082C6814A8367814AC400814A84B0DE0084B0814A84A5DE0084A5814A0000000082CE0000836F0000CA00DE0082CDDE00836EDE0094AADE0082CD814A836E814ACA00814A94AA814A0000000082C2DE00836400008363DE0082C2814A8363814AC200DE00C200814A82C3000082C1DE008362DE00AF00DE0082C1814A8362814AAF00814A0000000082D4000083750000CC00DE0083940000B300DE0082A4814A82A4DE008345DE00A900DE0082A3DE0082D3DE008374DE00CC00814A0000000082C0000083610000C100DE0082BFDE008360DE0082BF814A8360814AC100814A90E7814A90E7DE000000000082BE0000835F0000C000DE0082BDDE00835EDE00975BDE0082BD814A835E814AC000814A975B814A0000000082BC0000835D0000BF00DE0082BBDE00835CDE0082BB814A835C814ABF00814A8393DE008393814ADD00814ADD00DE00838ADE00D800DE00D800814A838A814A0000000082BA0000BE00DE0082B9DE00835ADE0082B9814A835A814ABE00814A835B00000000000082B8000083590000BD00DE0082B7DE008358DE0082B7814A8358814ABD00814A0000000082B6000083570000BC00DE0082B5DE008356DE0082B5814A8356814ABC00814A0000000082B4000083550000BB00DE0082B3DE008354DE0082B3814A8354814ABB00814A0000000082B2000083530000BA00DE0082B1DE008352DE0082B1814A8352814ABA00814A0000000082B0000083510000B900DE0082AFDE008350DE0082AF814A8350814AB900814A8396DE008396814A0000000082AE0000834F0000B800DE0082ADDE00834EDE0082AD814A834E814AB800814A0000000082AC0000834D0000B700DE0082ABDE00834CDE0082AB814A834C814AB700814A0000000082AA0000834B0000B600DE008395DE00834ADE0082A9DE0097CDDE008395814A834A814A82A9814A97CD814AB600814A0000000082F0000083920000A60000000000000082ED0000838F0000DC000000838E00000000000082EB0000838D0000DB00000081A000008CFB00000000000082EA0000838C0000DA0000000000000082E90000838B0000D90000000000000082E80000838A0000D80000000000000082E7000083890000D70000000000000082E6000083880000D6000000AE00000082E50000838700000000000082E4000083860000D5000000AD00000082E30000838500000000000082E2000083840000D4000000AC00000082E10000838300000000000082E0000083820000D30000000000000082DF000083810000D20000004D0045000000000082DE000083800000D10000000000000082DD0000837E0000D00000000000000082DC0000837D0000CF0000000000000082D90000837A0000CE0000000000000082D6000083770000CD0000000000000082D3000083740000CC0000000000000082D0000083710000CB0000000000000082CD0000836E0000CA00000094AA00000000000082CC0000836D0000C90000000000000082CB0000836C0000C80000000000000082CA0000836B0000C70000000000000082C90000836A0000C600000093F100000000000082C8000083690000C50000000000000082C6000083670000C400000084B0000084A500000000000082C4000083650000C300000081A7000089B300000000000082C2000083630000C200000082C1000083620000AF0000000000000082BF000083600000C100000090E700000000000082BD0000835E0000C0000000975B00000000000082BB0000835C0000BF0000000000000082B90000835A0000BE0000000000000082B7000083580000BD0000000000000082B5000083560000BC0000000000000082B3000083540000BB0000000000000082B1000083520000BA0000000000000082AF000083500000B9000000839600000000000082AD0000834E0000B80000000000000082AB0000834C0000B70000000000000082A90000834A0000B60000008395000097CD00000000000082A8000083490000B5000000AB00000082A70000834800000000000082A6000083470000B4000000AA00000082A50000834600008D4800000000000082A4000083450000B3000000A900000082A30000834400000000000082A2000083430000B2000000A800000082A100008342000000000000A7000000B1000000829F000082A00000834000008341000000000000815B0000815C0000815D0000816000002D000000817C0000B000000088EA00000000000039000000825800000000000038000000825700000000000037000000825600000000000036000000825500000000000035000000825400000000000034000000825300000000000033000000825200000000000032000000825100000000000082DB0000837C0000CE00DF0082D9DF00837ADF00837A818B82D9818BCE00818B0000000082D8000083790000CD00DF0082D6DF008377DF008377818B82D6818BCD00818B0000000082D5000083760000CC00DF0082D3DF008374DF008374818B82D3818BCC00818B0000000082D2000083730000CB00DF0082D0DF008371DF008371818B82D0818BCB00818B0000000082CF000083700000CA00DF0082CDDF00836EDF00836E818B82CD818BCA00818B94AADF0094AA814B0000000082790000829A00005A0000007A00000083A40000000000008278000082990000590000007900000083B200008454000084850000000000008277000082980000580000007800000083B4000083D4000084560000817E000084870000875D0000000000008276000082970000570000007700000083D6000084590000848A0000848B0000000000008275000082960000560000007600000083CB000083D2000087580000000000008274000082950000550000007500000083CA000081BE0000000000008273000082940000540000007400000083B1000083D100008453000084840000000000002400000053000000730000008190000081E70000827200008293000000000000827100008292000052000000720000008460000084910000000000008270000082910000510000007100000000000000826F000082900000500000007000000083AF000083CF0000845100008482000000000000826E0000828F00004F0000006F000000819B000083AD000083CD0000844F00008480000081FC0000815A000030000000824F000000000000826D0000828E00004E0000006E00000083AB000083C50000DD00000082F100008393000000000000826C0000828D00004D0000006D00000083AA0000844D0000847D000000000000826B0000828C00004C0000006C000000879800007C00000000000000826A0000828B00004B0000006B00000083A8000083C80000844B0000847B00000000000082690000828A00004A0000006A000000000000008268000082890000490000006900000083A7000087540000000000008267000082880000480000006800000083A50000844E0000847E000000000000826500008286000046000000660000000000000082660000828700004700000067000000000000008264000082850000450000006500000083A3000083C300008445000084460000847500008476000081B80000000000008263000082840000440000006400000000000000430000008283000063000000845200008483000082620000818E0000000000004200000082610000828200006200000083C0000083A000008442000084720000848C0000848E000081F30000000000002700000081660000000000004100000082600000610000008281000083BF000040000000819700008470000081F0000084400000839F000000000000220000008168000000000000250000008193000000000000000000006E616D002816000002000000937AFFFF97EAFFFF0000FFFF0400000082B78B0182A9A80182C6660182EBFC000000FFFF020000009273FFFF8F97FFFF0000FFFF0300000082A2C401826D870282DBF3010000FFFF050000008266C2024100EF02826D87028269A9024100EF020000FFFF0400000082C46C0182A2C401826D870282B197010000FFFF0300000082DC380182BB830182B197010000FFFF0300000082BF790182BB830182B197010000FFFF020000008BADFFFF9390FFFF0000FFFF0400000082CF170282A2C40182CF1702826D87020000FFFF03000000965CFFFF82A9A8019263FFFF0000FFFF0400000082B9870182ADA00182EBFC0082B78B010000FFFF0400000082CE4D0082BD7E01815BD2018CA2FFFF0000FFFF0500000082DC380182E80A0182D34401A700CB0182C862010000FFFF0400000082D5050282C2720182B58F01815BD2010000FFFF0400000082B1970182A9A80182A2C401826D87020000FFFF0400000082C46C0182A2C401826D870282DBF3010000FFFF0400000082D13700815BD20182BF790182ADA0010000FFFF0200000090ABFFFF977EFFFF0000FFFF0300000082C8620182BF790182B78B010000FFFF0400000082EBFC0082E80A0182B19701826D87020000FFFF0400000082C46C0182A2C40182DE300182DBF3010000FFFF0400000082C52A0082A9A80182DC380182E70E010000FFFF0400000082CF170282A2C40182B8A50082E80A010000FFFF0400000082DC3801826D870282A9A80182B78B010000FFFF0D0000004100EF028263D302826C91028268AE02826D87028268AE0224005C0282735302827164024100EF0282735302826E7902827164020000FFFF0600000082A8AE018B71FFFF976CFFFF918AFFFF926BFFFF8EBAFFFF0000FFFF0200000096F2FFFF95A8FFFF0000FFFF0400000092D2FFFF967BFFFF8C9BFFFF8E4FFFFF0000FFFF0400000092D2FFFF967BFFFF90B0FFFF8D4FFFFF0000FFFF040000008FACFFFF96ECFFFF8B60FFFF93BFFFFF0000FFFF0300000082716402826C9102827353020000FFFF040000009099FFFF8959FFFF815BD20193BFFFFF0000FFFF0400000092D2FFFF967BFFFF97C7FFFF8E4FFFFF0000FFFF0300000093A1FFFF89AAFFFF9776FFFF0000FFFF04000000815BD20190A3FFFF91D7FFFF94CDFFFF0000FFFF050000008FACFFFF9388FFFF9054FFFF91BEFFFF9859FFFF0000FFFF0200000091E5FFFF9683FFFF0000FFFF030000008A6FFFFF90C1FFFF8DDCFFFF0000FFFF020000009FAFFFFF92B0FFFF0000FFFF020000009683FFFF96F2FFFF0000FFFF0300000092AAFFFF9081FFFF82ABA4010000FFFF0300000082A9A80182D505029859FFFF0000FFFF010000008179FFFF0000FFFF01000000817AFFFF0000FFFF020000008941FFFF8C73FFFF0000FFFF020000009675FFFF8B4EFFFF0000FFFF0200000089BAFFFF979FFFFF0000FFFF0600000082A8AE0182A4BD0182DE3001905EFFFF979DFFFF8BB3FFFF0000FFFF090000008269A9024100EF02826F7002826AA0028268AE02826B9902826B99028264C702827164020000FFFF030000008B43FFFF88E1FFFF82A2C4010000FFFF040000008B43FFFF82BF790182AAE60082A2C4010000FFFF0400000082ABA40182BF790182AAE60082A2C4010000FFFF030000008B43FFFF8BB6FFFF82A2C4010000FFFF0400000090B8FFFF905FFFFF9496FFFF8EE3FFFF0000FFFF04000000926DFFFF8C62FFFF9278FFFF82EA02010000FFFF0500000082BF790182A6B50182A8AE0182ADA00182EA02010000FFFF0200000096DAFFFF88C3FFFF0000FFFF0200000096BEFFFF96D3FFFF0000FFFF05000000A700CB0182ABA40182DF2B0182ADA00182E70E010000FFFF0400000082EBFC00826D870282CF170282E80A010000FFFF02000000E19AFFFF9561FFFF0000FFFF0300000082E70E0182A2C4019561FFFF0000FFFF020000009492FFFF9273FFFF0000FFFF01000000E6EAFFFF0000FFFF0300000082BF7901826D870282CE4D000000FFFF0100000098FBFFFF0000FFFF0200000098FBFFFF9943FFFF0000FFFF02000000936AFFFF8E45FFFF0000FFFF020000008CEEFFFF9048FFFF0000FFFF02000000E6CBFFFF96AFFFFF0000FFFF02000000E271FFFF91BDFFFF0000FFFF0200000094F1FFFF906CFFFF0000FFFF030000009056FFFF95BDFFFF96AFFFFF0000FFFF0400000093C1FFFF8EEAFFFF9594FFFF978EFFFF0000FFFF0500000082CE4D0082A9A80182BF790182E61201826D87020000FFFF0400000082BF790182E61201826D87028CF6FFFF0000FFFF0500000082C6660182E9060182B197019597FFFF9843FFFF0000FFFF0400000082C6660182E9060182B197018FECFFFF0000FFFF03000000A700CB0182C8620182E906010000FFFF0300000082A4BD01826D870282B197010000FFFF0200000096D1FFFF9382FFFF0000FFFF030000008D95FFFF826D870282DA18000000FFFF040000009573FFFF89C2FFFF9047FFFF96AFFFFF0000FFFF020000009484FFFF8F97FFFF0000FFFF0200000088FAFFFF9484FFFF0000FFFF020000008BADFFFF8AADFFFF0000FFFF0400000082E2200182E80A0182DC3801826D87020000FFFF0300000082DC3801826D870282B197010000FFFF0300000082A8AE0182DF2B0182B197010000FFFF0300000082BF7901826D870282B197010000FFFF0300000082BF7901826D870282DBF3010000FFFF0300000082BF7901826D870282DA18000000FFFF0400000082B9870182C2720182ADA00182B78B010000FFFF0300000024005C028264C702827730020000FFFF020000009484FFFF8F74FFFF0000FFFF020000009483FFFF8F74FFFF0000FFFF0300000082EA020182A2C40182D505020000FFFF0400000082B78B0182D8FC0182E9060182DC38010000FFFF0500000082ADA00182E80A0182C6660182E80A0182B78B010000FFFF0500000082D3440182A6B50182E70E0182BF790182A8AE010000FFFF0300000082B4B700815BD20196CBFFFF0000FFFF0400000082B4B700815BD20182DF2B01826D87020000FFFF0400000082B98701826D870282B8A50082E80A010000FFFF0400000082A8AE0182C8620182C95D01815BD2010000FFFF030000008FE1FFFF8A51FFFF8ED2FFFF0000FFFF040000008265BD0282744C024300D802826AA0020000FFFF0400000082D34401A700CB0182C2720182ADA0010000FFFF0500000082BF790182E22001826D870282B1970182EBFC000000FFFF050000004200E0028268AE02827353024300D8028267B5020000FFFF040000004300D80282744C02826D8702827353020000FFFF05000000826F70028264C702826D87028268AE0224005C020000FFFF0300000082D8FC0182C95D0182B78B010000FFFF06000000827544024100EF028266C2028268AE02826D87024100EF020000FFFF0300000082CE4D0082ACDD0082C862010000FFFF0400000082D46700A700CB0182ACDD0082C862010000FFFF05000000826F700282744C0224005C0224005C02827828020000FFFF0200000090B8FFFF8974FFFF0000FFFF020000008E80FFFF82CB55010000FFFF020000008E45FFFF82B78B010000FFFF0100000095B3FFFF0000FFFF020000008998FFFF95A8FFFF0000FFFF0400000082BF7901826D870282DBF30182B197010000FFFF0400000082DBF30182B1970182BF7901826D87020000FFFF0300000082B3930182B987018E71FFFF0000FFFF020000008941FFFF96D1FFFF0000FFFF020000008BCAFFFF8BE0FFFF0000FFFF0400000082A9A80182D5050282B19701826D87020000FFFF0400000082A9A80182D5050282B1970182BB83010000FFFF0400000082A9A80182D50502826D870282B197010000FFFF060000004300D8024100EF02826F70024300D802826E7902826C91020000FFFF060000004300D8024100EF02826F70024300D802826E7902826D87020000FFFF060000004300D8024100EF02826F7002826AA002826E7902826C91020000FFFF060000004300D8024100EF02826F7002826AA002826E7902826D87020000FFFF06000000826AA0024100EF02826F70024300D802826E7902826C91020000FFFF06000000826AA0024100EF02826F70024300D802826E7902826D87020000FFFF06000000826AA0024100EF02826F7002826AA002826E7902826C91020000FFFF06000000826AA0024100EF02826F7002826AA002826E7902826D87020000FFFF090000008266C2024100EF0282DF2B01826C91024100EF0224005C02827353028264C702827164020000FFFF0A0000008266C2024100EF0282DF2B0124005C0282744C02826F7002826F7002826E790282716402827353020000FFFF0600000024005C028278280224005C02827353028264C702826C91020000FFFF0400000082B58F0182B78B0182C46C0182DE30010000FFFF050000008264C702827544028264C702826D8702827353020000FFFF0400000082A2C40182D72100826D870282C666010000FFFF0500000024005C02827353024100EF028265BD028265BD020000FFFF0400000082B78B0182BD7E0182C2720182D344010000FFFF08000000826E79028265BD028265BD028268AE024300D8028268AE024100EF02826B99020000FFFF0600000082A8AE0182D3440182A2C40182B58F0182E2200182E906010000FFFF0700000024005C0282744C02826F7002826F7002826E790282716402827353020000FFFF0400000082B3930182DBF301815BD20182C666010000FFFF050000004100EF028263D302826C91028268AE02826D87020000FFFF04000000A700CB0182C7400082DD3401826D87020000FFFF04000000A700CB0182C7400082DD340182BB83010000FFFF04000000A700CB0182C7400082DD340182C95D010000FFFF04000000A700CB0182C7400082DD340182C95D010000FFFF02000000895EFFFF8963FFFF0000FFFF0400000082A4BD01826D870282A6B50182A2C4010000FFFF020000008AC7FFFF979DFFFF0000FFFF0300000082A9A801826D870282E80A010000FFFF030000008AD6FFFF8C57FFFF8ED2FFFF0000FFFF030000008A4AFFFF94ADFFFF8ED2FFFF0000FFFF0400000082A8AE01926DFFFF82E70E0182B987010000FFFF0400000082A8AE0182B58F0182E70E0182B987010000FFFF020000008D90FFFF926DFFFF0000FFFF0300000088D4FFFF88C0FFFF9577FFFF0000FFFF0400000082E2200182E80A0182BF7901826D87020000FFFF0300000082E2200182ADA00182B4B7000000FFFF0400000092D2FFFF967BFFFF8F74FFFF8D4FFFFF0000FFFF0300000096D8FFFF967BFFFF8263D3020000FFFF0400000096D8FFFF967BFFFF97B4FFFF8CC8FFFF0000FFFF040000008B7BFFFF89BAFFFF4100EF02826F70020000FFFF040000008B7BFFFF89BAFFFF8B50FFFF8EF7FFFF0000FFFF08000000826C91028267B5028265BD02815BD2018266C20282BF7901815BD20182DE30010000FFFF06000000826C91028267B5028265BD0282BF7901815BD20182DE30010000FFFF040000008B43FFFF82AED40082E9060182A2C4010000FFFF0400000082ABA40182AED40082E9060182A2C4010000FFFF0200000090B8FFFF9496FFFF0000FFFF0200000088FAFFFF9790FFFF0000FFFF020000009641FFFF9550FFFF0000FFFF03000000945DFFFF968CFFFF898AFFFF0000FFFF020000008B54FFFF93AAFFFF0000FFFF0300000082DF2B0182ADA00182E70E010000FFFF0200000093F7FFFF965FFFFF0000FFFF02000000956EFFFF93FBFFFF0000FFFF0600000082BE800082C2720182BF790182EDF70082A2C40182D344010000FFFF010000009857FFFF0000FFFF0300000082C27201826D870282DA18000000FFFF020000008B68FFFF82E80A010000FFFF020000009181FFFF9852FFFF0000FFFF0500000082A2C40182DC380182E70E0182BF790182A8AE010000FFFF0500000082A8AE0182C8620182C95D0182B78B0182C666010000FFFF0200000088A4FFFF8974FFFF0000FFFF020000008AE7FFFF8ECBFFFF0000FFFF0200000095D0FFFF925BFFFF0000FFFF020000009790FFFF8CF0FFFF0000FFFF0300000082B9870182DE300182B58F010000FFFF030000008E4FFFFF82C2720182EBFC000000FFFF02000000E3E8FFFF96E5FFFF0000FFFF0200000093F7FFFF92D9FFFF0000FFFF0300000090ABFFFF8AB4FFFF91D1FFFF0000FFFF0500000082A8AE01815BD20182AAE60082B8A50082DE30010000FFFF020000008EA9FFFF88D4FFFF0000FFFF0200000093FBFFFF97D6FFFF0000FFFF020000009278FFFF9852FFFF0000FFFF020000008DD7FFFF96AFFFFF0000FFFF020000008B87FFFF96AFFFFF0000FFFF0200000094D8FFFF91B0FFFF0000FFFF02000000966BFFFF914EFFFF0000FFFF0200000093ECFFFF914EFFFF0000FFFF030000008E4FFFFF8D91FFFF906CFFFF0000FFFF0200000097BDFFFF904AFFFF0000FFFF0200000096B2FFFF90B8FFFF0000FFFF010000008FA8FFFF0000FFFF020000008FA9FFFF9577FFFF0000FFFF020000008F97FFFF8941FFFF0000FFFF020000008EE8FFFF88FAFFFF0000FFFF030000008D67FFFF96D1FFFF906CFFFF0000FFFF020000009849FFFF8F95FFFF0000FFFF020000008987FFFF8CF0FFFF0000FFFF0400000082ADA00182EBFC00826D870282DA18000000FFFF03000000A700CB0182DF2B018CF6FFFF0000FFFF0300000082A2C40182BD7E018CF6FFFF0000FFFF040000009553FFFF8AD1FFFF82C52A0082D467000000FFFF0400000082E90601826D870282D8FC01826D87020000FFFF020000008941FFFF9594FFFF0000FFFF020000008941FFFF904FFFFF0000FFFF0200000088FAFFFF8D73FFFF0000FFFF0400000082BF7901826D870282BF7901826D87020000FFFF02000000926AFFFF8DAAFFFF0000FFFF0500000082A8AE018F95FFFF82AF9B018EEBFFFF906CFFFF0000FFFF0300000082BF7901826D870296D1FFFF0000FFFF02000000914EFFFF906CFFFF0000FFFF0300000082ADA001826D870282C95D010000FFFF030000009099FFFF8959FFFF826F70020000FFFF0200000093B6FFFF92E5FFFF0000FFFF0200000095EFFFFF8C73FFFF0000FFFF020000008BE0FFFF8BCAFFFF0000FFFF0400000082A8AE0182C2720182CF170282A2C4010000FFFF0300000082BF790182ADA00182D137000000FFFF03000000A700CB0182C8620182E906010000FFFF03000000A700CB0182CA590182B78B010000FFFF0200000093FBFFFF8EF1FFFF0000FFFF01000000E453FFFF0000FFFF0200000090C2FFFF8AADFFFF0000FFFF0200000096ECFFFF8AADFFFF0000FFFF0200000095D6FFFF94E9FFFF0000FFFF020000008FACFFFF95D6FFFF0000FFFF0200000091E5FFFF95D6FFFF0000FFFF0300000082D1370082C2720182BF79010000FFFF0300000082A4BD01826D870282BF79010000FFFF020000009470FFFF906CFFFF0000FFFF020000009273FFFF8ABFFFFF0000FFFF0400000082B9870182ADA00182CD4C0182E70E010000FFFF0400000082744C02826D8702826AA002826E79020000FFFF0400000082BF7901826D870282A9A80182B78B010000FFFF0400000082CF1702826D870282C46C0182A2C4010000FFFF0200000095D6FFFF8AEDFFFF0000FFFF0400000082ABA401826D870282BD7E0182DC38010000FFFF05000000826C91024100EF02826D8702826AA002826E79020000FFFF0500000082B78B0182C66601815BD20182A9A801815BD2010000FFFF0300000082BF7901815BD20182C666010000FFFF0400000082AAE600826D870282B6AE0082E220010000FFFF000000006D7367001C080000020000009675FFFF8B4EFFFF0000FFFF030000008B43FFFF88E1FFFF82A2C4010000FFFF040000008B43FFFF82BF790182AAE60082A2C4010000FFFF0400000082ABA40182BF790182AAE60082A2C4010000FFFF030000008B43FFFF8BB6FFFF82A2C4010000FFFF0400000090B8FFFF905FFFFF9496FFFF8EE3FFFF0000FFFF04000000926DFFFF8C62FFFF9278FFFF82EA02010000FFFF0500000082BF790182A6B50182A8AE0182ADA00182EA02010000FFFF0200000096DAFFFF88C3FFFF0000FFFF0200000096BEFFFF96D3FFFF0000FFFF05000000A700CB0182ABA40182DF2B0182ADA00182E70E010000FFFF0400000082EBFC00826D870282CF170282E80A010000FFFF02000000E19AFFFF9561FFFF0000FFFF0300000082E70E0182A2C4019561FFFF0000FFFF020000009492FFFF9273FFFF0000FFFF01000000E6EAFFFF0000FFFF0300000082BF7901826D870282CE4D000000FFFF0100000098FBFFFF0000FFFF0200000098FBFFFF9943FFFF0000FFFF02000000936AFFFF8E45FFFF0000FFFF020000008CEEFFFF9048FFFF0000FFFF02000000E6CBFFFF96AFFFFF0000FFFF02000000E271FFFF91BDFFFF0000FFFF0200000094F1FFFF906CFFFF0000FFFF030000009056FFFF95BDFFFF96AFFFFF0000FFFF0400000093C1FFFF8EEAFFFF9594FFFF978EFFFF0000FFFF0500000082CE4D0082A9A80182BF790182E61201826D87020000FFFF0400000082BF790182E61201826D87028CF6FFFF0000FFFF0500000082C6660182E9060182B197019597FFFF9843FFFF0000FFFF0400000082C6660182E9060182B197018FECFFFF0000FFFF0200000096D1FFFF9382FFFF0000FFFF030000008D95FFFF826D870282DA18000000FFFF040000009573FFFF89C2FFFF9047FFFF96AFFFFF0000FFFF020000009484FFFF8F97FFFF0000FFFF0200000088FAFFFF9484FFFF0000FFFF020000008BADFFFF8AADFFFF0000FFFF0400000082E2200182E80A0182DC3801826D87020000FFFF0300000082DC3801826D870282B197010000FFFF0300000082A8AE0182DF2B0182B197010000FFFF0300000082BF7901826D870282DBF3010000FFFF0300000082BF7901826D870282DA18000000FFFF0400000082B9870182C2720182ADA00182B78B010000FFFF0300000024005C028264C702827730020000FFFF020000009484FFFF8F74FFFF0000FFFF020000009483FFFF8F74FFFF0000FFFF0300000082EA020182A2C40182D505020000FFFF0400000082B78B0182D8FC0182E9060182DC38010000FFFF0500000082ADA00182E80A0182C6660182E80A0182B78B010000FFFF0500000082D3440182A6B50182E70E0182BF790182A8AE010000FFFF0300000082B4B700815BD20196CBFFFF0000FFFF0400000082B4B700815BD20182DF2B01826D87020000FFFF0400000082B98701826D870282B8A50082E80A010000FFFF0400000082A8AE0182C8620182C95D01815BD2010000FFFF040000008265BD0282744C024300D802826AA0020000FFFF0400000082D34401A700CB0182C2720182ADA0010000FFFF0500000082BF790182E22001826D870282B1970182EBFC000000FFFF050000004200E0028268AE02827353024300D8028267B5020000FFFF040000004300D80282744C02826D8702827353020000FFFF05000000826F70028264C702826D87028268AE0224005C020000FFFF0300000082D8FC0182C95D0182B78B010000FFFF06000000827544024100EF028266C2028268AE02826D87024100EF020000FFFF0300000082CE4D0082ACDD0082C862010000FFFF0400000082D46700A700CB0182ACDD0082C862010000FFFF05000000826F700282744C0224005C0224005C02827828020000FFFF0200000090B8FFFF8974FFFF0000FFFF0400000082BF7901826D870282DBF30182B197010000FFFF0200000088FAFFFF9790FFFF0000FFFF020000009641FFFF9550FFFF0000FFFF020000008B54FFFF93AAFFFF0000FFFF0200000093F7FFFF965FFFFF0000FFFF02000000956EFFFF93FBFFFF0000FFFF0600000082BE800082C2720182BF790182EDF70082A2C40182D344010000FFFF020000009181FFFF9852FFFF0000FFFF0500000082A2C40182DC380182E70E0182BF790182A8AE010000FFFF0500000082A8AE0182C8620182C95D0182B78B0182C666010000FFFF0200000088A4FFFF8974FFFF0000FFFF020000008AE7FFFF8ECBFFFF0000FFFF020000009790FFFF8CF0FFFF0000FFFF02000000E3E8FFFF96E5FFFF0000FFFF0200000093F7FFFF92D9FFFF0000FFFF0300000090ABFFFF8AB4FFFF91D1FFFF0000FFFF0500000082A8AE01815BD20182AAE60082B8A50082DE30010000FFFF020000008EA9FFFF88D4FFFF0000FFFF0200000093FBFFFF97D6FFFF0000FFFF020000009278FFFF9852FFFF0000FFFF0200000097BDFFFF904AFFFF0000FFFF0200000096B2FFFF90B8FFFF0000FFFF010000008FA8FFFF0000FFFF020000008FA9FFFF9577FFFF0000FFFF020000008F97FFFF8941FFFF0000FFFF020000008EE8FFFF88FAFFFF0000FFFF020000008987FFFF8CF0FFFF0000FFFF0400000082E90601826D870282D8FC01826D87020000FFFF020000008941FFFF9594FFFF0000FFFF020000008941FFFF904FFFFF0000FFFF0200000088FAFFFF8D73FFFF0000FFFF02000000926AFFFF8DAAFFFF0000FFFF0300000082BF7901826D870296D1FFFF0000FFFF0200000093B6FFFF92E5FFFF0000FFFF0200000095EFFFFF8C73FFFF0000FFFF0200000093FBFFFF8EF1FFFF0000FFFF01000000E453FFFF0000FFFF0200000090C2FFFF8AADFFFF0000FFFF0200000096ECFFFF8AADFFFF0000FFFF00000000CA104E200031736B3030246D6A352E49414C4B5F7932524C714442576C316F7A434978354D65756A3143567A72315971706A72636F772D00CA1100014E2000005DF095950000000034B468235DCB72DC5DD49010020000000A00000004080A03060904080507";
+ res.WriteBytes(Util.FromHexString(d));
+ return res;
+ }
+ }
+}
diff --git a/Mhf.Server/QueueConsumer.cs b/Mhf.Server/QueueConsumer.cs
new file mode 100644
index 0000000..158a405
--- /dev/null
+++ b/Mhf.Server/QueueConsumer.cs
@@ -0,0 +1,363 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+using Arrowgene.Services;
+using Arrowgene.Services.Logging;
+using Arrowgene.Services.Networking.Tcp;
+using Arrowgene.Services.Networking.Tcp.Consumer;
+using Arrowgene.Services.Networking.Tcp.Consumer.BlockingQueueConsumption;
+using Arrowgene.Services.Networking.Tcp.Server.AsyncEvent;
+using Mhf.Server.Logging;
+using Mhf.Server.Model;
+using Mhf.Server.Packet;
+using Mhf.Server.Setting;
+
+namespace Mhf.Server
+{
+ public class QueueConsumer : IConsumer
+ {
+ public const int NoExpectedSize = -1;
+
+ private readonly BlockingCollection[] _queues;
+ private readonly Thread[] _threads;
+ private readonly Dictionary _clientHandlers;
+ private readonly Dictionary _connectionHandlers;
+ private readonly Dictionary _connections;
+ private readonly MhfLogger _logger;
+ private readonly object _lock;
+ private readonly int _maxUnitOfOrder;
+ private MhfSetting _setting;
+ private volatile bool _isRunning;
+
+ private CancellationTokenSource _cancellationTokenSource;
+
+ public int HandlersCount => _clientHandlers.Count;
+
+ public Action ClientDisconnected;
+ public Action ClientConnected;
+ public Action Started;
+ public Action Stopped;
+
+ public QueueConsumer(MhfSetting setting, AsyncEventSettings socketSetting)
+ {
+ _setting = setting;
+ _logger = LogProvider.Logger(this);
+ _maxUnitOfOrder = socketSetting.MaxUnitOfOrder;
+ _queues = new BlockingCollection[_maxUnitOfOrder];
+ _threads = new Thread[_maxUnitOfOrder];
+ _lock = new object();
+ _clientHandlers = new Dictionary();
+ _connectionHandlers = new Dictionary();
+ _connections = new Dictionary();
+ }
+
+ public void Clear()
+ {
+ _clientHandlers.Clear();
+ _connectionHandlers.Clear();
+ }
+
+ public void AddHandler(IClientHandler clientHandler, bool overwrite = false)
+ {
+ if (overwrite)
+ {
+ if (_clientHandlers.ContainsKey(clientHandler.Id))
+ {
+ _clientHandlers[clientHandler.Id] = clientHandler;
+ }
+ else
+ {
+ _clientHandlers.Add(clientHandler.Id, clientHandler);
+ }
+
+ return;
+ }
+
+ if (_clientHandlers.ContainsKey(clientHandler.Id))
+ {
+ _logger.Error($"ClientHandlerId: {clientHandler.Id} already exists");
+ }
+ else
+ {
+ _clientHandlers.Add(clientHandler.Id, clientHandler);
+ }
+ }
+
+ public void AddHandler(IConnectionHandler connectionHandler, bool overwrite = false)
+ {
+ if (overwrite)
+ {
+ if (_connectionHandlers.ContainsKey(connectionHandler.Id))
+ {
+ _connectionHandlers[connectionHandler.Id] = connectionHandler;
+ }
+ else
+ {
+ _connectionHandlers.Add(connectionHandler.Id, connectionHandler);
+ }
+
+ return;
+ }
+
+ if (_connectionHandlers.ContainsKey(connectionHandler.Id))
+ {
+ _logger.Error($"ConnectionHandlerId: {connectionHandler.Id} already exists");
+ }
+ else
+ {
+ _connectionHandlers.Add(connectionHandler.Id, connectionHandler);
+ }
+ }
+
+ private void HandleReceived(ITcpSocket socket, byte[] data)
+ {
+ if (!socket.IsAlive)
+ {
+ return;
+ }
+
+ MhfConnection connection;
+ lock (_lock)
+ {
+ if (!_connections.ContainsKey(socket))
+ {
+ _logger.Error(socket, $"Client does not exist in lookup");
+ return;
+ }
+
+ connection = _connections[socket];
+ }
+
+ List packets = connection.Receive(data);
+ foreach (MhfPacket packet in packets)
+ {
+ MhfClient client = connection.Client;
+ if (client != null)
+ {
+ HandleReceived_Client(client, packet);
+ }
+ else
+ {
+ HandleReceived_Connection(connection, packet);
+ }
+ }
+ }
+
+ private void HandleReceived_Connection(MhfConnection connection, MhfPacket packet)
+ {
+ if (!_connectionHandlers.ContainsKey(packet.Id))
+ {
+ // _logger.LogUnknownIncomingPacket(connection, packet);
+ return;
+ }
+
+ IConnectionHandler connectionHandler = _connectionHandlers[packet.Id];
+ if (connectionHandler.ExpectedSize != NoExpectedSize && packet.Data.Size < connectionHandler.ExpectedSize)
+ {
+ _logger.Error(connection,
+ $"Ignoring Packed (Id:{packet.Id}) is smaller ({packet.Data.Size}) than expected ({connectionHandler.ExpectedSize})");
+ return;
+ }
+
+ // _logger.LogIncomingPacket(connection, packet);
+ packet.Data.SetPositionStart();
+ try
+ {
+ connectionHandler.Handle(connection, packet);
+ }
+ catch (Exception ex)
+ {
+ _logger.Exception(connection, ex);
+ }
+ }
+
+ private void HandleReceived_Client(MhfClient client, MhfPacket packet)
+ {
+ if (!_clientHandlers.ContainsKey(packet.Id))
+ {
+ //_logger.LogUnknownIncomingPacket(client, packet);
+ return;
+ }
+
+ IClientHandler clientHandler = _clientHandlers[packet.Id];
+ if (clientHandler.ExpectedSize != NoExpectedSize && packet.Data.Size < clientHandler.ExpectedSize)
+ {
+ _logger.Error(client,
+ $"Ignoring Packed (Id:{packet.Id}) is smaller ({packet.Data.Size}) than expected ({clientHandler.ExpectedSize})");
+ return;
+ }
+
+ // _logger.LogIncomingPacket(client, packet);
+ packet.Data.SetPositionStart();
+ try
+ {
+ clientHandler.Handle(client, packet);
+ }
+ catch (Exception ex)
+ {
+ _logger.Exception(client, ex);
+ }
+ }
+
+ private void HandleDisconnected(ITcpSocket socket)
+ {
+ MhfConnection connection;
+ lock (_lock)
+ {
+ if (!_connections.ContainsKey(socket))
+ {
+ _logger.Error(socket, $"Disconnected client does not exist in lookup");
+ return;
+ }
+
+ connection = _connections[socket];
+ _connections.Remove(socket);
+ _logger.Debug($"Clients Count: {_connections.Count}");
+ }
+
+ Action onClientDisconnected = ClientDisconnected;
+ if (onClientDisconnected != null)
+ {
+ try
+ {
+ onClientDisconnected.Invoke(connection);
+ }
+ catch (Exception ex)
+ {
+ _logger.Exception(connection, ex);
+ }
+ }
+
+ _logger.Info(connection, $"Client disconnected");
+ }
+
+ private void HandleConnected(ITcpSocket socket)
+ {
+ MhfConnection connection = new MhfConnection(socket, new PacketFactory(_setting));
+ lock (_lock)
+ {
+ _connections.Add(socket, connection);
+ _logger.Debug($"Clients Count: {_connections.Count}");
+ }
+
+ Action onClientConnected = ClientConnected;
+ if (onClientConnected != null)
+ {
+ try
+ {
+ onClientConnected.Invoke(connection);
+ }
+ catch (Exception ex)
+ {
+ _logger.Exception(connection, ex);
+ }
+ }
+
+ _logger.Info(connection, $"Client connected");
+ }
+
+ private void Consume(int unitOfOrder)
+ {
+ while (_isRunning)
+ {
+ ClientEvent clientEvent;
+ try
+ {
+ clientEvent = _queues[unitOfOrder].Take(_cancellationTokenSource.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ return;
+ }
+
+ switch (clientEvent.ClientEventType)
+ {
+ case ClientEventType.ReceivedData:
+ HandleReceived(clientEvent.Socket, clientEvent.Data);
+ break;
+ case ClientEventType.Connected:
+ HandleConnected(clientEvent.Socket);
+ break;
+ case ClientEventType.Disconnected:
+ HandleDisconnected(clientEvent.Socket);
+ break;
+ }
+ }
+ }
+
+ void IConsumer.OnStart()
+ {
+ if (_isRunning)
+ {
+ _logger.Error($"Consumer already running.");
+ return;
+ }
+
+ _cancellationTokenSource = new CancellationTokenSource();
+ _isRunning = true;
+ for (int i = 0; i < _maxUnitOfOrder; i++)
+ {
+ int uuo = i;
+ _queues[i] = new BlockingCollection();
+ _threads[i] = new Thread(() => Consume(uuo));
+ _threads[i].Name = $"Consumer: {i}";
+ _logger.Info($"Starting Consumer: {i}");
+ _threads[i].Start();
+ }
+ }
+
+ public void OnStarted()
+ {
+ Action started = Started;
+ if (started != null)
+ {
+ started.Invoke();
+ }
+ }
+
+ void IConsumer.OnReceivedData(ITcpSocket socket, byte[] data)
+ {
+ _queues[socket.UnitOfOrder].Add(new ClientEvent(socket, ClientEventType.ReceivedData, data));
+ }
+
+ void IConsumer.OnClientDisconnected(ITcpSocket socket)
+ {
+ _queues[socket.UnitOfOrder].Add(new ClientEvent(socket, ClientEventType.Disconnected));
+ }
+
+ void IConsumer.OnClientConnected(ITcpSocket socket)
+ {
+ _queues[socket.UnitOfOrder].Add(new ClientEvent(socket, ClientEventType.Connected));
+ }
+
+ void IConsumer.OnStop()
+ {
+ if (!_isRunning)
+ {
+ _logger.Error($"Consumer already stopped.");
+ return;
+ }
+
+ _isRunning = false;
+ _cancellationTokenSource.Cancel();
+ for (int i = 0; i < _maxUnitOfOrder; i++)
+ {
+ Thread consumerThread = _threads[i];
+ _logger.Info($"Shutting Consumer: {i} down...");
+ Service.JoinThread(consumerThread, 10000, _logger);
+ _logger.Info($"Consumer: {i} ended.");
+ _threads[i] = null;
+ }
+ }
+
+ public void OnStopped()
+ {
+ Action stopped = Stopped;
+ if (stopped != null)
+ {
+ stopped.Invoke();
+ }
+ }
+ }
+}
diff --git a/Mhf.Server/Setting/DatabaseSetting.cs b/Mhf.Server/Setting/DatabaseSetting.cs
new file mode 100644
index 0000000..3eb6304
--- /dev/null
+++ b/Mhf.Server/Setting/DatabaseSetting.cs
@@ -0,0 +1,68 @@
+using System;
+using System.IO;
+using System.Runtime.Serialization;
+using Mhf.Server.Common;
+using Mhf.Server.Model;
+
+namespace Mhf.Server.Setting
+{
+ [DataContract]
+ public class DatabaseSetting
+ {
+ public DatabaseSetting()
+ {
+ Type = DatabaseType.SQLite;
+ SqLiteFolder = Path.Combine(Util.ExecutingDirectory(), "Files/Database");
+ Host = "localhost";
+ Port = 3306;
+ Database = "Mhf";
+ User = string.Empty;
+ Password = string.Empty;
+
+ string envDbType = Environment.GetEnvironmentVariable("DB_TYPE");
+ switch (envDbType)
+ {
+ case "sqlite":
+ Type = DatabaseType.SQLite;
+ break;
+ }
+
+ string envDbUser = Environment.GetEnvironmentVariable("DB_USER");
+ if (!string.IsNullOrEmpty(envDbUser))
+ {
+ User = envDbUser;
+ }
+
+ string envDbPass = Environment.GetEnvironmentVariable("DB_PASS");
+ if (!string.IsNullOrEmpty(envDbPass))
+ {
+ Password = envDbPass;
+ }
+ }
+
+ public DatabaseSetting(DatabaseSetting databaseSettings)
+ {
+ Type = databaseSettings.Type;
+ SqLiteFolder = databaseSettings.SqLiteFolder;
+ Host = databaseSettings.Host;
+ Port = databaseSettings.Port;
+ User = databaseSettings.User;
+ Password = databaseSettings.Password;
+ Database = databaseSettings.Database;
+ }
+
+ [DataMember(Order = 0)] public DatabaseType Type { get; set; }
+
+ [DataMember(Order = 1)] public string SqLiteFolder { get; set; }
+
+ [DataMember(Order = 2)] public string Host { get; set; }
+
+ [DataMember(Order = 3)] public short Port { get; set; }
+
+ [DataMember(Order = 4)] public string User { get; set; }
+
+ [DataMember(Order = 5)] public string Password { get; set; }
+
+ [DataMember(Order = 6)] public string Database { get; set; }
+ }
+}
diff --git a/Mhf.Server/Setting/MhfSetting.cs b/Mhf.Server/Setting/MhfSetting.cs
new file mode 100644
index 0000000..dde4da5
--- /dev/null
+++ b/Mhf.Server/Setting/MhfSetting.cs
@@ -0,0 +1,105 @@
+using System.IO;
+using System.Net;
+using System.Runtime.Serialization;
+using Arrowgene.Services.Networking.Tcp.Server.AsyncEvent;
+using Mhf.Server.Common;
+
+namespace Mhf.Server.Setting
+{
+ [DataContract]
+ public class MhfSetting
+ {
+ ///
+ /// Warning:
+ /// Changing while having existing accounts requires to rehash all passwords.
+ /// The number is log2, so adding +1 doubles the time it takes.
+ /// https://wildlyinaccurate.com/bcrypt-choosing-a-work-factor/
+ ///
+ public const int BCryptWorkFactor = 10;
+
+ [IgnoreDataMember] public IPAddress ListenIpAddress { get; set; }
+
+ [DataMember(Name = "ListenIpAddress", Order = 0)]
+ public string DataListenIpAddress
+ {
+ get => ListenIpAddress.ToString();
+ set => ListenIpAddress = string.IsNullOrEmpty(value) ? null : IPAddress.Parse(value);
+ }
+
+ [IgnoreDataMember] public IPAddress AuthServerIpAddress { get; set; }
+
+ [DataMember(Name = "AuthIpAddress", Order = 1)]
+ public string DataAuthIpAddress
+ {
+ get => AuthServerIpAddress.ToString();
+ set => AuthServerIpAddress = string.IsNullOrEmpty(value) ? null : IPAddress.Parse(value);
+ }
+
+ [DataMember(Order = 2)] public ushort AuthServerPort { get; set; }
+
+ [IgnoreDataMember] public IPAddress LobbyServerIpAddress { get; set; }
+
+ [DataMember(Name = "LobbyIpAddress", Order = 3)]
+ public string DataLobbyIpAddress
+ {
+ get => LobbyServerIpAddress.ToString();
+ set => LobbyServerIpAddress = string.IsNullOrEmpty(value) ? null : IPAddress.Parse(value);
+ }
+
+ [DataMember(Order = 4)] public ushort LobbyServerPort { get; set; }
+
+ [DataMember(Order = 10)] public bool NeedRegistration { get; set; }
+
+ [DataMember(Order = 20)] public int LogLevel { get; set; }
+
+ [DataMember(Order = 21)] public bool LogUnknownIncomingPackets { get; set; }
+
+ [DataMember(Order = 22)] public bool LogOutgoingPackets { get; set; }
+
+ [DataMember(Order = 23)] public bool LogIncomingPackets { get; set; }
+
+ [DataMember(Order = 40)] public string FilesFolder { get; set; }
+
+ [DataMember(Order = 60)] public WebSetting WebSetting { get; set; }
+ [DataMember(Order = 70)] public DatabaseSetting DatabaseSetting { get; set; }
+
+ [DataMember(Order = 100)] public AsyncEventSettings ServerSocketSettings { get; set; }
+
+ public MhfSetting()
+ {
+ ListenIpAddress = IPAddress.Any;
+ AuthServerIpAddress = IPAddress.Loopback;
+ AuthServerPort = 53312;
+ LobbyServerIpAddress = IPAddress.Loopback;
+ LobbyServerPort = 53310;
+ NeedRegistration = false;
+ LogLevel = 0;
+ LogUnknownIncomingPackets = true;
+ LogOutgoingPackets = true;
+ LogIncomingPackets = true;
+ WebSetting = new WebSetting();
+ DatabaseSetting = new DatabaseSetting();
+ ServerSocketSettings = new AsyncEventSettings();
+ ServerSocketSettings.MaxUnitOfOrder = 2;
+ FilesFolder = Path.Combine(Util.ExecutingDirectory(), "Files");
+ }
+
+ public MhfSetting(MhfSetting setting)
+ {
+ ListenIpAddress = setting.ListenIpAddress;
+ AuthServerIpAddress = setting.AuthServerIpAddress;
+ AuthServerPort = setting.AuthServerPort;
+ LobbyServerIpAddress = setting.LobbyServerIpAddress;
+ LobbyServerPort = setting.LobbyServerPort;
+ NeedRegistration = setting.NeedRegistration;
+ LogLevel = setting.LogLevel;
+ LogUnknownIncomingPackets = setting.LogUnknownIncomingPackets;
+ LogOutgoingPackets = setting.LogOutgoingPackets;
+ LogIncomingPackets = setting.LogIncomingPackets;
+ WebSetting = setting.WebSetting;
+ FilesFolder = setting.FilesFolder;
+ DatabaseSetting = new DatabaseSetting(setting.DatabaseSetting);
+ ServerSocketSettings = new AsyncEventSettings(setting.ServerSocketSettings);
+ }
+ }
+}
diff --git a/Mhf.Server/Setting/SettingProvider.cs b/Mhf.Server/Setting/SettingProvider.cs
new file mode 100644
index 0000000..2f10f02
--- /dev/null
+++ b/Mhf.Server/Setting/SettingProvider.cs
@@ -0,0 +1,53 @@
+using System.IO;
+using Arrowgene.Services.Serialization;
+using Mhf.Server.Common;
+
+namespace Mhf.Server.Setting
+{
+ public class SettingProvider
+ {
+ private readonly string _directory;
+
+
+ public SettingProvider(string directory = null)
+ {
+ if (Directory.Exists(directory))
+ {
+ _directory = directory;
+ }
+ else
+ {
+ _directory = Util.ExecutingDirectory();
+ }
+ }
+
+ public string GetSettingsPath(string file)
+ {
+ return Path.Combine(_directory, file);
+ }
+
+ public void Save(T settings, string file)
+ {
+ string path = GetSettingsPath(file);
+ string json = JsonSerializer.Serialize(settings);
+ File.WriteAllText(path, json);
+ }
+
+ public T Load(string file)
+ {
+ T settings;
+ string path = GetSettingsPath(file);
+ if (File.Exists(path))
+ {
+ string json = File.ReadAllText(path);
+ settings = JsonSerializer.Deserialize(json);
+ }
+ else
+ {
+ settings = default(T);
+ }
+
+ return settings;
+ }
+ }
+}
diff --git a/Mhf.Server/Setting/WebSetting.cs b/Mhf.Server/Setting/WebSetting.cs
new file mode 100644
index 0000000..26c75ef
--- /dev/null
+++ b/Mhf.Server/Setting/WebSetting.cs
@@ -0,0 +1,37 @@
+using System.IO;
+using System.Runtime.Serialization;
+using Mhf.Server.Common;
+
+namespace Mhf.Server.Setting
+{
+ [DataContract]
+ public class WebSetting
+ {
+ public WebSetting()
+ {
+ ServerHeader = null;
+ WebFolder = Path.Combine(Util.ExecutingDirectory(), "Files/www");
+ HttpPort = 80;
+ HttpsEnabled = true;
+ HttpsPort = 443;
+ HttpsCertPath = Path.Combine(Util.ExecutingDirectory(), "Files/mhf.pfx");
+ }
+
+ public WebSetting(WebSetting webSetting)
+ {
+ ServerHeader = webSetting.ServerHeader;
+ WebFolder = webSetting.WebFolder;
+ HttpPort = webSetting.HttpPort;
+ HttpsEnabled = webSetting.HttpsEnabled;
+ HttpsPort = webSetting.HttpsPort;
+ HttpsCertPath = webSetting.HttpsCertPath;
+ }
+
+ [DataMember(Order = 1)] public string ServerHeader { get; set; }
+ [DataMember(Order = 2)] public short HttpPort { get; set; }
+ [DataMember(Order = 3)] public bool HttpsEnabled { get; set; }
+ [DataMember(Order = 4)] public short HttpsPort { get; set; }
+ [DataMember(Order = 5)] public string HttpsCertPath { get; set; }
+ [DataMember(Order = 6)] public string WebFolder { get; set; }
+ }
+}
diff --git a/Mhf.Server/Web/Middleware/IWebMiddleware.cs b/Mhf.Server/Web/Middleware/IWebMiddleware.cs
new file mode 100644
index 0000000..82a7f9d
--- /dev/null
+++ b/Mhf.Server/Web/Middleware/IWebMiddleware.cs
@@ -0,0 +1,12 @@
+using System.Threading.Tasks;
+
+namespace Mhf.Server.Web.Middleware
+{
+ ///
+ /// Defines a middleware
+ ///
+ public interface IWebMiddleware
+ {
+ Task Handle(WebRequest request, WebMiddlewareDelegate next);
+ }
+}
diff --git a/Mhf.Server/Web/Middleware/WebMiddleware.cs b/Mhf.Server/Web/Middleware/WebMiddleware.cs
new file mode 100644
index 0000000..2ba72a4
--- /dev/null
+++ b/Mhf.Server/Web/Middleware/WebMiddleware.cs
@@ -0,0 +1,11 @@
+using System.Threading.Tasks;
+using Arrowgene.Services.Logging;
+
+namespace Mhf.Server.Web.Middleware
+{
+ public abstract class WebMiddleware : IWebMiddleware
+ {
+ protected ILogger Logger => LogProvider.Instance.GetLogger(this);
+ public abstract Task Handle(WebRequest request, WebMiddlewareDelegate next);
+ }
+}
diff --git a/Mhf.Server/Web/Middleware/WebMiddlewareDelegate.cs b/Mhf.Server/Web/Middleware/WebMiddlewareDelegate.cs
new file mode 100644
index 0000000..5de718a
--- /dev/null
+++ b/Mhf.Server/Web/Middleware/WebMiddlewareDelegate.cs
@@ -0,0 +1,6 @@
+using System.Threading.Tasks;
+
+namespace Mhf.Server.Web.Middleware
+{
+ public delegate Task WebMiddlewareDelegate(WebRequest request);
+}
diff --git a/Mhf.Server/Web/Middleware/WebMiddlewareStack.cs b/Mhf.Server/Web/Middleware/WebMiddlewareStack.cs
new file mode 100644
index 0000000..97d1426
--- /dev/null
+++ b/Mhf.Server/Web/Middleware/WebMiddlewareStack.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Mhf.Server.Web.Middleware
+{
+ ///
+ /// Implementation of a middleware
+ ///
+ public class WebMiddlewareStack
+ {
+ private WebMiddlewareDelegate _webMiddlewareDelegate;
+
+ public WebMiddlewareStack(WebMiddlewareDelegate kernel)
+ {
+ _webMiddlewareDelegate = kernel;
+ }
+
+ public Task Start(WebRequest request)
+ {
+ return _webMiddlewareDelegate(request);
+ }
+
+ public void Use(Func middleware)
+ {
+ _webMiddlewareDelegate = middleware(_webMiddlewareDelegate);
+ }
+ }
+}
diff --git a/Mhf.Server/Web/MimeTypeMap.cs b/Mhf.Server/Web/MimeTypeMap.cs
new file mode 100644
index 0000000..5661706
--- /dev/null
+++ b/Mhf.Server/Web/MimeTypeMap.cs
@@ -0,0 +1,655 @@
+namespace Mhf.Server.Web
+{
+ using System;
+ using System.Collections.Generic;
+
+ public static class MimeTypeMap
+ {
+ private static readonly IDictionary _mappings =
+ new Dictionary(BuildMappings());
+
+ private static IDictionary BuildMappings()
+ {
+ var mappings = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ // Monster Hunter Specific
+ {".xml", "application/xml"},
+ {".exe", "application/x-msdownload"},
+ {".dat", "application/octet-stream"},
+ // General
+ {".323", "text/h323"},
+ {".3g2", "video/3gpp2"},
+ {".3gp", "video/3gpp"},
+ {".3gp2", "video/3gpp2"},
+ {".3gpp", "video/3gpp"},
+ {".7z", "application/x-7z-compressed"},
+ {".aa", "audio/audible"},
+ {".AAC", "audio/aac"},
+ {".aaf", "application/octet-stream"},
+ {".aax", "audio/vnd.audible.aax"},
+ {".ac3", "audio/ac3"},
+ {".aca", "application/octet-stream"},
+ {".accda", "application/msaccess.addin"},
+ {".accdb", "application/msaccess"},
+ {".accdc", "application/msaccess.cab"},
+ {".accde", "application/msaccess"},
+ {".accdr", "application/msaccess.runtime"},
+ {".accdt", "application/msaccess"},
+ {".accdw", "application/msaccess.webapplication"},
+ {".accft", "application/msaccess.ftemplate"},
+ {".acx", "application/internet-property-stream"},
+ {".AddIn", "text/xml"},
+ {".ade", "application/msaccess"},
+ {".adobebridge", "application/x-bridge-url"},
+ {".adp", "application/msaccess"},
+ {".ADT", "audio/vnd.dlna.adts"},
+ {".ADTS", "audio/aac"},
+ {".afm", "application/octet-stream"},
+ {".ai", "application/postscript"},
+ {".aif", "audio/aiff"},
+ {".aifc", "audio/aiff"},
+ {".aiff", "audio/aiff"},
+ {".air", "application/vnd.adobe.air-application-installer-package+zip"},
+ {".amc", "application/mpeg"},
+ {".anx", "application/annodex"},
+ {".apk", "application/vnd.android.package-archive"},
+ {".application", "application/x-ms-application"},
+ {".art", "image/x-jg"},
+ {".asa", "application/xml"},
+ {".asax", "application/xml"},
+ {".ascx", "application/xml"},
+ {".asd", "application/octet-stream"},
+ {".asf", "video/x-ms-asf"},
+ {".ashx", "application/xml"},
+ {".asi", "application/octet-stream"},
+ {".asm", "text/plain"},
+ {".asmx", "application/xml"},
+ {".aspx", "application/xml"},
+ {".asr", "video/x-ms-asf"},
+ {".asx", "video/x-ms-asf"},
+ {".atom", "application/atom+xml"},
+ {".au", "audio/basic"},
+ {".avi", "video/x-msvideo"},
+ {".axa", "audio/annodex"},
+ {".axs", "application/olescript"},
+ {".axv", "video/annodex"},
+ {".bas", "text/plain"},
+ {".bcpio", "application/x-bcpio"},
+ {".bin", "application/octet-stream"},
+ {".bmp", "image/bmp"},
+ {".c", "text/plain"},
+ {".cab", "application/octet-stream"},
+ {".caf", "audio/x-caf"},
+ {".calx", "application/vnd.ms-office.calx"},
+ {".cat", "application/vnd.ms-pki.seccat"},
+ {".cc", "text/plain"},
+ {".cd", "text/plain"},
+ {".cdda", "audio/aiff"},
+ {".cdf", "application/x-cdf"},
+ {".cer", "application/x-x509-ca-cert"},
+ {".cfg", "text/plain"},
+ {".chm", "application/octet-stream"},
+ {".class", "application/x-java-applet"},
+ {".clp", "application/x-msclip"},
+ {".cmd", "text/plain"},
+ {".cmx", "image/x-cmx"},
+ {".cnf", "text/plain"},
+ {".cod", "image/cis-cod"},
+ {".config", "application/xml"},
+ {".contact", "text/x-ms-contact"},
+ {".coverage", "application/xml"},
+ {".cpio", "application/x-cpio"},
+ {".cpp", "text/plain"},
+ {".crd", "application/x-mscardfile"},
+ {".crl", "application/pkix-crl"},
+ {".crt", "application/x-x509-ca-cert"},
+ {".cs", "text/plain"},
+ {".csdproj", "text/plain"},
+ {".csh", "application/x-csh"},
+ {".csproj", "text/plain"},
+ {".css", "text/css"},
+ {".csv", "text/csv"},
+ {".cur", "application/octet-stream"},
+ {".cxx", "text/plain"},
+ {".datasource", "application/xml"},
+ {".dbproj", "text/plain"},
+ {".dcr", "application/x-director"},
+ {".def", "text/plain"},
+ {".deploy", "application/octet-stream"},
+ {".der", "application/x-x509-ca-cert"},
+ {".dgml", "application/xml"},
+ {".dib", "image/bmp"},
+ {".dif", "video/x-dv"},
+ {".dir", "application/x-director"},
+ {".disco", "text/xml"},
+ {".divx", "video/divx"},
+ {".dll", "application/x-msdownload"},
+ {".dll.config", "text/xml"},
+ {".dlm", "text/dlm"},
+ {".doc", "application/msword"},
+ {".docm", "application/vnd.ms-word.document.macroEnabled.12"},
+ {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
+ {".dot", "application/msword"},
+ {".dotm", "application/vnd.ms-word.template.macroEnabled.12"},
+ {".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
+ {".dsp", "application/octet-stream"},
+ {".dsw", "text/plain"},
+ {".dtd", "text/xml"},
+ {".dtsConfig", "text/xml"},
+ {".dv", "video/x-dv"},
+ {".dvi", "application/x-dvi"},
+ {".dwf", "drawing/x-dwf"},
+ {".dwg", "application/acad"},
+ {".dwp", "application/octet-stream"},
+ {".dxf", "application/x-dxf"},
+ {".dxr", "application/x-director"},
+ {".eml", "message/rfc822"},
+ {".emz", "application/octet-stream"},
+ {".eot", "application/vnd.ms-fontobject"},
+ {".eps", "application/postscript"},
+ {".es", "application/ecmascript"},
+ {".etl", "application/etl"},
+ {".etx", "text/x-setext"},
+ {".evy", "application/envoy"},
+
+ {".exe.config", "text/xml"},
+ {".f4v", "video/mp4"},
+ {".fdf", "application/vnd.fdf"},
+ {".fif", "application/fractals"},
+ {".filters", "application/xml"},
+ {".fla", "application/octet-stream"},
+ {".flac", "audio/flac"},
+ {".flr", "x-world/x-vrml"},
+ {".flv", "video/x-flv"},
+ {".fsscript", "application/fsharp-script"},
+ {".fsx", "application/fsharp-script"},
+ {".generictest", "application/xml"},
+ {".gif", "image/gif"},
+ {".gpx", "application/gpx+xml"},
+ {".group", "text/x-ms-group"},
+ {".gsm", "audio/x-gsm"},
+ {".gtar", "application/x-gtar"},
+ {".gz", "application/x-gzip"},
+ {".h", "text/plain"},
+ {".hdf", "application/x-hdf"},
+ {".hdml", "text/x-hdml"},
+ {".hhc", "application/x-oleobject"},
+ {".hhk", "application/octet-stream"},
+ {".hhp", "application/octet-stream"},
+ {".hlp", "application/winhlp"},
+ {".hpp", "text/plain"},
+ {".hqx", "application/mac-binhex40"},
+ {".hta", "application/hta"},
+ {".htc", "text/x-component"},
+ {".htm", "text/html"},
+ {".html", "text/html"},
+ {".htt", "text/webviewhtml"},
+ {".hxa", "application/xml"},
+ {".hxc", "application/xml"},
+ {".hxd", "application/octet-stream"},
+ {".hxe", "application/xml"},
+ {".hxf", "application/xml"},
+ {".hxh", "application/octet-stream"},
+ {".hxi", "application/octet-stream"},
+ {".hxk", "application/xml"},
+ {".hxq", "application/octet-stream"},
+ {".hxr", "application/octet-stream"},
+ {".hxs", "application/octet-stream"},
+ {".hxt", "text/html"},
+ {".hxv", "application/xml"},
+ {".hxw", "application/octet-stream"},
+ {".hxx", "text/plain"},
+ {".i", "text/plain"},
+ {".ico", "image/x-icon"},
+ {".ics", "application/octet-stream"},
+ {".idl", "text/plain"},
+ {".ief", "image/ief"},
+ {".iii", "application/x-iphone"},
+ {".inc", "text/plain"},
+ {".inf", "application/octet-stream"},
+ {".ini", "text/plain"},
+ {".inl", "text/plain"},
+ {".ins", "application/x-internet-signup"},
+ {".ipa", "application/x-itunes-ipa"},
+ {".ipg", "application/x-itunes-ipg"},
+ {".ipproj", "text/plain"},
+ {".ipsw", "application/x-itunes-ipsw"},
+ {".iqy", "text/x-ms-iqy"},
+ {".isp", "application/x-internet-signup"},
+ {".isma", "application/octet-stream"},
+ {".ismv", "application/octet-stream"},
+ {".ite", "application/x-itunes-ite"},
+ {".itlp", "application/x-itunes-itlp"},
+ {".itms", "application/x-itunes-itms"},
+ {".itpc", "application/x-itunes-itpc"},
+ {".IVF", "video/x-ivf"},
+ {".jar", "application/java-archive"},
+ {".java", "application/octet-stream"},
+ {".jck", "application/liquidmotion"},
+ {".jcz", "application/liquidmotion"},
+ {".jfif", "image/pjpeg"},
+ {".jnlp", "application/x-java-jnlp-file"},
+ {".jpb", "application/octet-stream"},
+ {".jpe", "image/jpeg"},
+ {".jpeg", "image/jpeg"},
+ {".jpg", "image/jpeg"},
+ {".js", "application/javascript"},
+ {".json", "application/json"},
+ {".jsx", "text/jscript"},
+ {".jsxbin", "text/plain"},
+ {".latex", "application/x-latex"},
+ {".library-ms", "application/windows-library+xml"},
+ {".lit", "application/x-ms-reader"},
+ {".loadtest", "application/xml"},
+ {".lpk", "application/octet-stream"},
+ {".lsf", "video/x-la-asf"},
+ {".lst", "text/plain"},
+ {".lsx", "video/x-la-asf"},
+ {".lzh", "application/octet-stream"},
+ {".m13", "application/x-msmediaview"},
+ {".m14", "application/x-msmediaview"},
+ {".m1v", "video/mpeg"},
+ {".m2t", "video/vnd.dlna.mpeg-tts"},
+ {".m2ts", "video/vnd.dlna.mpeg-tts"},
+ {".m2v", "video/mpeg"},
+ {".m3u", "audio/x-mpegurl"},
+ {".m3u8", "audio/x-mpegurl"},
+ {".m4a", "audio/m4a"},
+ {".m4b", "audio/m4b"},
+ {".m4p", "audio/m4p"},
+ {".m4r", "audio/x-m4r"},
+ {".m4v", "video/x-m4v"},
+ {".mac", "image/x-macpaint"},
+ {".mak", "text/plain"},
+ {".man", "application/x-troff-man"},
+ {".manifest", "application/x-ms-manifest"},
+ {".map", "text/plain"},
+ {".master", "application/xml"},
+ {".mbox", "application/mbox"},
+ {".mda", "application/msaccess"},
+ {".mdb", "application/x-msaccess"},
+ {".mde", "application/msaccess"},
+ {".mdp", "application/octet-stream"},
+ {".me", "application/x-troff-me"},
+ {".mfp", "application/x-shockwave-flash"},
+ {".mht", "message/rfc822"},
+ {".mhtml", "message/rfc822"},
+ {".mid", "audio/mid"},
+ {".midi", "audio/mid"},
+ {".mix", "application/octet-stream"},
+ {".mk", "text/plain"},
+ {".mk3d", "video/x-matroska-3d"},
+ {".mka", "audio/x-matroska"},
+ {".mkv", "video/x-matroska"},
+ {".mmf", "application/x-smaf"},
+ {".mno", "text/xml"},
+ {".mny", "application/x-msmoney"},
+ {".mod", "video/mpeg"},
+ {".mov", "video/quicktime"},
+ {".movie", "video/x-sgi-movie"},
+ {".mp2", "video/mpeg"},
+ {".mp2v", "video/mpeg"},
+ {".mp3", "audio/mpeg"},
+ {".mp4", "video/mp4"},
+ {".mp4v", "video/mp4"},
+ {".mpa", "video/mpeg"},
+ {".mpe", "video/mpeg"},
+ {".mpeg", "video/mpeg"},
+ {".mpf", "application/vnd.ms-mediapackage"},
+ {".mpg", "video/mpeg"},
+ {".mpp", "application/vnd.ms-project"},
+ {".mpv2", "video/mpeg"},
+ {".mqv", "video/quicktime"},
+ {".ms", "application/x-troff-ms"},
+ {".msg", "application/vnd.ms-outlook"},
+ {".msi", "application/octet-stream"},
+ {".mso", "application/octet-stream"},
+ {".mts", "video/vnd.dlna.mpeg-tts"},
+ {".mtx", "application/xml"},
+ {".mvb", "application/x-msmediaview"},
+ {".mvc", "application/x-miva-compiled"},
+ {".mxf", "application/mxf"},
+ {".mxp", "application/x-mmxp"},
+ {".nc", "application/x-netcdf"},
+ {".nsc", "video/x-ms-asf"},
+ {".nws", "message/rfc822"},
+ {".ocx", "application/octet-stream"},
+ {".oda", "application/oda"},
+ {".odb", "application/vnd.oasis.opendocument.database"},
+ {".odc", "application/vnd.oasis.opendocument.chart"},
+ {".odf", "application/vnd.oasis.opendocument.formula"},
+ {".odg", "application/vnd.oasis.opendocument.graphics"},
+ {".odh", "text/plain"},
+ {".odi", "application/vnd.oasis.opendocument.image"},
+ {".odl", "text/plain"},
+ {".odm", "application/vnd.oasis.opendocument.text-master"},
+ {".odp", "application/vnd.oasis.opendocument.presentation"},
+ {".ods", "application/vnd.oasis.opendocument.spreadsheet"},
+ {".odt", "application/vnd.oasis.opendocument.text"},
+ {".oga", "audio/ogg"},
+ {".ogg", "audio/ogg"},
+ {".ogv", "video/ogg"},
+ {".ogx", "application/ogg"},
+ {".one", "application/onenote"},
+ {".onea", "application/onenote"},
+ {".onepkg", "application/onenote"},
+ {".onetmp", "application/onenote"},
+ {".onetoc", "application/onenote"},
+ {".onetoc2", "application/onenote"},
+ {".opus", "audio/ogg"},
+ {".orderedtest", "application/xml"},
+ {".osdx", "application/opensearchdescription+xml"},
+ {".otf", "application/font-sfnt"},
+ {".otg", "application/vnd.oasis.opendocument.graphics-template"},
+ {".oth", "application/vnd.oasis.opendocument.text-web"},
+ {".otp", "application/vnd.oasis.opendocument.presentation-template"},
+ {".ots", "application/vnd.oasis.opendocument.spreadsheet-template"},
+ {".ott", "application/vnd.oasis.opendocument.text-template"},
+ {".oxt", "application/vnd.openofficeorg.extension"},
+ {".p10", "application/pkcs10"},
+ {".p12", "application/x-pkcs12"},
+ {".p7b", "application/x-pkcs7-certificates"},
+ {".p7c", "application/pkcs7-mime"},
+ {".p7m", "application/pkcs7-mime"},
+ {".p7r", "application/x-pkcs7-certreqresp"},
+ {".p7s", "application/pkcs7-signature"},
+ {".pbm", "image/x-portable-bitmap"},
+ {".pcast", "application/x-podcast"},
+ {".pct", "image/pict"},
+ {".pcx", "application/octet-stream"},
+ {".pcz", "application/octet-stream"},
+ {".pdf", "application/pdf"},
+ {".pfb", "application/octet-stream"},
+ {".pfm", "application/octet-stream"},
+ {".pfx", "application/x-pkcs12"},
+ {".pgm", "image/x-portable-graymap"},
+ {".pic", "image/pict"},
+ {".pict", "image/pict"},
+ {".pkgdef", "text/plain"},
+ {".pkgundef", "text/plain"},
+ {".pko", "application/vnd.ms-pki.pko"},
+ {".pls", "audio/scpls"},
+ {".pma", "application/x-perfmon"},
+ {".pmc", "application/x-perfmon"},
+ {".pml", "application/x-perfmon"},
+ {".pmr", "application/x-perfmon"},
+ {".pmw", "application/x-perfmon"},
+ {".png", "image/png"},
+ {".pnm", "image/x-portable-anymap"},
+ {".pnt", "image/x-macpaint"},
+ {".pntg", "image/x-macpaint"},
+ {".pnz", "image/png"},
+ {".pot", "application/vnd.ms-powerpoint"},
+ {".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"},
+ {".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"},
+ {".ppa", "application/vnd.ms-powerpoint"},
+ {".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"},
+ {".ppm", "image/x-portable-pixmap"},
+ {".pps", "application/vnd.ms-powerpoint"},
+ {".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"},
+ {".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
+ {".ppt", "application/vnd.ms-powerpoint"},
+ {".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"},
+ {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
+ {".prf", "application/pics-rules"},
+ {".prm", "application/octet-stream"},
+ {".prx", "application/octet-stream"},
+ {".ps", "application/postscript"},
+ {".psc1", "application/PowerShell"},
+ {".psd", "application/octet-stream"},
+ {".psess", "application/xml"},
+ {".psm", "application/octet-stream"},
+ {".psp", "application/octet-stream"},
+ {".pst", "application/vnd.ms-outlook"},
+ {".pub", "application/x-mspublisher"},
+ {".pwz", "application/vnd.ms-powerpoint"},
+ {".qht", "text/x-html-insertion"},
+ {".qhtm", "text/x-html-insertion"},
+ {".qt", "video/quicktime"},
+ {".qti", "image/x-quicktime"},
+ {".qtif", "image/x-quicktime"},
+ {".qtl", "application/x-quicktimeplayer"},
+ {".qxd", "application/octet-stream"},
+ {".ra", "audio/x-pn-realaudio"},
+ {".ram", "audio/x-pn-realaudio"},
+ {".rar", "application/x-rar-compressed"},
+ {".ras", "image/x-cmu-raster"},
+ {".rat", "application/rat-file"},
+ {".rc", "text/plain"},
+ {".rc2", "text/plain"},
+ {".rct", "text/plain"},
+ {".rdlc", "application/xml"},
+ {".reg", "text/plain"},
+ {".resx", "application/xml"},
+ {".rf", "image/vnd.rn-realflash"},
+ {".rgb", "image/x-rgb"},
+ {".rgs", "text/plain"},
+ {".rm", "application/vnd.rn-realmedia"},
+ {".rmi", "audio/mid"},
+ {".rmp", "application/vnd.rn-rn_music_package"},
+ {".roff", "application/x-troff"},
+ {".rpm", "audio/x-pn-realaudio-plugin"},
+ {".rqy", "text/x-ms-rqy"},
+ {".rtf", "application/rtf"},
+ {".rtx", "text/richtext"},
+ {".rvt", "application/octet-stream"},
+ {".ruleset", "application/xml"},
+ {".s", "text/plain"},
+ {".safariextz", "application/x-safari-safariextz"},
+ {".scd", "application/x-msschedule"},
+ {".scr", "text/plain"},
+ {".sct", "text/scriptlet"},
+ {".sd2", "audio/x-sd2"},
+ {".sdp", "application/sdp"},
+ {".sea", "application/octet-stream"},
+ {".searchConnector-ms", "application/windows-search-connector+xml"},
+ {".setpay", "application/set-payment-initiation"},
+ {".setreg", "application/set-registration-initiation"},
+ {".settings", "application/xml"},
+ {".sgimb", "application/x-sgimb"},
+ {".sgml", "text/sgml"},
+ {".sh", "application/x-sh"},
+ {".shar", "application/x-shar"},
+ {".shtml", "text/html"},
+ {".sit", "application/x-stuffit"},
+ {".sitemap", "application/xml"},
+ {".skin", "application/xml"},
+ {".skp", "application/x-koan"},
+ {".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"},
+ {".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"},
+ {".slk", "application/vnd.ms-excel"},
+ {".sln", "text/plain"},
+ {".slupkg-ms", "application/x-ms-license"},
+ {".smd", "audio/x-smd"},
+ {".smi", "application/octet-stream"},
+ {".smx", "audio/x-smd"},
+ {".smz", "audio/x-smd"},
+ {".snd", "audio/basic"},
+ {".snippet", "application/xml"},
+ {".snp", "application/octet-stream"},
+ {".sol", "text/plain"},
+ {".sor", "text/plain"},
+ {".spc", "application/x-pkcs7-certificates"},
+ {".spl", "application/futuresplash"},
+ {".spx", "audio/ogg"},
+ {".src", "application/x-wais-source"},
+ {".srf", "text/plain"},
+ {".SSISDeploymentManifest", "text/xml"},
+ {".ssm", "application/streamingmedia"},
+ {".sst", "application/vnd.ms-pki.certstore"},
+ {".stl", "application/vnd.ms-pki.stl"},
+ {".sv4cpio", "application/x-sv4cpio"},
+ {".sv4crc", "application/x-sv4crc"},
+ {".svc", "application/xml"},
+ {".svg", "image/svg+xml"},
+ {".swf", "application/x-shockwave-flash"},
+ {".step", "application/step"},
+ {".stp", "application/step"},
+ {".t", "application/x-troff"},
+ {".tar", "application/x-tar"},
+ {".tcl", "application/x-tcl"},
+ {".testrunconfig", "application/xml"},
+ {".testsettings", "application/xml"},
+ {".tex", "application/x-tex"},
+ {".texi", "application/x-texinfo"},
+ {".texinfo", "application/x-texinfo"},
+ {".tgz", "application/x-compressed"},
+ {".thmx", "application/vnd.ms-officetheme"},
+ {".thn", "application/octet-stream"},
+ {".tif", "image/tiff"},
+ {".tiff", "image/tiff"},
+ {".tlh", "text/plain"},
+ {".tli", "text/plain"},
+ {".toc", "application/octet-stream"},
+ {".tr", "application/x-troff"},
+ {".trm", "application/x-msterminal"},
+ {".trx", "application/xml"},
+ {".ts", "video/vnd.dlna.mpeg-tts"},
+ {".tsv", "text/tab-separated-values"},
+ {".ttf", "application/font-sfnt"},
+ {".tts", "video/vnd.dlna.mpeg-tts"},
+ {".txt", "text/plain"},
+ {".u32", "application/octet-stream"},
+ {".uls", "text/iuls"},
+ {".user", "text/plain"},
+ {".ustar", "application/x-ustar"},
+ {".vb", "text/plain"},
+ {".vbdproj", "text/plain"},
+ {".vbk", "video/mpeg"},
+ {".vbproj", "text/plain"},
+ {".vbs", "text/vbscript"},
+ {".vcf", "text/x-vcard"},
+ {".vcproj", "application/xml"},
+ {".vcs", "text/plain"},
+ {".vcxproj", "application/xml"},
+ {".vddproj", "text/plain"},
+ {".vdp", "text/plain"},
+ {".vdproj", "text/plain"},
+ {".vdx", "application/vnd.ms-visio.viewer"},
+ {".vml", "text/xml"},
+ {".vscontent", "application/xml"},
+ {".vsct", "text/xml"},
+ {".vsd", "application/vnd.visio"},
+ {".vsi", "application/ms-vsi"},
+ {".vsix", "application/vsix"},
+ {".vsixlangpack", "text/xml"},
+ {".vsixmanifest", "text/xml"},
+ {".vsmdi", "application/xml"},
+ {".vspscc", "text/plain"},
+ {".vss", "application/vnd.visio"},
+ {".vsscc", "text/plain"},
+ {".vssettings", "text/xml"},
+ {".vssscc", "text/plain"},
+ {".vst", "application/vnd.visio"},
+ {".vstemplate", "text/xml"},
+ {".vsto", "application/x-ms-vsto"},
+ {".vsw", "application/vnd.visio"},
+ {".vsx", "application/vnd.visio"},
+ {".vtt", "text/vtt"},
+ {".vtx", "application/vnd.visio"},
+ {".wasm", "application/wasm"},
+ {".wav", "audio/wav"},
+ {".wave", "audio/wav"},
+ {".wax", "audio/x-ms-wax"},
+ {".wbk", "application/msword"},
+ {".wbmp", "image/vnd.wap.wbmp"},
+ {".wcm", "application/vnd.ms-works"},
+ {".wdb", "application/vnd.ms-works"},
+ {".wdp", "image/vnd.ms-photo"},
+ {".webarchive", "application/x-safari-webarchive"},
+ {".webm", "video/webm"},
+ {".webp", "image/webp"},
+ {".webtest", "application/xml"},
+ {".wiq", "application/xml"},
+ {".wiz", "application/msword"},
+ {".wks", "application/vnd.ms-works"},
+ {".WLMP", "application/wlmoviemaker"},
+ {".wlpginstall", "application/x-wlpg-detect"},
+ {".wlpginstall3", "application/x-wlpg3-detect"},
+ {".wm", "video/x-ms-wm"},
+ {".wma", "audio/x-ms-wma"},
+ {".wmd", "application/x-ms-wmd"},
+ {".wmf", "application/x-msmetafile"},
+ {".wml", "text/vnd.wap.wml"},
+ {".wmlc", "application/vnd.wap.wmlc"},
+ {".wmls", "text/vnd.wap.wmlscript"},
+ {".wmlsc", "application/vnd.wap.wmlscriptc"},
+ {".wmp", "video/x-ms-wmp"},
+ {".wmv", "video/x-ms-wmv"},
+ {".wmx", "video/x-ms-wmx"},
+ {".wmz", "application/x-ms-wmz"},
+ {".woff", "application/font-woff"},
+ {".woff2", "application/font-woff2"},
+ {".wpl", "application/vnd.ms-wpl"},
+ {".wps", "application/vnd.ms-works"},
+ {".wri", "application/x-mswrite"},
+ {".wrl", "x-world/x-vrml"},
+ {".wrz", "x-world/x-vrml"},
+ {".wsc", "text/scriptlet"},
+ {".wsdl", "text/xml"},
+ {".wvx", "video/x-ms-wvx"},
+ {".x", "application/directx"},
+ {".xaf", "x-world/x-vrml"},
+ {".xaml", "application/xaml+xml"},
+ {".xap", "application/x-silverlight-app"},
+ {".xbap", "application/x-ms-xbap"},
+ {".xbm", "image/x-xbitmap"},
+ {".xdr", "text/plain"},
+ {".xht", "application/xhtml+xml"},
+ {".xhtml", "application/xhtml+xml"},
+ {".xla", "application/vnd.ms-excel"},
+ {".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"},
+ {".xlc", "application/vnd.ms-excel"},
+ {".xld", "application/vnd.ms-excel"},
+ {".xlk", "application/vnd.ms-excel"},
+ {".xll", "application/vnd.ms-excel"},
+ {".xlm", "application/vnd.ms-excel"},
+ {".xls", "application/vnd.ms-excel"},
+ {".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"},
+ {".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"},
+ {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
+ {".xlt", "application/vnd.ms-excel"},
+ {".xltm", "application/vnd.ms-excel.template.macroEnabled.12"},
+ {".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"},
+ {".xlw", "application/vnd.ms-excel"},
+ {".xmp", "application/octet-stream"},
+ {".xmta", "application/xml"},
+ {".xof", "x-world/x-vrml"},
+ {".XOML", "text/plain"},
+ {".xpm", "image/x-xpixmap"},
+ {".xps", "application/vnd.ms-xpsdocument"},
+ {".xrm-ms", "text/xml"},
+ {".xsc", "application/xml"},
+ {".xsd", "text/xml"},
+ {".xsf", "text/xml"},
+ {".xsl", "text/xml"},
+ {".xslt", "text/xml"},
+ {".xsn", "application/octet-stream"},
+ {".xss", "application/xml"},
+ {".xspf", "application/xspf+xml"},
+ {".xtp", "application/octet-stream"},
+ {".xwd", "image/x-xwindowdump"},
+ {".z", "application/x-compress"},
+ {".zip", "application/zip"}
+ };
+ return mappings;
+ }
+
+ public static string GetMimeType(string extension)
+ {
+ if (extension == null)
+ {
+ return null;
+ }
+
+ if (!extension.StartsWith("."))
+ {
+ extension = "." + extension;
+ }
+
+ string mime;
+
+ return _mappings.TryGetValue(extension, out mime) ? mime : "application/octet-stream";
+ }
+ }
+}
diff --git a/Mhf.Server/Web/Route/FileWebRoute.cs b/Mhf.Server/Web/Route/FileWebRoute.cs
new file mode 100644
index 0000000..7a4d008
--- /dev/null
+++ b/Mhf.Server/Web/Route/FileWebRoute.cs
@@ -0,0 +1,14 @@
+using Microsoft.Extensions.FileProviders;
+
+namespace Mhf.Server.Web.Route
+{
+ public abstract class FileWebRoute : WebRoute
+ {
+ protected IFileProvider FileProvider { get; }
+
+ public FileWebRoute(IFileProvider fileProvider)
+ {
+ FileProvider = fileProvider;
+ }
+ }
+}
diff --git a/Mhf.Server/Web/Route/IWebRoute.cs b/Mhf.Server/Web/Route/IWebRoute.cs
new file mode 100644
index 0000000..f3d0d08
--- /dev/null
+++ b/Mhf.Server/Web/Route/IWebRoute.cs
@@ -0,0 +1,16 @@
+using System.Threading.Tasks;
+
+namespace Mhf.Server.Web.Route
+{
+ ///
+ /// Defines a handler for a route
+ ///
+ public interface IWebRoute
+ {
+ string Route { get; }
+ Task Get(WebRequest request);
+ Task Post(WebRequest request);
+ Task Put(WebRequest request);
+ Task Delete(WebRequest request);
+ }
+}
diff --git a/Mhf.Server/Web/Route/IWebRouter.cs b/Mhf.Server/Web/Route/IWebRouter.cs
new file mode 100644
index 0000000..2d4ebad
--- /dev/null
+++ b/Mhf.Server/Web/Route/IWebRouter.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+
+namespace Mhf.Server.Web.Route
+{
+ public interface IWebRouter
+ {
+ void AddRoute(IWebRoute route);
+ Task Route(WebRequest request);
+ }
+}
diff --git a/Mhf.Server/Web/Route/ServerWebRoute.cs b/Mhf.Server/Web/Route/ServerWebRoute.cs
new file mode 100644
index 0000000..bef66f8
--- /dev/null
+++ b/Mhf.Server/Web/Route/ServerWebRoute.cs
@@ -0,0 +1,12 @@
+namespace Mhf.Server.Web.Route
+{
+ public abstract class ServerWebRoute : WebRoute
+ {
+ protected MhfServer Server { get; }
+
+ public ServerWebRoute(MhfServer server)
+ {
+ Server = server;
+ }
+ }
+}
diff --git a/Mhf.Server/Web/Route/WebRoute.cs b/Mhf.Server/Web/Route/WebRoute.cs
new file mode 100644
index 0000000..c41880a
--- /dev/null
+++ b/Mhf.Server/Web/Route/WebRoute.cs
@@ -0,0 +1,35 @@
+using System.Threading.Tasks;
+using Arrowgene.Services.Logging;
+
+namespace Mhf.Server.Web.Route
+{
+ ///
+ /// Implementation of Kestrel server as backend
+ ///
+ public abstract class WebRoute : IWebRoute
+ {
+ protected ILogger Logger => LogProvider.Instance.GetLogger(this);
+
+ public abstract string Route { get; }
+
+ public virtual Task Get(WebRequest request)
+ {
+ return WebResponse.NotFound();
+ }
+
+ public virtual Task Post(WebRequest request)
+ {
+ return WebResponse.NotFound();
+ }
+
+ public virtual Task Put(WebRequest request)
+ {
+ return WebResponse.NotFound();
+ }
+
+ public virtual Task Delete(WebRequest request)
+ {
+ return WebResponse.NotFound();
+ }
+ }
+}
diff --git a/Mhf.Server/Web/Route/WebRouter.cs b/Mhf.Server/Web/Route/WebRouter.cs
new file mode 100644
index 0000000..80d7eed
--- /dev/null
+++ b/Mhf.Server/Web/Route/WebRouter.cs
@@ -0,0 +1,84 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Arrowgene.Services.Logging;
+using Mhf.Server.Setting;
+
+namespace Mhf.Server.Web.Route
+{
+ ///
+ /// Parses routes and calls the route
+ ///
+ public class WebRouter : IWebRouter
+ {
+ private Dictionary _routes;
+ private ILogger _logger;
+ private MhfSetting _setting;
+
+ public WebRouter(MhfSetting setting)
+ {
+ _setting = setting;
+ _logger = LogProvider.Instance.GetLogger(this);
+ _routes = new Dictionary();
+ }
+
+
+ ///
+ /// Adds a handler for a specific route.
+ ///
+ public void AddRoute(IWebRoute route)
+ {
+ _routes.Add(route.Route, route);
+ }
+
+ ///
+ /// Passes incoming requests to the correct route
+ ///
+ public async Task Route(WebRequest request)
+ {
+ _logger.Info($"Request: {request}");
+ if (request.Path == null)
+ {
+ _logger.Error($"Request path not set, please check sever request mapping implementation");
+ return await WebResponse.InternalServerError();
+ }
+
+ if (_routes.ContainsKey(request.Path))
+ {
+ IWebRoute route = _routes[request.Path];
+ Task responseTask = null;
+ switch (request.Method)
+ {
+ case WebRequestMethod.Get:
+ responseTask = route.Get(request);
+ break;
+ case WebRequestMethod.Post:
+ responseTask = route.Post(request);
+ break;
+ case WebRequestMethod.Put:
+ responseTask = route.Put(request);
+ break;
+ case WebRequestMethod.Delete:
+ responseTask = route.Delete(request);
+ break;
+ }
+
+ if (responseTask == null)
+ {
+ _logger.Info($"Request method: {request.Method} not supported for requested path: {request.Path}");
+ return await WebResponse.InternalServerError();
+ }
+
+ WebResponse response = await responseTask;
+ response.RouteFound = true;
+ if (!string.IsNullOrEmpty(_setting.WebSetting.ServerHeader))
+ {
+ response.Header.Add("Server", _setting.WebSetting.ServerHeader);
+ }
+
+ return response;
+ }
+
+ return await WebResponse.NotFound();
+ }
+ }
+}
diff --git a/Mhf.Server/Web/Server/IWebServer.cs b/Mhf.Server/Web/Server/IWebServer.cs
new file mode 100644
index 0000000..f1d9888
--- /dev/null
+++ b/Mhf.Server/Web/Server/IWebServer.cs
@@ -0,0 +1,15 @@
+using System.Threading.Tasks;
+using Mhf.Server.Web.Route;
+
+namespace Mhf.Server.Web.Server
+{
+ ///
+ /// Defines web server
+ ///
+ public interface IWebServer
+ {
+ void SetHandler(IWebServerHandler handler);
+ Task Start();
+ Task Stop();
+ }
+}
diff --git a/Mhf.Server/Web/Server/IWebServerHandler.cs b/Mhf.Server/Web/Server/IWebServerHandler.cs
new file mode 100644
index 0000000..8c272c4
--- /dev/null
+++ b/Mhf.Server/Web/Server/IWebServerHandler.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Mhf.Server.Web.Server
+{
+ public interface IWebServerHandler
+ {
+ Task Handle(WebRequest request);
+ }
+}
diff --git a/Mhf.Server/Web/Server/Kestrel/KestrelLogger.cs b/Mhf.Server/Web/Server/Kestrel/KestrelLogger.cs
new file mode 100644
index 0000000..18265a6
--- /dev/null
+++ b/Mhf.Server/Web/Server/Kestrel/KestrelLogger.cs
@@ -0,0 +1,61 @@
+using System;
+using Arrowgene.Services.Logging;
+using Microsoft.Extensions.Logging;
+using ILogger = Microsoft.Extensions.Logging.ILogger;
+using LogLevel = Microsoft.Extensions.Logging.LogLevel;
+
+namespace Mhf.Server.Web.Server.Kestrel
+{
+ public class KestrelLogger : ILogger
+ {
+ private readonly string _name;
+ private Arrowgene.Services.Logging.ILogger _logger;
+
+ public KestrelLogger(string name)
+ {
+ _name = name;
+ _logger = LogProvider.Logger(name);
+ }
+
+ public IDisposable BeginScope(TState state)
+ {
+ return null;
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return true;
+ }
+
+ public void Log(LogLevel logLevel,
+ EventId eventId,
+ TState state,
+ Exception exception,
+ Func formatter)
+ {
+ if (exception != null)
+ {
+ _logger.Exception(exception);
+ return;
+ }
+
+ string message = $"{formatter(state, exception)}";
+ switch (logLevel)
+ {
+ case LogLevel.Trace:
+ case LogLevel.Debug:
+ case LogLevel.None:
+ case LogLevel.Information:
+ _logger.Debug(message);
+ break;
+ case LogLevel.Warning:
+ _logger.Info(message);
+ break;
+ case LogLevel.Error:
+ case LogLevel.Critical:
+ _logger.Error(message);
+ break;
+ }
+ }
+ }
+}
diff --git a/Mhf.Server/Web/Server/Kestrel/KestrelLoggerProvider.cs b/Mhf.Server/Web/Server/Kestrel/KestrelLoggerProvider.cs
new file mode 100644
index 0000000..929bc0d
--- /dev/null
+++ b/Mhf.Server/Web/Server/Kestrel/KestrelLoggerProvider.cs
@@ -0,0 +1,25 @@
+using System.Collections.Concurrent;
+using Microsoft.Extensions.Logging;
+
+namespace Mhf.Server.Web.Server.Kestrel
+{
+ public class KestrelLoggerProvider: ILoggerProvider
+ {
+ private readonly ConcurrentDictionary _loggers = new ConcurrentDictionary();
+
+ public KestrelLoggerProvider()
+ {
+
+ }
+
+ public ILogger CreateLogger(string categoryName)
+ {
+ return _loggers.GetOrAdd(categoryName, name => new KestrelLogger(name));
+ }
+
+ public void Dispose()
+ {
+ _loggers.Clear();
+ }
+ }
+}
diff --git a/Mhf.Server/Web/Server/Kestrel/KestrelWebServer.cs b/Mhf.Server/Web/Server/Kestrel/KestrelWebServer.cs
new file mode 100644
index 0000000..d87a32d
--- /dev/null
+++ b/Mhf.Server/Web/Server/Kestrel/KestrelWebServer.cs
@@ -0,0 +1,198 @@
+using System;
+using System.Diagnostics;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+using Arrowgene.Services.Logging;
+using Mhf.Server.Setting;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
+using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using ILogger = Arrowgene.Services.Logging.ILogger;
+
+namespace Mhf.Server.Web.Server.Kestrel
+{
+ ///
+ /// Implementation of Kestrel server as backend
+ ///
+ public class KestrelWebServer : IWebServer
+ {
+ private CancellationTokenSource _cancellationTokenSource;
+ private ApplicationLifetime _applicationLifetime;
+ private IServer _server;
+ private int _shutdownTimeout = 10000;
+ private IWebServerHandler _handler;
+ private MhfSetting _setting;
+ private ILogger _logger;
+
+ public KestrelWebServer(MhfSetting setting)
+ {
+ _logger = LogProvider.Logger(this);
+ _setting = setting;
+ _cancellationTokenSource = new CancellationTokenSource();
+ }
+
+ public void SetHandler(IWebServerHandler handler)
+ {
+ _handler = handler;
+ }
+
+ public async Task Start()
+ {
+ IHttpApplication app;
+ try
+ {
+ if (_handler == null)
+ {
+ throw new Exception("Missing Handler - Call SetHandler()");
+ }
+
+ ILoggerFactory loggerFactory = new LoggerFactory();
+ loggerFactory.AddProvider(new KestrelLoggerProvider());
+ ServiceCollection services = new ServiceCollection();
+ services.AddSingleton(loggerFactory);
+ services.AddLogging();
+
+ IServiceProvider serviceProvider = GetProviderFromFactory(services);
+ IOptions kestrelServerOptions = Options.Create(new KestrelServerOptions());
+ kestrelServerOptions.Value.ApplicationServices = serviceProvider;
+ kestrelServerOptions.Value.ListenAnyIP(_setting.WebSetting.HttpPort);
+ if (_setting.WebSetting.HttpsEnabled)
+ {
+ kestrelServerOptions.Value.ListenAnyIP(_setting.WebSetting.HttpsPort,
+ listenOptions =>
+ {
+ X509Certificate2 cert = new X509Certificate2(_setting.WebSetting.HttpsCertPath);
+ listenOptions.UseHttps(cert);
+ });
+ }
+
+ kestrelServerOptions.Value.AddServerHeader = false;
+
+ IOptions socketTransportOptions = Options.Create(new SocketTransportOptions());
+ _applicationLifetime = new ApplicationLifetime(
+ loggerFactory.CreateLogger()
+ );
+ ITransportFactory transportFactory = new SocketTransportFactory(
+ socketTransportOptions, _applicationLifetime, loggerFactory
+ );
+
+
+ _server = new KestrelServer(kestrelServerOptions, transportFactory, loggerFactory);
+ DiagnosticListener diagnosticListener = new DiagnosticListener("a");
+ IOptions formOptions = Options.Create(new FormOptions());
+ IHttpContextFactory httpContextFactory = new HttpContextFactory(formOptions);
+ app = new HostingApplication(
+ RequestDelegate,
+ loggerFactory.CreateLogger(),
+ diagnosticListener,
+ httpContextFactory
+ );
+ }
+ catch (Exception ex)
+ {
+ _logger.Exception(ex);
+ return;
+ }
+
+ Task kestrelStartup = _server.StartAsync(app, _cancellationTokenSource.Token);
+ await kestrelStartup;
+ _cancellationTokenSource.Token.Register(
+ state => ((IApplicationLifetime) state).StopApplication(),
+ _applicationLifetime
+ );
+ TaskCompletionSource completionSource = new TaskCompletionSource(
+ TaskCreationOptions.RunContinuationsAsynchronously
+ );
+ _applicationLifetime.ApplicationStopping.Register(
+ obj => ((TaskCompletionSource) obj).TrySetResult(null),
+ completionSource
+ );
+ Task kestrelCompleted = completionSource.Task;
+ object kestrelCompletedResult = await kestrelCompleted;
+ Task kestrelShutdown = _server.StopAsync(new CancellationToken());
+ await kestrelShutdown;
+ }
+
+ public async Task Stop()
+ {
+ CancellationToken token = new CancellationTokenSource(_shutdownTimeout).Token;
+ _applicationLifetime?.StopApplication();
+ if (_server != null)
+ {
+ await _server.StopAsync(token).ConfigureAwait(false);
+ }
+
+ _applicationLifetime?.NotifyStopped();
+ HostingEventSource.Log.HostStop();
+ }
+
+ ///
+ /// Called whenever a web request arrives.
+ /// - Maps Kestrel HttpRequest/HttpResponse to WebRequest/WebResponse
+ /// - Calls router to handle the request
+ ///
+ private async Task RequestDelegate(HttpContext context)
+ {
+ WebRequest request = new WebRequest();
+ request.Host = context.Request.Host.Host;
+ request.Port = context.Request.Host.Port;
+ request.Method = WebRequest.ParseMethod(context.Request.Method);
+ request.Path = context.Request.Path;
+ request.Scheme = context.Request.Scheme;
+ request.ContentType = context.Request.ContentType;
+ request.QueryString = context.Request.QueryString.Value;
+ request.ContentLength = context.Request.ContentLength;
+ foreach (string key in context.Request.Headers.Keys)
+ {
+ request.Header.Add(key, context.Request.Headers[key]);
+ }
+
+ foreach (string key in context.Request.Query.Keys)
+ {
+ request.QueryParameter.Add(key, context.Request.Query[key]);
+ }
+
+ foreach (string key in context.Request.Cookies.Keys)
+ {
+ request.Cookies.Add(key, context.Request.Cookies[key]);
+ }
+
+ await context.Request.Body.CopyToAsync(request.Body);
+ request.Body.Position = 0;
+ WebResponse response = await _handler.Handle(request);
+ context.Response.StatusCode = response.StatusCode;
+ foreach (string key in response.Header.Keys)
+ {
+ context.Response.Headers.Add(key, response.Header[key]);
+ }
+
+ response.Body.Position = 0;
+ await response.Body.CopyToAsync(context.Response.Body);
+ }
+
+ private IServiceProvider GetProviderFromFactory(IServiceCollection collection)
+ {
+ ServiceProvider provider = collection.BuildServiceProvider();
+ IServiceProviderFactory service =
+ provider.GetService>();
+ if (service == null || service is DefaultServiceProviderFactory)
+ {
+ return provider;
+ }
+
+ using (provider)
+ {
+ return service.CreateServiceProvider(service.CreateBuilder(collection));
+ }
+ }
+ }
+}
diff --git a/Mhf.Server/Web/WebCollection.cs b/Mhf.Server/Web/WebCollection.cs
new file mode 100644
index 0000000..53404fa
--- /dev/null
+++ b/Mhf.Server/Web/WebCollection.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+
+namespace Mhf.Server.Web
+{
+ public class WebCollection
+ {
+ private Dictionary _collection;
+ private Func _keyTransformer;
+
+ public WebCollection() : this(null)
+ {
+ }
+
+ public WebCollection(Func keyTransformer)
+ {
+ _collection = new Dictionary();
+ _keyTransformer = keyTransformer;
+ }
+
+ public ICollection Keys => _collection.Keys;
+
+ public void Add(TKey key, TValue value)
+ {
+ if (_keyTransformer != null)
+ {
+ key = _keyTransformer(key);
+ }
+
+ _collection.Add(key, value);
+ }
+
+ public TValue Get(TKey key)
+ {
+ if (_collection.TryGetValue(key, out TValue value))
+ {
+ return value;
+ }
+
+ return default(TValue);
+ }
+
+ public bool ContainsKey(TKey key)
+ {
+ return _collection.ContainsKey(key);
+ }
+
+ public TValue this[TKey key]
+ {
+ get => _collection[key];
+ set => _collection[key] = value;
+ }
+ }
+}
diff --git a/Mhf.Server/Web/WebRequest.cs b/Mhf.Server/Web/WebRequest.cs
new file mode 100644
index 0000000..c6e0f06
--- /dev/null
+++ b/Mhf.Server/Web/WebRequest.cs
@@ -0,0 +1,102 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Serialization;
+using Mhf.Server.Common;
+
+namespace Mhf.Server.Web
+{
+ public class WebRequest
+ {
+ public static WebRequestMethod ParseMethod(string method)
+ {
+ method = method.ToLowerInvariant();
+ switch (method)
+ {
+ case "get": return WebRequestMethod.Get;
+ case "put": return WebRequestMethod.Put;
+ case "post": return WebRequestMethod.Post;
+ case "delete": return WebRequestMethod.Delete;
+ }
+
+ return WebRequestMethod.Unknown;
+ }
+
+ public WebRequestMethod Method { get; set; }
+ public string Host { get; set; }
+ public int? Port { get; set; }
+ public string Path { get; set; }
+ public string Scheme { get; set; }
+ public string ContentType { get; set; }
+ public string QueryString { get; set; }
+ public long? ContentLength { get; set; }
+ [XmlIgnore] public Stream Body { get; set; }
+ public WebCollection Header { get; }
+ public WebCollection QueryParameter { get; }
+ public WebCollection Cookies { get; }
+
+ public WebRequest()
+ {
+ Path = null;
+ Body = new MemoryStream();
+ Header = new WebCollection(key => key.ToLowerInvariant());
+ Cookies = new WebCollection(key => key.ToLowerInvariant());
+ QueryParameter = new WebCollection(key => key.ToLowerInvariant());
+ }
+
+ ///
+ /// Clears the body.
+ ///
+ public void ClearBody()
+ {
+ Body.Position = 0;
+ Body.SetLength(0);
+ }
+
+ ///
+ /// Writes give bytes from current position till length of bytes.
+ ///
+ public Task WriteAsync(byte[] bytes)
+ {
+ return WriteAsync(bytes, Body.Position);
+ }
+
+ ///
+ /// Writes give bytes from given position till length of bytes.
+ ///
+ public Task WriteAsync(byte[] bytes, long position)
+ {
+ Body.Position = position;
+ return Body.WriteAsync(bytes, 0, bytes.Length);
+ }
+
+ public Task ReadBytesAsync()
+ {
+ Body.Position = 0;
+ return Util.ReadAsync(Body);
+ }
+
+ public Task ReadStringAsync()
+ {
+ return ReadStringAsync(Encoding.UTF8);
+ }
+
+ public async Task ReadStringAsync(Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+
+ Body.Position = 0;
+ byte[] body = await Util.ReadAsync(Body);
+ return encoding.GetString(body);
+ }
+
+ public override string ToString()
+ {
+ return $"{Method} {Scheme}://{Host}:{Port?.ToString()}{Path}{QueryString}";
+ }
+ }
+}
diff --git a/Mhf.Server/Web/WebRequestMethod.cs b/Mhf.Server/Web/WebRequestMethod.cs
new file mode 100644
index 0000000..6c29a35
--- /dev/null
+++ b/Mhf.Server/Web/WebRequestMethod.cs
@@ -0,0 +1,11 @@
+namespace Mhf.Server.Web
+{
+ public enum WebRequestMethod
+ {
+ Unknown = -1,
+ Get = 0,
+ Post = 1,
+ Put = 2,
+ Delete = 3
+ }
+}
diff --git a/Mhf.Server/Web/WebResponse.cs b/Mhf.Server/Web/WebResponse.cs
new file mode 100644
index 0000000..a2213e4
--- /dev/null
+++ b/Mhf.Server/Web/WebResponse.cs
@@ -0,0 +1,72 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.FileProviders;
+
+namespace Mhf.Server.Web
+{
+ public class WebResponse
+ {
+ public static async Task NotFound()
+ {
+ WebResponse notFound = new WebResponse();
+ notFound.StatusCode = 404;
+ await notFound.WriteAsync("404 - route not found");
+ return notFound;
+ }
+
+ public static async Task InternalServerError()
+ {
+ WebResponse internalServerError = new WebResponse();
+ internalServerError.StatusCode = 500;
+ await internalServerError.WriteAsync("500 - an internal error occured");
+ return internalServerError;
+ }
+
+ public Stream Body { get; set; }
+ public int StatusCode { get; set; }
+ public bool RouteFound { get; set; }
+
+ public WebCollection Header { get; }
+
+ public WebResponse()
+ {
+ Body = new MemoryStream();
+ Header = new WebCollection();
+ RouteFound = false;
+ }
+
+ public Task WriteAsync(IFileInfo fileInfo, bool contentLength = true)
+ {
+ if (contentLength)
+ {
+ Header.Add("content-length", $"{fileInfo.Length}");
+ }
+
+ return fileInfo.CreateReadStream().CopyToAsync(Body);
+ }
+
+ public Task WriteAsync(string text, bool contentLength = true)
+ {
+ if (text == null)
+ throw new ArgumentNullException(nameof(text));
+ return WriteAsync(text, Encoding.UTF8, contentLength);
+ }
+
+ public Task WriteAsync(string text, Encoding encoding, bool contentLength = true)
+ {
+ if (text == null)
+ throw new ArgumentNullException(nameof(text));
+ if (encoding == null)
+ throw new ArgumentNullException(nameof(encoding));
+ byte[] bytes = encoding.GetBytes(text);
+ if (contentLength)
+ {
+ Header.Add("content-length", $"{bytes.Length}");
+ }
+
+ return Body.WriteAsync(bytes, 0, bytes.Length);
+ }
+ }
+}
diff --git a/Mhf.Server/Web/WebServer.cs b/Mhf.Server/Web/WebServer.cs
new file mode 100644
index 0000000..f43badd
--- /dev/null
+++ b/Mhf.Server/Web/WebServer.cs
@@ -0,0 +1,65 @@
+using System.Threading.Tasks;
+using Arrowgene.Services.Logging;
+using Mhf.Server.Setting;
+using Mhf.Server.Web.Middleware;
+using Mhf.Server.Web.Route;
+using Mhf.Server.Web.Server;
+
+namespace Mhf.Server.Web
+{
+ ///
+ /// Manages web requests
+ ///
+ public class WebServer : IWebServerHandler
+ {
+ private IWebServer _server;
+ private WebRouter _router;
+ private WebMiddlewareStack _middlewareStack;
+ private ILogger _logger;
+
+ public WebServer(MhfSetting setting, IWebServer server)
+ {
+ _logger = LogProvider.Logger(this);
+ _server = server;
+ _router = new WebRouter(setting);
+ _server.SetHandler(this);
+ _middlewareStack = new WebMiddlewareStack(_router.Route);
+ }
+
+ public async Task Start()
+ {
+ await _server.Start();
+ }
+
+ public async Task Stop()
+ {
+ await _server.Stop();
+ }
+
+ public async Task Handle(WebRequest request)
+ {
+ WebResponse response = await _middlewareStack.Start(request);
+ if (!response.RouteFound)
+ {
+ _logger.Info($"No route or middleware registered for requested Path: {request.Path}");
+ }
+ return response;
+ }
+
+ public void AddRoute(IWebRoute route)
+ {
+ _router.AddRoute(route);
+ }
+
+ public void AddMiddleware(IWebMiddleware middleware)
+ {
+ _middlewareStack.Use(next => req => middleware.Handle(req, next));
+ // middleware.Use(
+ // next => req =>
+ // {
+ // return next(req);
+ // }
+ // );
+ }
+ }
+}
diff --git a/Mhf.Server/WebMiddlewares/StaticFileMiddleware.cs b/Mhf.Server/WebMiddlewares/StaticFileMiddleware.cs
new file mode 100644
index 0000000..b966468
--- /dev/null
+++ b/Mhf.Server/WebMiddlewares/StaticFileMiddleware.cs
@@ -0,0 +1,40 @@
+using System.IO;
+using System.Threading.Tasks;
+using Mhf.Server.Web;
+using Mhf.Server.Web.Middleware;
+using Microsoft.Extensions.FileProviders;
+
+namespace Mhf.Server.WebMiddlewares
+{
+ public class StaticFileMiddleware : IWebMiddleware
+ {
+ private string _root;
+ private IFileProvider _provider;
+
+ public StaticFileMiddleware(string root, IFileProvider provider)
+ {
+ _root = root;
+ _provider = provider;
+ }
+
+ public async Task Handle(WebRequest request, WebMiddlewareDelegate next)
+ {
+ WebResponse response = await next(request);
+ if (!response.RouteFound && !string.IsNullOrEmpty(request.Path))
+ {
+ IFileInfo file = _provider.GetFileInfo(request.Path);
+ if (file.Exists)
+ {
+ response.RouteFound = true;
+ response = new WebResponse();
+ response.StatusCode = 200;
+ string mimeType = MimeTypeMap.GetMimeType(Path.GetExtension(file.Name));
+ response.Header.Add("content-type", mimeType);
+ await response.WriteAsync(file);
+ }
+ }
+
+ return response;
+ }
+ }
+}
diff --git a/Mhf.Server/WebRoutes/AuthLauncherLoginRoute.cs b/Mhf.Server/WebRoutes/AuthLauncherLoginRoute.cs
new file mode 100644
index 0000000..edba6ff
--- /dev/null
+++ b/Mhf.Server/WebRoutes/AuthLauncherLoginRoute.cs
@@ -0,0 +1,26 @@
+using System.Threading.Tasks;
+using Mhf.Server.Web;
+using Mhf.Server.Web.Route;
+using Microsoft.Extensions.FileProviders;
+
+namespace Mhf.Server.WebRoutes
+{
+ public class AuthLauncherLoginRoute : FileWebRoute
+ {
+ public AuthLauncherLoginRoute(IFileProvider fileProvider) : base(fileProvider)
+ {
+ }
+
+ public override string Route => "/auth/launcher/login";
+
+ public override async Task Post(WebRequest request)
+ {
+ WebResponse response = new WebResponse();
+ response.StatusCode = 200;
+ response.Header.Add("content-type", "text/html; charset=UTF-8");
+ IFileInfo startHtml = FileProvider.GetFileInfo("auth/launcher/login.html");
+ await response.WriteAsync(startHtml);
+ return response;
+ }
+ }
+}
diff --git a/Mhf.Server/WebRoutes/AuthLauncherStartRoute.cs b/Mhf.Server/WebRoutes/AuthLauncherStartRoute.cs
new file mode 100644
index 0000000..0225c4f
--- /dev/null
+++ b/Mhf.Server/WebRoutes/AuthLauncherStartRoute.cs
@@ -0,0 +1,26 @@
+using System.Threading.Tasks;
+using Mhf.Server.Web;
+using Mhf.Server.Web.Route;
+using Microsoft.Extensions.FileProviders;
+
+namespace Mhf.Server.WebRoutes
+{
+ public class AuthLauncherStartRoute : FileWebRoute
+ {
+ public AuthLauncherStartRoute(IFileProvider fileProvider) : base(fileProvider)
+ {
+ }
+
+ public override string Route => "/auth/launcher/start.html";
+
+ public override async Task Get(WebRequest request)
+ {
+ WebResponse response = new WebResponse();
+ response.StatusCode = 200;
+ response.Header.Add("content-type", "text/html; charset=UTF-8");
+ IFileInfo startHtml = FileProvider.GetFileInfo("auth/launcher/start.html");
+ await response.WriteAsync(startHtml);
+ return response;
+ }
+ }
+}
diff --git a/Mhf.Server/WebRoutes/IndexRoute.cs b/Mhf.Server/WebRoutes/IndexRoute.cs
new file mode 100644
index 0000000..355f54d
--- /dev/null
+++ b/Mhf.Server/WebRoutes/IndexRoute.cs
@@ -0,0 +1,19 @@
+using System.Threading.Tasks;
+using Mhf.Server.Web;
+using Mhf.Server.Web.Route;
+
+namespace Mhf.Server.WebRoutes
+{
+ public class IndexRoute : WebRoute
+ {
+ public override string Route => "/";
+
+ public override async Task Get(WebRequest request)
+ {
+ WebResponse response = new WebResponse();
+ response.StatusCode = 200;
+ await response.WriteAsync("Welcome - Index Page!");
+ return response;
+ }
+ }
+}
diff --git a/Mhf.Server/WebRoutes/LauncherIndexRoute.cs b/Mhf.Server/WebRoutes/LauncherIndexRoute.cs
new file mode 100644
index 0000000..f40ee5c
--- /dev/null
+++ b/Mhf.Server/WebRoutes/LauncherIndexRoute.cs
@@ -0,0 +1,26 @@
+using System.Threading.Tasks;
+using Mhf.Server.Web;
+using Mhf.Server.Web.Route;
+using Microsoft.Extensions.FileProviders;
+
+namespace Mhf.Server.WebRoutes
+{
+ public class LauncherIndexRoute : FileWebRoute
+ {
+ public LauncherIndexRoute(IFileProvider fileProvider) : base(fileProvider)
+ {
+ }
+
+ public override string Route => "/launcher/";
+
+ public override async Task Get(WebRequest request)
+ {
+ WebResponse response = new WebResponse();
+ response.StatusCode = 200;
+ response.Header.Add("content-type", "text/html; charset=UTF-8");
+ IFileInfo startHtml = FileProvider.GetFileInfo("launcher/index.html");
+ await response.WriteAsync(startHtml);
+ return response;
+ }
+ }
+}
diff --git a/Mhf.Server/WebRoutes/MhfFileRoute.cs b/Mhf.Server/WebRoutes/MhfFileRoute.cs
new file mode 100644
index 0000000..a9ba7e6
--- /dev/null
+++ b/Mhf.Server/WebRoutes/MhfFileRoute.cs
@@ -0,0 +1,43 @@
+using System.Threading.Tasks;
+using Mhf.Server.Web;
+using Mhf.Server.Web.Route;
+using Microsoft.Extensions.FileProviders;
+
+namespace Mhf.Server.WebRoutes
+{
+ public class MhfFileRoute : FileWebRoute
+ {
+ public MhfFileRoute(IFileProvider fileProvider) : base(fileProvider)
+ {
+ }
+
+ public override string Route => "/mhf_file.php";
+
+ public override async Task Get(WebRequest request)
+ {
+ if (request.QueryParameter.ContainsKey("key"))
+ {
+ IFileInfo mhfFile = FileProvider.GetFileInfo("MHFUP_00.DAT");
+ WebResponse response = new WebResponse();
+ response.StatusCode = 200;
+ response.Header.Add("content-type", "application/octet-stream");
+ response.Header.Add("content-disposition", "inline; filename=\"MHFUP_00.DAT\"");
+ response.Header.Add("connection", "close");
+ await response.WriteAsync(mhfFile);
+ return response;
+ }
+
+ if (request.QueryParameter.ContainsKey("chk"))
+ {
+ WebResponse response = new WebResponse();
+ response.StatusCode = 200;
+ response.Header.Add("content-type", "application/octet-stream");
+ response.Header.Add("connection", "close");
+ await response.WriteAsync("[mhf Check Message:0]");
+ return response;
+ }
+
+ return await WebResponse.NotFound();
+ }
+ }
+}
diff --git a/MonsterHunterFrontierZ.sln b/MonsterHunterFrontierZ.sln
new file mode 100644
index 0000000..2cc876e
--- /dev/null
+++ b/MonsterHunterFrontierZ.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mhf.Server", "Mhf.Server/Mhf.Server.csproj", "{5C0F18BB-B735-4284-82A2-27506DFC9E56}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mhf.Cli", "Mhf.Cli\Mhf.Cli.csproj", "{72B2D640-DEA0-4BA7-A3B5-899954BFBBF5}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5C0F18BB-B735-4284-82A2-27506DFC9E56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5C0F18BB-B735-4284-82A2-27506DFC9E56}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5C0F18BB-B735-4284-82A2-27506DFC9E56}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5C0F18BB-B735-4284-82A2-27506DFC9E56}.Release|Any CPU.Build.0 = Release|Any CPU
+ {72B2D640-DEA0-4BA7-A3B5-899954BFBBF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {72B2D640-DEA0-4BA7-A3B5-899954BFBBF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72B2D640-DEA0-4BA7-A3B5-899954BFBBF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {72B2D640-DEA0-4BA7-A3B5-899954BFBBF5}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e35a4ab
--- /dev/null
+++ b/README.md
@@ -0,0 +1,162 @@
+Monster Hunter Frontier Z - Server
+===
+Server Emulator for the Online Game Monster Hunter Frontier Z.
+Please read through the whole file before asking questions.
+
+## Table of contents
+- [Disclaimer](#disclaimer)
+- [Notice](#notice)
+- [Setup](#setup)
+ - [Visual Studio](#visual-studio)
+ - [VS Code](#vs-code)
+ - [IntelliJ Rider](#intellij-rider)
+- [Hosts](#hosts)
+- [Sever](#server)
+- [Client](#client)
+ - [Unpacking](#unpacking)
+ - [GameGuard](#gameguard)
+ - [Bugs](#bugs)
+- [Guidelines](#guidelines)
+- [Attribution](#attribution)
+ - [Contributers](#contributers)
+ - [3rd Parties and Libraries](#3rd-parties-and-libraries)
+
+# Disclaimer
+The project is intended for educational purpose only.
+
+# Notice
+This server requires that you own a copy of the game and assets.
+These assets are not included in this repository to comply with copyrights.
+In order to run this server you are required to provide a `wwww` folder.
+At the moment no alternative as been developed, as soon as this happened this repository will be updated.
+
+# Setup
+## 1) Clone the repository
+`git clone https://github.com/sebastian-heinz/mhf-server.git`
+
+## 2) Install .Net Core 3.0 SDK or later
+https://dotnet.microsoft.com/download
+
+## 3) Use your IDE of choice:
+
+## 3.1) Visual Studio
+### Notice:
+Minimum version of "Visual Studio 2019 v16.3" or later.
+
+### Open Project:
+Open the `MonsterHunterFrontierZ.sln`-file
+
+## 3.2) VS Code
+Download IDE: https://code.visualstudio.com/download
+C# Plugin: https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp
+
+### Open Project:
+Open the Project Folder:
+`\mhf-server`
+
+## 3.3) IntelliJ Rider
+https://www.jetbrains.com/rider/
+
+### Open Project:
+Open the `MonsterHunterFrontierZ.sln`-file
+
+## 4) Debug the Project
+Run the `Mhf.Cli`-Project
+
+# Hosts
+Add following entries to your hosts file to force the client to connect to the local instance.
+```
+127.0.0.1 cog-members.mhf-z.jp # MHF Launcher (web)
+127.0.0.1 capcom-onlinegames.jp # MHF Authentication (web)
+127.0.0.1 www.capcom-onlinegames.jp # MHF Authentication (web)
+127.0.0.1 sign-mhf.capcom-networks.jp # MHF Authentication (auth/server)
+127.0.0.1 srv-mhf.capcom-networks.jp # MHF ServerList (web)
+127.0.0.1 l0.mhf-g.jp # MHF File Checksum (web)
+127.0.0.1 u0.mhf-g.jp # MHF File Host (web)
+```
+
+# Server
+With default configuration the server will listen on following ports:
+```
+80 - http/launcher gui
+433 - https/authentication
+53312 - tcp/authentication
+53310 - tcp/lobby
+```
+ensure that no other local services run on these.request
+
+# Client
+Following modifications are recommended when trying to use this server:
+
+## Unpacking
+mhf.exe, mhl.dll, mhfo.dll and mhfo-hd.dll are protected with AsProtect.
+A tool called `AsDecom` can unpack these files, but it must run on WindowsXP.
+
+## GameGuard
+To disable GameGuard please delete or rename `gameguard.des`-file.
+Additionally in `mhl.dll` the following byte need to be patched `000053C3:74->77`
+
+## Bugs
+`mhl.dll`-file contains a bug when performing HTTP requests.
+```
+00356352 48 54 54 50 2F 31 2E 31 0A 43 HTTP/1.1.C
+00356368 61 63 68 65 2D 43 6F 6E 74 72 6F 6C 3A 20 6E 6F ache-Control: no
+00356384 2D 63 61 63 68 65 0A -cache.
+```
+It only uses a single LF `0x0A` whereas the spec requires CR LF `0x0D 0x0A`
+In order to obtain enough bytes we can change `cache-control`-header to `Expires`-headers like so:
+```
+Offset(d) 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
+
+00356352 48 54 54 50 2F 31 2E 31 0D 0A HTTP/1.1..
+00356368 45 78 70 69 72 65 73 3A 20 30 0D 0A Expires: 0..
+```
+without this patch the C# web server will not process the request, and maybe other servers as well.
+
+# Guidelines
+## Git
+### Workflow
+The work on this project should happen via `feature-branches`
+
+Feature branches (or sometimes called topic branches) are used to develop new features for the upcoming or a distant future release.
+When starting development of a feature, the target release in which this feature will be incorporated may well be unknown at that point.
+The essence of a feature branch is that it exists as long as the feature is in development,
+but will eventually be merged back into develop (to definitely add the new feature to the upcoming release) or discarded (in case of a disappointing experiment).
+
+1) Create a new `feature/feature-name` or `fix/bug-fix-name` branch from master
+2) Push all your changes to that branch
+3) Create a Pull Request to merge that branch into `master`
+
+## Best Practise
+- Do not use Console.WriteLine etc, use the specially designed logger.
+- Own the Code: extract solutions, discard libraries.
+- Annotate functions with documentation comments (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments).
+
+## C# Coding Standards and Naming Conventions
+| Object Name | Notation | Char Mask | Underscores |
+|:--------------------------|:------------|:-------------------|:------------|
+| Class name | PascalCase | [A-z][0-9] | No |
+| Constructor name | PascalCase | [A-z][0-9] | No |
+| Method name | PascalCase | [A-z][0-9] | No |
+| Method arguments | camelCase | [A-z][0-9] | No |
+| Local variables | camelCase | [A-z][0-9] | No |
+| Constants name | PascalCase | [A-z][0-9] | No |
+| Field name | _camelCase | [A-z][0-9] | Yes |
+| Properties name | PascalCase | [A-z][0-9] | No |
+| Delegate name | PascalCase | [A-z] | No |
+| Enum type name | PascalCase | [A-z] | No |
+
+# Attribution
+## Contributors
+- Nothilvien [@sebastian-heinz](https://github.com/sebastian-heinz)
+
+## 3rd Parties and Libraries
+- System.Data.SQLite (https://system.data.sqlite.org/)
+- MySqlConnector (https://www.nuget.org/packages/MySqlConnector)
+- bcrypt.net (https://github.com/BcryptNet/bcrypt.net)
+- AspNetCore (https://github.com/aspnet/AspNetCore)
+- .NET Standard (https://github.com/dotnet/standard)
+- Arrowgene.Services (https://github.com/Arrowgene/Arrowgene.Services)
+- CRC32 Implementation by Damien Guard (https://github.com/damieng/DamienGKit/blob/master/CSharp/DamienG.Library/Security/Cryptography/Crc32.cs)
+
+
diff --git a/ReleaseFiles/StartServer.cmd b/ReleaseFiles/StartServer.cmd
new file mode 100644
index 0000000..6bc478f
--- /dev/null
+++ b/ReleaseFiles/StartServer.cmd
@@ -0,0 +1,3 @@
+pushd "%~dp0"
+cd ./Server.
+start Mhf.Cli.exe
\ No newline at end of file
diff --git a/mhf.version b/mhf.version
new file mode 100644
index 0000000..945273a
--- /dev/null
+++ b/mhf.version
@@ -0,0 +1 @@
+1.00
\ No newline at end of file