diff --git a/AxibugEmuOnline.Client/Assets/Aya.UNes.asmdef b/AxibugEmuOnline.Client/Assets/Aya.UNes.asmdef
new file mode 100644
index 0000000..dd9bddb
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Aya.UNes.asmdef
@@ -0,0 +1,13 @@
+{
+ "name": "AxibugEmuOnline.Client.UNES",
+ "references": [],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": true,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/AxibugEmuOnline.Client/Assets/Aya.UNes.asmdef.meta b/AxibugEmuOnline.Client/Assets/Aya.UNes.asmdef.meta
new file mode 100644
index 0000000..11f4d03
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Aya.UNes.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 3fe77f1eed9fc0847a86648f644fe815
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/LICENSE.md b/AxibugEmuOnline.Client/Assets/LICENSE.md
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/LICENSE.md
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/AxibugEmuOnline.Client/Assets/LICENSE.md.meta b/AxibugEmuOnline.Client/Assets/LICENSE.md.meta
new file mode 100644
index 0000000..f0c8496
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/LICENSE.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8b0730be05596c544807c9992169cb68
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Plugins.meta b/AxibugEmuOnline.Client/Assets/Plugins.meta
new file mode 100644
index 0000000..926dfd4
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Plugins.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 69eb869b06dab98439f07da4549fb7cb
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Google.Protobuf.dll b/AxibugEmuOnline.Client/Assets/Plugins/Google.Protobuf.dll
new file mode 100644
index 0000000..7663c31
Binary files /dev/null and b/AxibugEmuOnline.Client/Assets/Plugins/Google.Protobuf.dll differ
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Google.Protobuf.dll.meta b/AxibugEmuOnline.Client/Assets/Plugins/Google.Protobuf.dll.meta
new file mode 100644
index 0000000..794d80b
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Plugins/Google.Protobuf.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: 2c61c2e567bd4e146b4c09946e815a55
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/HaoYueNet.ClientNetworkNet.Standard2.dll b/AxibugEmuOnline.Client/Assets/Plugins/HaoYueNet.ClientNetworkNet.Standard2.dll
new file mode 100644
index 0000000..c3c4a90
Binary files /dev/null and b/AxibugEmuOnline.Client/Assets/Plugins/HaoYueNet.ClientNetworkNet.Standard2.dll differ
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/HaoYueNet.ClientNetworkNet.Standard2.dll.meta b/AxibugEmuOnline.Client/Assets/Plugins/HaoYueNet.ClientNetworkNet.Standard2.dll.meta
new file mode 100644
index 0000000..fa5284c
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Plugins/HaoYueNet.ClientNetworkNet.Standard2.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: 324deac494a24a7499801349c7908062
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/HaoYueNet.ServerNetwork.Standard2.dll b/AxibugEmuOnline.Client/Assets/Plugins/HaoYueNet.ServerNetwork.Standard2.dll
new file mode 100644
index 0000000..f525fd2
Binary files /dev/null and b/AxibugEmuOnline.Client/Assets/Plugins/HaoYueNet.ServerNetwork.Standard2.dll differ
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/HaoYueNet.ServerNetwork.Standard2.dll.meta b/AxibugEmuOnline.Client/Assets/Plugins/HaoYueNet.ServerNetwork.Standard2.dll.meta
new file mode 100644
index 0000000..07f0863
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Plugins/HaoYueNet.ServerNetwork.Standard2.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: bba3137572a9a52478aa945ce387a192
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/NoSugarNet.ClientCore.Standard2.dll b/AxibugEmuOnline.Client/Assets/Plugins/NoSugarNet.ClientCore.Standard2.dll
new file mode 100644
index 0000000..58d737f
Binary files /dev/null and b/AxibugEmuOnline.Client/Assets/Plugins/NoSugarNet.ClientCore.Standard2.dll differ
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/NoSugarNet.ClientCore.Standard2.dll.meta b/AxibugEmuOnline.Client/Assets/Plugins/NoSugarNet.ClientCore.Standard2.dll.meta
new file mode 100644
index 0000000..dbe316c
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Plugins/NoSugarNet.ClientCore.Standard2.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: 208c5b5b9ca9139418ec61b89a1516ed
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/NoSugarNet.DataHelper.dll b/AxibugEmuOnline.Client/Assets/Plugins/NoSugarNet.DataHelper.dll
new file mode 100644
index 0000000..46e3832
Binary files /dev/null and b/AxibugEmuOnline.Client/Assets/Plugins/NoSugarNet.DataHelper.dll differ
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/NoSugarNet.DataHelper.dll.meta b/AxibugEmuOnline.Client/Assets/Plugins/NoSugarNet.DataHelper.dll.meta
new file mode 100644
index 0000000..2b3ed26
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Plugins/NoSugarNet.DataHelper.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: 1d83e3c67ee8b794c97b5876ece7a16e
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/System.Buffers.dll b/AxibugEmuOnline.Client/Assets/Plugins/System.Buffers.dll
new file mode 100644
index 0000000..c0970c0
Binary files /dev/null and b/AxibugEmuOnline.Client/Assets/Plugins/System.Buffers.dll differ
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/System.Buffers.dll.meta b/AxibugEmuOnline.Client/Assets/Plugins/System.Buffers.dll.meta
new file mode 100644
index 0000000..efe13f4
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Plugins/System.Buffers.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: a3e5397cebfd55d4d950fc3d7fcafd8f
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/System.Memory.dll b/AxibugEmuOnline.Client/Assets/Plugins/System.Memory.dll
new file mode 100644
index 0000000..1e6aef8
Binary files /dev/null and b/AxibugEmuOnline.Client/Assets/Plugins/System.Memory.dll differ
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/System.Memory.dll.meta b/AxibugEmuOnline.Client/Assets/Plugins/System.Memory.dll.meta
new file mode 100644
index 0000000..b03e202
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Plugins/System.Memory.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: 7079d9113f8bfda4db8a78fffdd414a9
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/System.Numerics.Vectors.dll b/AxibugEmuOnline.Client/Assets/Plugins/System.Numerics.Vectors.dll
new file mode 100644
index 0000000..a808165
Binary files /dev/null and b/AxibugEmuOnline.Client/Assets/Plugins/System.Numerics.Vectors.dll differ
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/System.Numerics.Vectors.dll.meta b/AxibugEmuOnline.Client/Assets/Plugins/System.Numerics.Vectors.dll.meta
new file mode 100644
index 0000000..6e2fab3
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Plugins/System.Numerics.Vectors.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: 677f64bf577a1724699f45841c5bcb04
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/System.Runtime.CompilerServices.Unsafe.dll b/AxibugEmuOnline.Client/Assets/Plugins/System.Runtime.CompilerServices.Unsafe.dll
new file mode 100644
index 0000000..b17135b
Binary files /dev/null and b/AxibugEmuOnline.Client/Assets/Plugins/System.Runtime.CompilerServices.Unsafe.dll differ
diff --git a/AxibugEmuOnline.Client/Assets/Plugins/System.Runtime.CompilerServices.Unsafe.dll.meta b/AxibugEmuOnline.Client/Assets/Plugins/System.Runtime.CompilerServices.Unsafe.dll.meta
new file mode 100644
index 0000000..378070a
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Plugins/System.Runtime.CompilerServices.Unsafe.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: c92fb77cb5b063541bd04cb8a496013f
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Resources.meta b/AxibugEmuOnline.Client/Assets/Resources.meta
new file mode 100644
index 0000000..dfacc8d
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Resources.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 481be8b9dd1218749a75bf1bd4612c04
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Resources/Mario.bytes b/AxibugEmuOnline.Client/Assets/Resources/Mario.bytes
new file mode 100644
index 0000000..878ef21
Binary files /dev/null and b/AxibugEmuOnline.Client/Assets/Resources/Mario.bytes differ
diff --git a/AxibugEmuOnline.Client/Assets/Resources/Mario.bytes.meta b/AxibugEmuOnline.Client/Assets/Resources/Mario.bytes.meta
new file mode 100644
index 0000000..78d1e2d
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Resources/Mario.bytes.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 040fea71e1812ce45bd2b72c8ad2e2ae
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Resources/Texture.meta b/AxibugEmuOnline.Client/Assets/Resources/Texture.meta
new file mode 100644
index 0000000..deb6af3
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Resources/Texture.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6b52033f7628c4346a6dd0b2806e75ee
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Resources/Texture/RenderTexture.renderTexture b/AxibugEmuOnline.Client/Assets/Resources/Texture/RenderTexture.renderTexture
new file mode 100644
index 0000000..99ddfbc
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Resources/Texture/RenderTexture.renderTexture
@@ -0,0 +1,39 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!84 &8400000
+RenderTexture:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name: RenderTexture
+ m_ImageContentsHash:
+ serializedVersion: 2
+ Hash: 00000000000000000000000000000000
+ m_ForcedFallbackFormat: 4
+ m_DownscaleFallback: 0
+ m_IsAlphaChannelOptional: 0
+ serializedVersion: 5
+ m_Width: 256
+ m_Height: 240
+ m_AntiAliasing: 1
+ m_MipCount: -1
+ m_DepthStencilFormat: 92
+ m_ColorFormat: 8
+ m_MipMap: 0
+ m_GenerateMips: 1
+ m_SRGB: 0
+ m_UseDynamicScale: 0
+ m_BindMS: 0
+ m_EnableCompatibleFormat: 1
+ m_TextureSettings:
+ serializedVersion: 2
+ m_FilterMode: 1
+ m_Aniso: 0
+ m_MipBias: 0
+ m_WrapU: 1
+ m_WrapV: 1
+ m_WrapW: 1
+ m_Dimension: 2
+ m_VolumeDepth: 1
+ m_ShadowSamplingMode: 2
diff --git a/AxibugEmuOnline.Client/Assets/Resources/Texture/RenderTexture.renderTexture.meta b/AxibugEmuOnline.Client/Assets/Resources/Texture/RenderTexture.renderTexture.meta
new file mode 100644
index 0000000..d8035c9
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Resources/Texture/RenderTexture.renderTexture.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6645567e4c11d9447b1aee2406f681c5
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime.meta b/AxibugEmuOnline.Client/Assets/Runtime.meta
new file mode 100644
index 0000000..079a930
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ca1eb074eb7f66946b0d974b802d7b8d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Controller.meta b/AxibugEmuOnline.Client/Assets/Runtime/Controller.meta
new file mode 100644
index 0000000..52daa48
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Controller.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 87f4900ceb2e3b24caa8eec8df6b34e7
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Controller/IController.cs b/AxibugEmuOnline.Client/Assets/Runtime/Controller/IController.cs
new file mode 100644
index 0000000..0548145
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Controller/IController.cs
@@ -0,0 +1,15 @@
+using UnityEngine;
+
+namespace AxibugEmuOnline.Client.UNES.Controller
+{
+ public interface IController
+ {
+ void Strobe(bool on);
+
+ int ReadState();
+
+ void PressKey(KeyCode keyCode);
+
+ void ReleaseKey(KeyCode keyCode);
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Controller/IController.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Controller/IController.cs.meta
new file mode 100644
index 0000000..097e178
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Controller/IController.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fde4f0c3566240b4fa824b147b019ff7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Controller/KeyConfig.cs b/AxibugEmuOnline.Client/Assets/Runtime/Controller/KeyConfig.cs
new file mode 100644
index 0000000..811c060
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Controller/KeyConfig.cs
@@ -0,0 +1,22 @@
+using System;
+using UnityEngine;
+
+namespace AxibugEmuOnline.Client.UNES.Controller
+{
+ [Serializable]
+ public class KeyConfig
+ {
+ public KeyCode Up = KeyCode.UpArrow;
+ public KeyCode Down = KeyCode.DownArrow;
+ public KeyCode Left = KeyCode.LeftArrow;
+ public KeyCode Right = KeyCode.RightArrow;
+
+ public KeyCode A = KeyCode.A;
+ public KeyCode B = KeyCode.S;
+
+ public KeyCode Start = KeyCode.Alpha1;
+ public KeyCode Select = KeyCode.Alpha2;
+
+ public KeyCode Debug = KeyCode.P;
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Controller/KeyConfig.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Controller/KeyConfig.cs.meta
new file mode 100644
index 0000000..4e8d851
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Controller/KeyConfig.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3e51d74785e24fa4f972a1eb002a81d7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Controller/NesController.cs b/AxibugEmuOnline.Client/Assets/Runtime/Controller/NesController.cs
new file mode 100644
index 0000000..5b2126e
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Controller/NesController.cs
@@ -0,0 +1,83 @@
+using System.Collections.Generic;
+using UnityEngine;
+using AxibugEmuOnline.Client.UNES.Input;
+
+namespace AxibugEmuOnline.Client.UNES.Controller
+{
+ public class NesController : IController
+ {
+ private readonly UNESBehaviour _nes;
+ private int _data;
+ private int _serialData;
+ private bool _strobing;
+
+ public bool Debug;
+
+ public KeyConfig Config => _nes.KeyConfig;
+
+ // bit: 7 6 5 4 3 2 1 0
+ // button: A B Select Start Up Down Left
+
+ private readonly Dictionary _keyMapping;
+
+ public NesController(UNESBehaviour nse)
+ {
+ _nes = nse;
+
+ _keyMapping = new Dictionary
+ {
+ {Config.A, 7},
+ {Config.B, 6},
+ {Config.Select, 5},
+ {Config.Start, 4},
+ {Config.Up, 3},
+ {Config.Down, 2},
+ {Config.Left, 1},
+ {Config.Right, 0},
+ };
+ }
+
+ public void Strobe(bool on)
+ {
+ _serialData = _data;
+ _strobing = on;
+ }
+
+ public int ReadState()
+ {
+ int ret = ((_serialData & 0x80) > 0).AsByte();
+ if (!_strobing)
+ {
+ _serialData <<= 1;
+ _serialData &= 0xFF;
+ }
+
+ return ret;
+ }
+
+ public void PressKey(KeyCode keyCode)
+ {
+ if (keyCode == Config.Debug)
+ {
+ Debug ^= true;
+ }
+
+ if (!_keyMapping.ContainsKey(keyCode))
+ {
+ return;
+ }
+
+ _data |= 1 << _keyMapping[keyCode];
+ }
+
+ public void ReleaseKey(KeyCode keyCode)
+ {
+ if (!_keyMapping.ContainsKey(keyCode))
+ {
+ return;
+ }
+
+ _data &= ~(1 << _keyMapping[keyCode]);
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Controller/NesController.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Controller/NesController.cs.meta
new file mode 100644
index 0000000..0582652
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Controller/NesController.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6a6001b2b26e33144b77fe0b02e21dc0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core.meta
new file mode 100644
index 0000000..62ce764
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2f857291907b21142bc251146119762c
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/Addressable.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/Addressable.cs
new file mode 100644
index 0000000..770cb10
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/Addressable.cs
@@ -0,0 +1,62 @@
+using System.Runtime.CompilerServices;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ public abstract class Addressable
+ {
+ public delegate uint ReadDelegate(uint address);
+
+ public delegate void WriteDelegate(uint address, byte val);
+
+ protected readonly Emulator _emulator;
+ protected readonly ReadDelegate[] _readMap;
+ protected readonly WriteDelegate[] _writeMap;
+ protected readonly uint _addressSize;
+
+ protected Addressable(Emulator emulator, uint addressSpace)
+ {
+ _emulator = emulator;
+ _addressSize = addressSpace;
+ _readMap = new ReadDelegate[addressSpace + 1];
+ _writeMap = new WriteDelegate[addressSpace + 1];
+ }
+
+ protected virtual void InitializeMemoryMap()
+ {
+ _readMap.Fill(address => 0);
+
+ // Some games write to addresses not mapped and expect to continue afterwards
+ _writeMap.Fill((address, val) => { });
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public uint ReadByte(uint address)
+ {
+ address &= _addressSize;
+ return _readMap[address](address);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void WriteByte(uint address, uint val)
+ {
+ address &= _addressSize;
+ _writeMap[address](address, (byte)val);
+ }
+
+ public void MapReadHandler(uint start, uint end, CPU.ReadDelegate func)
+ {
+ for (uint i = start; i <= end; i++)
+ {
+ _readMap[i] = func;
+ }
+ }
+
+ public void MapWriteHandler(uint start, uint end, CPU.WriteDelegate func)
+ {
+ for (uint i = start; i <= end; i++)
+ {
+ _writeMap[i] = func;
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/Addressable.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/Addressable.cs.meta
new file mode 100644
index 0000000..84e599c
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/Addressable.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8649a70e245ed1744aa620cb28504fb5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Core.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Core.cs
new file mode 100644
index 0000000..b68c527
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Core.cs
@@ -0,0 +1,81 @@
+using System;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ sealed partial class CPU
+ {
+ public enum InterruptType
+ {
+ NMI,
+ IRQ,
+ RESET
+ }
+
+ private readonly uint[] _interruptHandlerOffsets = { 0xFFFA, 0xFFFE, 0xFFFC };
+ private readonly bool[] _interrupts = new bool[2];
+
+ public void Initialize()
+ {
+ A = 0;
+ X = 0;
+ Y = 0;
+ SP = 0xFD;
+ P = 0x24;
+
+ PC = ReadWord(_interruptHandlerOffsets[(int) InterruptType.RESET]);
+ }
+
+ public void Reset()
+ {
+ SP -= 3;
+ F.InterruptsDisabled = true;
+ }
+
+ public void TickFromPPU()
+ {
+ if (Cycle-- > 0) return;
+ ExecuteSingleInstruction();
+ }
+
+ public void ExecuteSingleInstruction()
+ {
+ for (var i = 0; i < _interrupts.Length; i++)
+ {
+ if (_interrupts[i])
+ {
+ PushWord(PC);
+ Push(P);
+ PC = ReadWord(_interruptHandlerOffsets[i]);
+ F.InterruptsDisabled = true;
+ _interrupts[i] = false;
+ return;
+ }
+ }
+
+ _currentInstruction = NextByte();
+
+ Cycle += _opCodeDefs[_currentInstruction].Cycles;
+
+ ResetInstructionAddressingMode();
+ // if (_numExecuted > 10000 && PC - 1 == 0xFF61)
+ // if(_emulator.Controller.debug || 0x6E00 <= PC && PC <= 0x6EEF)
+ // Console.WriteLine($"{(PC - 1).ToString("X4")} {_currentInstruction.ToString("X2")} {opcodeNames[_currentInstruction]}\t\t\tA:{A.ToString("X2")} X:{X.ToString("X2")} Y:{Y.ToString("X2")} P:{P.ToString("X2")} SP:{SP.ToString("X2")}");
+
+ var op = _opCodes[_currentInstruction];
+ if (op == null)
+ {
+ throw new ArgumentException(_currentInstruction.ToString("X2"));
+ }
+
+ op();
+ }
+
+ public void TriggerInterrupt(InterruptType type)
+ {
+ if (!F.InterruptsDisabled || type == InterruptType.NMI)
+ {
+ _interrupts[(int)type] = true;
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Core.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Core.cs.meta
new file mode 100644
index 0000000..5f375dc
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Core.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 63ec605962bce0e4899b19d49d4ed747
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.IORegisters.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.IORegisters.cs
new file mode 100644
index 0000000..039a0d1
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.IORegisters.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ sealed partial class CPU
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void WriteIoRegister(uint reg, byte val)
+ {
+ switch (reg)
+ {
+ case 0x4014: // OAM DMA
+ _emulator.PPU.PerformDMA(val);
+ break;
+ case 0x4016:
+ _emulator.Controller.Strobe(val == 1);
+ break;
+ }
+
+ if (reg <= 0x401F)
+ {
+ return; // APU write
+ }
+
+ throw new NotImplementedException($"{reg:X4} = {val:X2}");
+ }
+
+ public uint ReadIORegister(uint reg)
+ {
+ switch (reg)
+ {
+ case 0x4016:
+ return (uint) _emulator.Controller.ReadState() & 0x1;
+ }
+ return 0x00;
+ //throw new NotImplementedException();
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.IORegisters.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.IORegisters.cs.meta
new file mode 100644
index 0000000..42ddf45
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.IORegisters.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6ad8b3c04ef34de48b5cab3029811d9d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Instructions.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Instructions.cs
new file mode 100644
index 0000000..1d32389
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Instructions.cs
@@ -0,0 +1,463 @@
+using System;
+using static AxibugEmuOnline.Client.UNES.CPU.AddressingMode;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ public sealed partial class CPU
+ {
+ [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
+ public class OpCodeDef : Attribute
+ {
+ public int OpCode;
+ public int Cycles = 1;
+ public bool PageBoundary;
+ public bool RMW;
+ public AddressingMode Mode = None;
+ }
+
+ [OpCodeDef(OpCode = 0x20, Cycles = 6)]
+ private void JSR()
+ {
+ PushWord(PC + 1);
+ PC = NextWord();
+ }
+
+ [OpCodeDef(OpCode = 0x40, Cycles = 6)]
+ private void RTI()
+ {
+ // TODO: this dummy fetch should happen for all single-byte instructions
+ NextByte();
+ P = Pop();
+ PC = PopWord();
+ }
+
+ [OpCodeDef(OpCode = 0x60, Cycles = 6)]
+ private void RTS()
+ {
+ NextByte();
+ PC = PopWord() + 1;
+ }
+
+ [OpCodeDef(OpCode = 0xC8, Cycles = 2)]
+ private void INY() => Y++;
+
+ [OpCodeDef(OpCode = 0x88, Cycles = 2)]
+ private void DEY() => Y--;
+
+ [OpCodeDef(OpCode = 0xE8, Cycles = 2)]
+ private void INX() => X++;
+
+ [OpCodeDef(OpCode = 0xCA, Cycles = 2, RMW = true)]
+ private void DEX() => X--;
+
+ [OpCodeDef(OpCode = 0xA8, Cycles = 2)]
+ private void TAY() => Y = A;
+
+ [OpCodeDef(OpCode = 0x98, Cycles = 2)]
+ private void TYA() => A = Y;
+
+ [OpCodeDef(OpCode = 0xAA, Cycles = 2, RMW = true)]
+ private void TAX() => X = A;
+
+ [OpCodeDef(OpCode = 0x8A, Cycles = 2, RMW = true)]
+ private void TXA() => A = X;
+
+ [OpCodeDef(OpCode = 0xBA, Cycles = 2)]
+ private void TSX() => X = SP;
+
+ [OpCodeDef(OpCode = 0x9A, Cycles = 2, RMW = true)]
+ private void TXS() => SP = X;
+
+ [OpCodeDef(OpCode = 0x08, Cycles = 3)]
+ private void PHP() => Push(P | BreakSourceBit);
+
+ [OpCodeDef(OpCode = 0x28, Cycles = 4)]
+ private void PLP() => P = (uint)(Pop() & ~BreakSourceBit);
+
+ [OpCodeDef(OpCode = 0x68, Cycles = 4)]
+ private void PLA() => A = Pop();
+
+ [OpCodeDef(OpCode = 0x48, Cycles = 3)]
+ private void PHA() => Push(A);
+
+ [OpCodeDef(OpCode = 0x24, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0x2C, Mode = Absolute, Cycles = 4)]
+ private void BIT()
+ {
+ uint val = AddressRead();
+ F.Overflow = (val & 0x40) > 0;
+ F.Zero = (val & A) == 0;
+ F.Negative = (val & 0x80) > 0;
+ }
+
+ private void Branch(bool cond)
+ {
+ uint nPC = (uint)(PC + NextSByte() + 1);
+ if (cond)
+ {
+ PC = nPC;
+ Cycle++;
+ }
+ }
+
+ [OpCodeDef(OpCode = 0x4C, Cycles = 3)]
+ [OpCodeDef(OpCode = 0x6C, Cycles = 5)]
+ private void JMP()
+ {
+ if (_currentInstruction == 0x4C)
+ {
+ PC = NextWord();
+ }
+ else if (_currentInstruction == 0x6C)
+ {
+ uint off = NextWord();
+ // AN INDIRECT JUMP MUST NEVER USE A VECTOR BEGINNING ON THE LAST BYTE OF A PAGE
+ //
+ // If address $3000 contains $40, $30FF contains $80, and $3100 contains $50,
+ // the result of JMP ($30FF) will be a transfer of control to $4080 rather than
+ // $5080 as you intended i.e. the 6502 took the low byte of the address from
+ // $30FF and the high byte from $3000.
+ //
+ // http://www.6502.org/tutorials/6502opcodes.html
+ uint hi = (off & 0xFF) == 0xFF ? off - 0xFF : off + 1;
+ uint oldPC = PC;
+ PC = ReadByte(off) | (ReadByte(hi) << 8);
+
+ if ((oldPC & 0xFF00) != (PC & 0xFF00))
+ {
+ Cycle += 2;
+ }
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [OpCodeDef(OpCode = 0xB0, Cycles = 2)]
+ private void BCS() => Branch(F.Carry);
+
+ [OpCodeDef(OpCode = 0x90, Cycles = 2)]
+ private void BCC() => Branch(!F.Carry);
+
+ [OpCodeDef(OpCode = 0xF0, Cycles = 2)]
+ private void BEQ() => Branch(F.Zero);
+
+ [OpCodeDef(OpCode = 0xD0, Cycles = 2)]
+ private void BNE() => Branch(!F.Zero);
+
+ [OpCodeDef(OpCode = 0x70, Cycles = 2)]
+ private void BVS() => Branch(F.Overflow);
+
+ [OpCodeDef(OpCode = 0x50, Cycles = 2)]
+ private void BVC() => Branch(!F.Overflow);
+
+ [OpCodeDef(OpCode = 0x10, Cycles = 2)]
+ private void BPL() => Branch(!F.Negative);
+
+ [OpCodeDef(OpCode = 0x30, Cycles = 2)]
+ private void BMI() => Branch(F.Negative);
+
+ [OpCodeDef(OpCode = 0x81, Mode = IndirectX, Cycles = 6)]
+ [OpCodeDef(OpCode = 0x91, Mode = IndirectY, Cycles = 6)]
+ [OpCodeDef(OpCode = 0x95, Mode = ZeroPageX, Cycles = 4)]
+ [OpCodeDef(OpCode = 0x99, Mode = AbsoluteY, Cycles = 5)]
+ [OpCodeDef(OpCode = 0x9D, Mode = AbsoluteX, Cycles = 5)]
+ [OpCodeDef(OpCode = 0x85, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0x8D, Mode = Absolute, Cycles = 4)]
+ private void STA() => AddressWrite(A);
+
+ [OpCodeDef(OpCode = 0x96, Mode = ZeroPageY, Cycles = 4)]
+ [OpCodeDef(OpCode = 0x86, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0x8E, Mode = Absolute, Cycles = 4)]
+ private void STX() => AddressWrite(X);
+
+ [OpCodeDef(OpCode = 0x94, Mode = ZeroPageX, Cycles = 4)]
+ [OpCodeDef(OpCode = 0x84, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0x8C, Mode = Absolute, Cycles = 4)]
+ private void STY() => AddressWrite(Y);
+
+ [OpCodeDef(OpCode = 0x18, Cycles = 2)]
+ private void CLC() => F.Carry = false;
+
+ [OpCodeDef(OpCode = 0x38, Cycles = 2)]
+ private void SEC() => F.Carry = true;
+
+ [OpCodeDef(OpCode = 0x58, Cycles = 2)]
+ private void CLI() => F.InterruptsDisabled = false;
+
+ [OpCodeDef(OpCode = 0x78, Cycles = 2)]
+ private void SEI() => F.InterruptsDisabled = true;
+
+ [OpCodeDef(OpCode = 0xB8, Cycles = 2)]
+ private void CLV() => F.Overflow = false;
+
+ [OpCodeDef(OpCode = 0xD8, Cycles = 2)]
+ private void CLD() => F.DecimalMode = false;
+
+ [OpCodeDef(OpCode = 0xF8, Cycles = 2)]
+ private void SED() => F.DecimalMode = true;
+
+ [OpCodeDef(OpCode = 0xEA, Cycles = 2)]
+ [OpCodeDef(OpCode = 0x1A, Cycles = 2)] // Unofficial
+ [OpCodeDef(OpCode = 0x3A, Cycles = 2)] // Unofficial
+ [OpCodeDef(OpCode = 0x5A, Cycles = 2)] // Unofficial
+ [OpCodeDef(OpCode = 0x7A, Cycles = 2)] // Unofficial
+ [OpCodeDef(OpCode = 0xDA, Cycles = 2)] // Unofficial
+ [OpCodeDef(OpCode = 0xFA, Cycles = 2)] // Unofficial
+ private void NOP() { }
+
+ [OpCodeDef(OpCode = 0xA1, Mode = IndirectX, Cycles = 6)]
+ [OpCodeDef(OpCode = 0xA5, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0xA9, Mode = Immediate, Cycles = 2)]
+ [OpCodeDef(OpCode = 0xAD, Mode = Absolute, Cycles = 4)]
+ [OpCodeDef(OpCode = 0xB1, Mode = IndirectY, Cycles = 5, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0xB5, Mode = ZeroPageX, Cycles = 4)]
+ [OpCodeDef(OpCode = 0xB9, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0xBD, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)]
+ private void LDA() => A = AddressRead();
+
+ [OpCodeDef(OpCode = 0xA0, Mode = Immediate, Cycles = 2)]
+ [OpCodeDef(OpCode = 0xA4, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0xAC, Mode = Absolute, Cycles = 4)]
+ [OpCodeDef(OpCode = 0xB4, Mode = ZeroPageX, Cycles = 4)]
+ [OpCodeDef(OpCode = 0xBC, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)]
+ private void LDY() => Y = AddressRead();
+
+ [OpCodeDef(OpCode = 0xA2, Mode = Immediate, Cycles = 2, RMW = true)]
+ [OpCodeDef(OpCode = 0xA6, Mode = ZeroPage, Cycles = 3, RMW = true)]
+ [OpCodeDef(OpCode = 0xAE, Mode = Absolute, Cycles = 4, RMW = true)]
+ [OpCodeDef(OpCode = 0xB6, Mode = ZeroPageY, Cycles = 4, RMW = true)]
+ [OpCodeDef(OpCode = 0xBE, Mode = AbsoluteY, Cycles = 4, PageBoundary = true, RMW = true)]
+ private void LDX() => X = AddressRead();
+
+ [OpCodeDef(OpCode = 0x01, Mode = IndirectX, Cycles = 6)]
+ [OpCodeDef(OpCode = 0x05, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0x09, Mode = Immediate, Cycles = 2)]
+ [OpCodeDef(OpCode = 0x0D, Mode = Absolute, Cycles = 4)]
+ [OpCodeDef(OpCode = 0x11, Mode = IndirectY, Cycles = 5, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0x15, Mode = ZeroPageX, Cycles = 4)]
+ [OpCodeDef(OpCode = 0x19, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0x1D, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)]
+ private void ORA() => A |= AddressRead();
+
+ [OpCodeDef(OpCode = 0x21, Mode = IndirectX, Cycles = 6)]
+ [OpCodeDef(OpCode = 0x25, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0x29, Mode = Immediate, Cycles = 2)]
+ [OpCodeDef(OpCode = 0x2D, Mode = Absolute, Cycles = 4)]
+ [OpCodeDef(OpCode = 0x31, Mode = IndirectY, Cycles = 5, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0x35, Mode = ZeroPageX, Cycles = 4)]
+ [OpCodeDef(OpCode = 0x39, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0x3D, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)]
+ private void AND() => A &= AddressRead();
+
+ [OpCodeDef(OpCode = 0x41, Mode = IndirectX, Cycles = 6)]
+ [OpCodeDef(OpCode = 0x45, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0x49, Mode = Immediate, Cycles = 2)]
+ [OpCodeDef(OpCode = 0x4D, Mode = Absolute, Cycles = 4)]
+ [OpCodeDef(OpCode = 0x51, Mode = IndirectY, Cycles = 5, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0x55, Mode = ZeroPageX, Cycles = 4)]
+ [OpCodeDef(OpCode = 0x59, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0x5D, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)]
+ private void EOR() => A ^= AddressRead();
+
+ [OpCodeDef(OpCode = 0xE1, Mode = IndirectX, Cycles = 6)]
+ [OpCodeDef(OpCode = 0xE5, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0x69, Mode = Immediate, Cycles = 2)]
+ [OpCodeDef(OpCode = 0xE9, Mode = Immediate, Cycles = 2)] // Official duplicate of $69
+ [OpCodeDef(OpCode = 0xEB, Mode = Immediate, Cycles = 2)] // Unofficial duplicate of $69
+ [OpCodeDef(OpCode = 0xED, Mode = Absolute, Cycles = 4)]
+ [OpCodeDef(OpCode = 0xF1, Mode = IndirectY, Cycles = 5, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0xF5, Mode = ZeroPageX, Cycles = 4)]
+ [OpCodeDef(OpCode = 0xF9, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0xFD, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)]
+ private void SBC() => ADCImpl((byte)~AddressRead());
+
+ [OpCodeDef(OpCode = 0x61, Mode = IndirectX, Cycles = 6)]
+ [OpCodeDef(OpCode = 0x65, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0x69, Mode = Immediate, Cycles = 2)]
+ [OpCodeDef(OpCode = 0x6D, Mode = Absolute, Cycles = 4)]
+ [OpCodeDef(OpCode = 0x71, Mode = IndirectY, Cycles = 5, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0x75, Mode = ZeroPageX, Cycles = 4)]
+ [OpCodeDef(OpCode = 0x79, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0x7D, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)]
+ private void ADC() => ADCImpl(AddressRead());
+
+ private void ADCImpl(uint val)
+ {
+ int nA = (sbyte)A + (sbyte)val + (sbyte)(F.Carry ? 1 : 0);
+ F.Overflow = nA < -128 || nA > 127;
+ F.Carry = (A + val + (F.Carry ? 1 : 0)) > 0xFF;
+ A = (byte)(nA & 0xFF);
+ }
+
+ [OpCodeDef(OpCode = 0x00, Cycles = 7)]
+ private void BRK()
+ {
+ NextByte();
+ Push(P | BreakSourceBit);
+ F.InterruptsDisabled = true;
+ PC = ReadByte(0xFFFE) | (ReadByte(0xFFFF) << 8);
+ }
+
+ [OpCodeDef(OpCode = 0xC1, Mode = IndirectX, Cycles = 6)]
+ [OpCodeDef(OpCode = 0xC5, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0xC9, Mode = Immediate, Cycles = 2)]
+ [OpCodeDef(OpCode = 0xCD, Mode = Absolute, Cycles = 4)]
+ [OpCodeDef(OpCode = 0xD1, Mode = IndirectY, Cycles = 5, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0xD5, Mode = ZeroPageX, Cycles = 4)]
+ [OpCodeDef(OpCode = 0xD9, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)]
+ [OpCodeDef(OpCode = 0xDD, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)]
+ private void CMP() => CMPImpl(A);
+
+ [OpCodeDef(OpCode = 0xE0, Mode = Immediate, Cycles = 2)]
+ [OpCodeDef(OpCode = 0xE4, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0xEC, Mode = Absolute, Cycles = 4)]
+ private void CPX() => CMPImpl(X);
+
+ [OpCodeDef(OpCode = 0xC0, Mode = Immediate, Cycles = 2)]
+ [OpCodeDef(OpCode = 0xC4, Mode = ZeroPage, Cycles = 3)]
+ [OpCodeDef(OpCode = 0xCC, Mode = Absolute, Cycles = 4)]
+ private void CPY() => CMPImpl(Y);
+
+ private void CMPImpl(uint reg)
+ {
+ long d = reg - (int)AddressRead();
+
+ F.Negative = (d & 0x80) > 0 && d != 0;
+ F.Carry = d >= 0;
+ F.Zero = d == 0;
+ }
+
+ [OpCodeDef(OpCode = 0x46, Mode = ZeroPage, Cycles = 5, RMW = true)]
+ [OpCodeDef(OpCode = 0x4E, Mode = Absolute, Cycles = 6, RMW = true)]
+ [OpCodeDef(OpCode = 0x56, Mode = ZeroPageX, Cycles = 6, RMW = true)]
+ [OpCodeDef(OpCode = 0x5E, Mode = AbsoluteX, Cycles = 7, RMW = true)]
+ [OpCodeDef(OpCode = 0x4A, Mode = Direct, Cycles = 2, RMW = true)]
+ private void LSR()
+ {
+ uint D = AddressRead();
+ F.Carry = (D & 0x1) > 0;
+ D >>= 1;
+ _F(D);
+ AddressWrite(D);
+ }
+
+ [OpCodeDef(OpCode = 0x06, Mode = ZeroPage, Cycles = 5, RMW = true)]
+ [OpCodeDef(OpCode = 0x0E, Mode = Absolute, Cycles = 6, RMW = true)]
+ [OpCodeDef(OpCode = 0x16, Mode = ZeroPageX, Cycles = 6, RMW = true)]
+ [OpCodeDef(OpCode = 0x1E, Mode = AbsoluteX, Cycles = 7, RMW = true)]
+ [OpCodeDef(OpCode = 0x0A, Mode = Direct, Cycles = 2, RMW = true)]
+ private void ASL()
+ {
+ uint D = AddressRead();
+ F.Carry = (D & 0x80) > 0;
+ D <<= 1;
+ _F(D);
+ AddressWrite(D);
+ }
+
+ [OpCodeDef(OpCode = 0x66, Mode = ZeroPage, Cycles = 5, RMW = true)]
+ [OpCodeDef(OpCode = 0x6E, Mode = Absolute, Cycles = 6, RMW = true)]
+ [OpCodeDef(OpCode = 0x76, Mode = ZeroPageX, Cycles = 6, RMW = true)]
+ [OpCodeDef(OpCode = 0x7E, Mode = AbsoluteX, Cycles = 7, RMW = true)]
+ [OpCodeDef(OpCode = 0x6A, Mode = Direct, Cycles = 2, RMW = true)]
+ private void ROR()
+ {
+ uint D = AddressRead();
+ bool c = F.Carry;
+ F.Carry = (D & 0x1) > 0;
+ D >>= 1;
+ if (c) D |= 0x80;
+ _F(D);
+ AddressWrite(D);
+ }
+
+ [OpCodeDef(OpCode = 0x26, Mode = ZeroPage, Cycles = 5, RMW = true)]
+ [OpCodeDef(OpCode = 0x2E, Mode = Absolute, Cycles = 6, RMW = true)]
+ [OpCodeDef(OpCode = 0x36, Mode = ZeroPageX, Cycles = 6, RMW = true)]
+ [OpCodeDef(OpCode = 0x3E, Mode = AbsoluteX, Cycles = 7, RMW = true)]
+ [OpCodeDef(OpCode = 0x2A, Mode = Direct, Cycles = 2, RMW = true)]
+ private void ROL()
+ {
+ uint D = AddressRead();
+ bool c = F.Carry;
+ F.Carry = (D & 0x80) > 0;
+ D <<= 1;
+ if (c) D |= 0x1;
+ _F(D);
+ AddressWrite(D);
+ }
+
+ [OpCodeDef(OpCode = 0xE6, Mode = ZeroPage, Cycles = 5, RMW = true)]
+ [OpCodeDef(OpCode = 0xEE, Mode = Absolute, Cycles = 6, RMW = true)]
+ [OpCodeDef(OpCode = 0xF6, Mode = ZeroPageX, Cycles = 6, RMW = true)]
+ [OpCodeDef(OpCode = 0xFE, Mode = AbsoluteX, Cycles = 7, RMW = true)]
+ private void INC()
+ {
+ byte D = (byte)(AddressRead() + 1);
+ _F(D);
+ AddressWrite(D);
+ }
+
+ [OpCodeDef(OpCode = 0xC6, Mode = ZeroPage, Cycles = 5, RMW = true)]
+ [OpCodeDef(OpCode = 0xCE, Mode = Absolute, Cycles = 3, RMW = true)]
+ [OpCodeDef(OpCode = 0xD6, Mode = ZeroPageX, Cycles = 6, RMW = true)]
+ [OpCodeDef(OpCode = 0xDE, Mode = AbsoluteX, Cycles = 7, RMW = true)]
+ private void DEC()
+ {
+ byte D = (byte)(AddressRead() - 1);
+ _F(D);
+ AddressWrite(D);
+ }
+
+ #region Unofficial Opcodes
+
+ [OpCodeDef(OpCode = 0x80, Cycles = 2)]
+ [OpCodeDef(OpCode = 0x82, Cycles = 2)]
+ [OpCodeDef(OpCode = 0x89, Cycles = 2)]
+ [OpCodeDef(OpCode = 0xC2, Cycles = 2)]
+ [OpCodeDef(OpCode = 0xE2, Cycles = 2)]
+ private void SKB() => NextByte(); // Essentially a 2-byte NOP
+
+ [OpCodeDef(OpCode = 0x0B, Mode = Immediate, Cycles = 2)]
+ [OpCodeDef(OpCode = 0x2B, Mode = Immediate, Cycles = 2)]
+ private void ANC()
+ {
+ A &= AddressRead();
+ F.Carry = F.Negative;
+ }
+
+ [OpCodeDef(OpCode = 0x4B, Mode = Immediate, Cycles = 2)]
+ private void ALR()
+ {
+ A &= AddressRead();
+ F.Carry = (A & 0x1) > 0;
+ A >>= 1;
+ _F(A);
+ }
+
+ [OpCodeDef(OpCode = 0x6B, Mode = Immediate, Cycles = 2)]
+ private void ARR()
+ {
+ A &= AddressRead();
+ bool c = F.Carry;
+ F.Carry = (A & 0x1) > 0;
+ A >>= 1;
+ if (c) A |= 0x80;
+ _F(A);
+ }
+
+ [OpCodeDef(OpCode = 0xAB, Mode = Immediate, Cycles = 2)]
+ private void ATX()
+ {
+ // This opcode ORs the A register with #$EE, ANDs the result with an immediate
+ // value, and then stores the result in both A and X.
+ A |= ReadByte(0xEE);
+ A &= AddressRead();
+ X = A;
+ }
+
+ #endregion
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Instructions.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Instructions.cs.meta
new file mode 100644
index 0000000..7817694
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Instructions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cae8846694d75f04a8214a484032d533
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Memory.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Memory.cs
new file mode 100644
index 0000000..5a628b7
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Memory.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using static AxibugEmuOnline.Client.UNES.CPU.AddressingMode;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ sealed partial class CPU
+ {
+ public enum AddressingMode
+ {
+ None,
+ Direct,
+ Immediate,
+ ZeroPage,
+ Absolute,
+ ZeroPageX,
+ ZeroPageY,
+ AbsoluteX,
+ AbsoluteY,
+ IndirectX,
+ IndirectY
+ }
+
+ private uint? _currentMemoryAddress;
+ private uint _rmwValue;
+
+ private void ResetInstructionAddressingMode() => _currentMemoryAddress = null;
+
+ private uint _Address()
+ {
+ var def = _opCodeDefs[_currentInstruction];
+ switch (def.Mode)
+ {
+ case Immediate:
+ return PC++;
+ case ZeroPage:
+ return NextByte();
+ case Absolute:
+ return NextWord();
+ case ZeroPageX:
+ return (NextByte() + X) & 0xFF;
+ case ZeroPageY:
+ return (NextByte() + Y) & 0xFF;
+ case AbsoluteX:
+ var address = NextWord();
+ if (def.PageBoundary && (address & 0xFF00) != ((address + X) & 0xFF00))
+ {
+ Cycle += 1;
+ }
+
+ return address + X;
+ case AbsoluteY:
+ address = NextWord();
+ if (def.PageBoundary && (address & 0xFF00) != ((address + Y) & 0xFF00))
+ {
+ Cycle += 1;
+ }
+
+ return address + Y;
+ case IndirectX:
+ var off = (NextByte() + X) & 0xFF;
+ return ReadByte(off) | (ReadByte((off + 1) & 0xFF) << 8);
+ case IndirectY:
+ off = NextByte() & 0xFF;
+ address = ReadByte(off) | (ReadByte((off + 1) & 0xFF) << 8);
+ if (def.PageBoundary && (address & 0xFF00) != ((address + Y) & 0xFF00))
+ {
+ Cycle += 1;
+ }
+
+ return (address + Y) & 0xFFFF;
+ }
+ throw new NotImplementedException();
+ }
+
+ public uint AddressRead()
+ {
+ if (_opCodeDefs[_currentInstruction].Mode == Direct)
+ {
+ return _rmwValue = A;
+ }
+
+ if (_currentMemoryAddress == null)
+ {
+ _currentMemoryAddress = _Address();
+ }
+
+ return _rmwValue = ReadByte((uint)_currentMemoryAddress) & 0xFF;
+ }
+
+ public void AddressWrite(uint val)
+ {
+ if (_opCodeDefs[_currentInstruction].Mode == Direct)
+ {
+ A = val;
+ }
+ else
+ {
+ if (_currentMemoryAddress == null)
+ {
+ _currentMemoryAddress = _Address();
+ }
+
+ if (_opCodeDefs[_currentInstruction].RMW)
+ {
+ WriteByte((uint)_currentMemoryAddress, _rmwValue);
+ }
+
+ WriteByte((uint)_currentMemoryAddress, val);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private uint ReadWord(uint address) => ReadByte(address) | (ReadByte(address + 1) << 8);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private uint NextByte() => ReadByte(PC++);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private uint NextWord() => NextByte() | (NextByte() << 8);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private sbyte NextSByte() => (sbyte)NextByte();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void Push(uint what)
+ {
+ WriteByte(0x100 + SP, what);
+ SP--;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private uint Pop()
+ {
+ SP++;
+ return ReadByte(0x100 + SP);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void PushWord(uint what)
+ {
+ Push(what >> 8);
+ Push(what & 0xFF);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private uint PopWord() => Pop() | (Pop() << 8);
+
+ protected override void InitializeMemoryMap()
+ {
+ base.InitializeMemoryMap();
+
+ MapReadHandler(0x0000, 0x1FFF, address => _ram[address & 0x07FF]);
+ MapReadHandler(0x2000, 0x3FFF, address => _emulator.PPU.ReadRegister((address & 0x7) - 0x2000));
+ MapReadHandler(0x4000, 0x4017, ReadIORegister);
+
+ MapWriteHandler(0x0000, 0x1FFF, (address, val) => _ram[address & 0x07FF] = val);
+ MapWriteHandler(0x2000, 0x3FFF, (address, val) => _emulator.PPU.WriteRegister((address & 0x7) - 0x2000, val));
+ MapWriteHandler(0x4000, 0x401F, WriteIoRegister);
+
+ _emulator.Mapper.InitializeMemoryMap(this);
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Memory.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Memory.cs.meta
new file mode 100644
index 0000000..e10a02a
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Memory.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cc18416f50415b74e96092e235a95f92
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Registers.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Registers.cs
new file mode 100644
index 0000000..3179eb6
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Registers.cs
@@ -0,0 +1,89 @@
+using System.Runtime.CompilerServices;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ sealed partial class CPU
+ {
+ private const int CarryBit = 0x1;
+ private const int ZeroBit = 0x2;
+ private const int InterruptDisabledBit = 0x4;
+ private const int DecimalModeBit = 0x8;
+ private const int BreakSourceBit = 0x10;
+ private const int OverflowBit = 0x40;
+ private const int NegativeBit = 0x80;
+
+ public class CPUFlags
+ {
+ public bool Negative;
+ public bool Overflow;
+ public bool BreakSource;
+ public bool DecimalMode;
+ public bool InterruptsDisabled;
+ public bool Zero;
+ public bool Carry;
+ }
+
+ public readonly CPUFlags F = new CPUFlags();
+
+ public uint _A, _X, _Y, _SP;
+ public uint PC;
+
+ public uint A
+ {
+ get => _A;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private set => _A = _F(value & 0xFF);
+ }
+
+ public uint X
+ {
+ get => _X;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private set => _X = _F(value & 0xFF);
+ }
+
+ public uint Y
+ {
+ get => _Y;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private set => _Y = _F(value & 0xFF);
+ }
+
+ public uint SP
+ {
+ get => _SP;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private set => _SP = value & 0xFF;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private uint _F(uint val)
+ {
+ F.Zero = (val & 0xFF) == 0;
+ F.Negative = (val & 0x80) > 0;
+ return val;
+ }
+
+ public uint P
+ {
+ get => (uint) ((F.Carry.AsByte() << 0) |
+ (F.Zero.AsByte() << 1) |
+ (F.InterruptsDisabled.AsByte() << 2) |
+ (F.DecimalMode.AsByte() << 3) |
+ (F.BreakSource.AsByte() << 4) |
+ (1 << 5) |
+ (F.Overflow.AsByte() << 6) |
+ (F.Negative.AsByte() << 7));
+ set
+ {
+ F.Carry = (value & CarryBit) > 0;
+ F.Zero = (value & ZeroBit) > 0;
+ F.InterruptsDisabled = (value & InterruptDisabledBit) > 0;
+ F.DecimalMode = (value & DecimalModeBit) > 0;
+ F.BreakSource = (value & BreakSourceBit) > 0;
+ F.Overflow = (value & OverflowBit) > 0;
+ F.Negative = (value & NegativeBit) > 0;
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Registers.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Registers.cs.meta
new file mode 100644
index 0000000..508a0d9
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.Registers.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 57750595bb8e0d24babf00d51e4624bb
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.cs
new file mode 100644
index 0000000..6bd0564
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Linq;
+using System.Reflection;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ sealed partial class CPU : Addressable
+ {
+ private readonly byte[] _ram = new byte[0x800];
+ public int Cycle;
+ private uint _currentInstruction;
+
+ public delegate void OpCode();
+
+ private readonly OpCode[] _opCodes = new OpCode[256];
+ private readonly string[] _opCodeNames = new string[256];
+ private readonly OpCodeDef[] _opCodeDefs = new OpCodeDef[256];
+
+ public CPU(Emulator emulator) : base(emulator, 0xFFFF)
+ {
+ InitializeOpCodes();
+ InitializeMemoryMap();
+ Initialize();
+ }
+
+ private void InitializeOpCodes()
+ {
+ var opCodeBindings = from opCode in GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
+ let defs = opCode.GetCustomAttributes(typeof(OpCodeDef), false)
+ where defs.Length > 0
+ select new
+ {
+ binding = (OpCode)Delegate.CreateDelegate(typeof(OpCode), this, opCode.Name),
+ name = opCode.Name,
+ defs = (from d in defs select (OpCodeDef)d)
+ };
+
+ foreach (var opCode in opCodeBindings)
+ {
+ foreach (var def in opCode.defs)
+ {
+ _opCodes[def.OpCode] = opCode.binding;
+ _opCodeNames[def.OpCode] = opCode.name;
+ _opCodeDefs[def.OpCode] = def;
+ }
+ }
+ }
+
+ public void Execute()
+ {
+ for (var i = 0; i < 5000; i++)
+ {
+ ExecuteSingleInstruction();
+ }
+
+ uint w;
+ ushort x = 6000;
+ string z = "";
+ while ((w = ReadByte(x)) != '\0')
+ {
+ z += (char)w;
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.cs.meta
new file mode 100644
index 0000000..f80ca2b
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/CPU.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fa724afa42498dd4e80f44c3da2a5881
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/Cartridge.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/Cartridge.cs
new file mode 100644
index 0000000..bb1c6bc
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/Cartridge.cs
@@ -0,0 +1,66 @@
+using System;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ public class Cartridge
+ {
+ public readonly byte[] Raw;
+ public readonly int PRGROMSize;
+ public readonly int CHRROMSize;
+ public readonly int PRGRAMSize;
+ public readonly int PRGROMOffset;
+ public readonly int MapperNumber;
+ public readonly byte[] PRGROM;
+ public readonly byte[] CHRROM;
+ public VRAMMirroringMode MirroringMode;
+
+ public enum VRAMMirroringMode
+ {
+ Horizontal, Vertical, All, Upper, Lower
+ }
+
+ public Cartridge(byte[] bytes)
+ {
+ Raw = bytes;
+
+ var header = BitConverter.ToInt32(Raw, 0);
+ if (header != 0x1A53454E) // "NES"
+ {
+ throw new FormatException("unexpected header value " + header.ToString("X"));
+ }
+
+ PRGROMSize = Raw[4] * 0x4000; // 16kb units
+ CHRROMSize = Raw[5] * 0x2000; // 8kb units
+ PRGRAMSize = Raw[8] * 0x2000;
+
+ var hasTrainer = (Raw[6] & 0b100) > 0;
+ PRGROMOffset = 16 + (hasTrainer ? 512 : 0);
+
+ MirroringMode = (Raw[6] & 0x1) > 0 ? VRAMMirroringMode.Vertical : VRAMMirroringMode.Horizontal;
+ if ((Raw[6] & 0x8) > 0)
+ {
+ MirroringMode = VRAMMirroringMode.All;
+ }
+
+ MapperNumber = (Raw[6] >> 4) | (Raw[7] & 0xF0);
+
+ PRGROM = new byte[PRGROMSize];
+ Array.Copy(Raw, PRGROMOffset, PRGROM, 0, PRGROMSize);
+
+ if (CHRROMSize == 0)
+ {
+ CHRROM = new byte[0x2000];
+ }
+ else
+ {
+ CHRROM = new byte[CHRROMSize];
+ Array.Copy(Raw, PRGROMOffset + PRGROMSize, CHRROM, 0, CHRROMSize);
+ }
+ }
+
+ public override string ToString()
+ {
+ return $"Cartridge{{PRGROMSize={PRGROMSize}, CHRROMSize={CHRROMSize}, PRGROMOffset={PRGROMOffset}, MapperNumber={MapperNumber}}}";
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/Cartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/Cartridge.cs.meta
new file mode 100644
index 0000000..4fcccd7
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/Cartridge.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b45675095efbb224cad44ee16c2587ff
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/Emulator.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/Emulator.cs
new file mode 100644
index 0000000..271c287
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/Emulator.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using AxibugEmuOnline.Client.UNES.Controller;
+using AxibugEmuOnline.Client.UNES.Mapper;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ public class Emulator
+ {
+ private static readonly Dictionary> Mappers = (from type in Assembly.GetExecutingAssembly().GetTypes()
+ let def = (MapperDef)type.GetCustomAttributes(typeof(MapperDef), true).FirstOrDefault()
+ where def != null
+ select new { def, type }).ToDictionary(a => a.def.Id, a => new KeyValuePair(a.type, a.def));
+
+ public IController Controller;
+
+ public readonly CPU CPU;
+
+ public readonly PPU PPU;
+
+ public readonly BaseMapper Mapper;
+
+ public readonly Cartridge Cartridge;
+
+ public Emulator(byte[] bytes, IController controller)
+ {
+ Cartridge = new Cartridge(bytes);
+ if (!Mappers.ContainsKey(Cartridge.MapperNumber))
+ {
+ throw new NotImplementedException($"unsupported mapper {Cartridge.MapperNumber}");
+ }
+
+ Mapper = (BaseMapper)Activator.CreateInstance(Mappers[Cartridge.MapperNumber].Key, this);
+ CPU = new CPU(this);
+ PPU = new PPU(this);
+ Controller = controller;
+
+ // Load();
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/Emulator.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/Emulator.cs.meta
new file mode 100644
index 0000000..49774f0
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/Emulator.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9c56d6518f6443745a10dd7dda0ac7f9
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Core.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Core.cs
new file mode 100644
index 0000000..5ead01e
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Core.cs
@@ -0,0 +1,394 @@
+using System;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ partial class PPU
+ {
+ private const int GameWidth = 256, GameHeight = 240;
+
+ private uint _bufferPos;
+ public readonly uint[] RawBitmap = new uint[GameWidth * GameHeight];
+ //查找表的Idx网络缓存
+ public readonly byte[] RawBitmap_paletteIdxCache = new byte[GameWidth * GameHeight];
+ private readonly uint[] _priority = new uint[GameWidth * GameHeight];
+
+ // TODO: use real chroma/luma decoding
+ private readonly uint[] _palette = {
+ 0x7C7C7C, 0x0000FC, 0x0000BC, 0x4428BC, 0x940084, 0xA80020, 0xA81000, 0x881400,
+ 0x503000, 0x007800, 0x006800, 0x005800, 0x004058, 0x000000, 0x000000, 0x000000,
+ 0xBCBCBC, 0x0078F8, 0x0058F8, 0x6844FC, 0xD800CC, 0xE40058, 0xF83800, 0xE45C10,
+ 0xAC7C00, 0x00B800, 0x00A800, 0x00A844, 0x008888, 0x000000, 0x000000, 0x000000,
+ 0xF8F8F8, 0x3CBCFC, 0x6888FC, 0x9878F8, 0xF878F8, 0xF85898, 0xF87858, 0xFCA044,
+ 0xF8B800, 0xB8F818, 0x58D854, 0x58F898, 0x00E8D8, 0x787878, 0x000000, 0x000000,
+ 0xFCFCFC, 0xA4E4FC, 0xB8B8F8, 0xD8B8F8, 0xF8B8F8, 0xF8A4C0, 0xF0D0B0, 0xFCE0A8,
+ 0xF8D878, 0xD8F878, 0xB8F8B8, 0xB8F8D8, 0x00FCFC, 0xF8D8F8, 0x000000, 0x000000
+ };
+
+ private int _scanLineCount = 261;
+ private int _cyclesPerLine = 341;
+ private int _cpuSyncCounter;
+ private readonly uint[] _scanLineOAM = new uint[8 * 4];
+ private readonly bool[] _isSprite0 = new bool[8];
+ private int _spriteCount;
+
+ private long _tileShiftRegister;
+ private uint _currentNameTableByte;
+ private uint _currentHighTile, _currentLowTile;
+ private uint _currentColor;
+
+ public void ProcessPixel(int x, int y)
+ {
+ ProcessBackgroundForPixel(x, y);
+ if (F.DrawSprites)
+ {
+ ProcessSpritesForPixel(x, y);
+ }
+
+ if (y != -1)
+ {
+ _bufferPos++;
+ }
+ }
+
+ private void CountSpritesOnLine(int scanLine)
+ {
+ _spriteCount = 0;
+ var height = F.TallSpritesEnabled ? 16 : 8;
+
+ for (var idx = 0; idx < _oam.Length; idx += 4)
+ {
+ var y = _oam[idx] + 1;
+ if (scanLine >= y && scanLine < y + height)
+ {
+ _isSprite0[_spriteCount] = idx == 0;
+ _scanLineOAM[_spriteCount * 4 + 0] = _oam[idx + 0];
+ _scanLineOAM[_spriteCount * 4 + 1] = _oam[idx + 1];
+ _scanLineOAM[_spriteCount * 4 + 2] = _oam[idx + 2];
+ _scanLineOAM[_spriteCount * 4 + 3] = _oam[idx + 3];
+ _spriteCount++;
+ }
+
+ if (_spriteCount == 8)
+ {
+ break;
+ }
+ }
+ }
+
+ private void NextNameTableByte()
+ {
+ _currentNameTableByte = ReadByte(0x2000 | (V & 0x0FFF));
+ }
+
+ private void NextTileByte(bool hi)
+ {
+ var tileIdx = _currentNameTableByte * 16;
+ var address = F.PatternTableAddress + tileIdx + FineY;
+
+ if (hi)
+ {
+ _currentHighTile = ReadByte(address + 8);
+ }
+ else
+ {
+ _currentLowTile = ReadByte(address);
+ }
+ }
+
+ private void NextAttributeByte()
+ {
+ // Bless nesdev
+ var address = 0x23C0 | (V & 0x0C00) | ((V >> 4) & 0x38) | ((V >> 2) & 0x07);
+ _currentColor = (ReadByte(address) >> (int)((CoarseX & 2) | ((CoarseY & 2) << 1))) & 0x3;
+ }
+
+ private void ShiftTileRegister()
+ {
+ for (var x = 0; x < 8; x++)
+ {
+ uint palette = ((_currentHighTile & 0x80) >> 6) | ((_currentLowTile & 0x80) >> 7);
+ _tileShiftRegister |= (palette + _currentColor * 4) << ((7 - x) * 4);
+ _currentLowTile <<= 1;
+ _currentHighTile <<= 1;
+ }
+ }
+
+ private void ProcessBackgroundForPixel(int cycle, int scanLine)
+ {
+ if (cycle < 8 && !F.DrawLeftBackground || !F.DrawBackground && scanLine != -1)
+ {
+ // Maximally sketchy: if current address is in the PPU palette, then it draws that palette entry if rendering is disabled
+ // Otherwise, it draws $3F00 (universal bg color)
+ // https://www.romhacking.net/forum/index.php?topic=20554.0
+ // Don't know if any game actually uses it, but a test ROM I wrote unexpectedly showed this
+ // corner case
+
+ //RawBitmap[_bufferPos] = _palette[ReadByte(0x3F00 + ((F.BusAddress & 0x3F00) == 0x3F00 ? F.BusAddress & 0x001F : 0)) & 0x3F];
+ byte pIdx = (byte)(ReadByte(0x3F00 + ((F.BusAddress & 0x3F00) == 0x3F00 ? F.BusAddress & 0x001F : 0)) & 0x3F);
+ //RawBitmap[_bufferPos] = _palette[pIdx];
+ RawBitmap_paletteIdxCache[_bufferPos] = pIdx;
+ return;
+ }
+
+ var paletteEntry = (uint)(_tileShiftRegister >> 32 >> (int)((7 - X) * 4)) & 0x0F;
+ if (paletteEntry % 4 == 0) paletteEntry = 0;
+
+ if (scanLine != -1)
+ {
+ _priority[_bufferPos] = paletteEntry;
+ //RawBitmap[_bufferPos] = _palette[ReadByte(0x3F00u + paletteEntry) & 0x3F];
+ byte pIdx = (byte)(ReadByte(0x3F00u + paletteEntry) & 0x3F);
+ //RawBitmap[_bufferPos] = _palette[pIdx];
+ RawBitmap_paletteIdxCache[_bufferPos] = pIdx;
+ }
+ }
+
+ private void ProcessSpritesForPixel(int x, int scanLine)
+ {
+ for (var idx = _spriteCount * 4 - 4; idx >= 0; idx -= 4)
+ {
+ var spriteX = _scanLineOAM[idx + 3];
+ var spriteY = _scanLineOAM[idx] + 1;
+
+ // Don't draw this sprite if...
+ if (spriteY == 0 || // it's located at y = 0
+ spriteY > 239 || // it's located past y = 239 ($EF)
+ x >= spriteX + 8 || // it's behind the current dot
+ x < spriteX || // it's ahead of the current dot
+ x < 8 && !F.DrawLeftSprites) // it's in the clip area, and clipping is enabled
+ {
+ continue;
+ }
+
+ // amusingly enough, the PPU's palette handling is basically identical
+ // to that of the Gameboy / Gameboy Color, so I've sort of just copy/pasted
+ // handling code wholesale from my GBC emulator at
+ // https://github.com/Xyene/Nitrous-Emulator/blob/master/src/main/java/nitrous/lcd/LCD.java#L642
+ var tileIdx = _scanLineOAM[idx + 1];
+ if (F.TallSpritesEnabled)
+ {
+ tileIdx &= ~0x1u;
+ }
+
+ tileIdx *= 16;
+
+ var attribute = _scanLineOAM[idx + 2] & 0xE3;
+
+ var palette = attribute & 0x3;
+ var front = (attribute & 0x20) == 0;
+ var flipX = (attribute & 0x40) > 0;
+ var flipY = (attribute & 0x80) > 0;
+
+ var px = (int) (x - spriteX);
+ var line = (int) (scanLine - spriteY);
+
+ var tableBase = F.TallSpritesEnabled ? (_scanLineOAM[idx + 1] & 1) * 0x1000 : F.SpriteTableAddress;
+
+ if (F.TallSpritesEnabled)
+ {
+ if (line >= 8)
+ {
+ line -= 8;
+ if (!flipY)
+ {
+ tileIdx += 16;
+ }
+
+ flipY = false;
+ }
+
+ if (flipY)
+ {
+ tileIdx += 16;
+ }
+ }
+
+ // here we handle the x and y flipping by tweaking the indices we are accessing
+ var logicalX = flipX ? 7 - px : px;
+ var logicalLine = flipY ? 7 - line : line;
+
+ var address = (uint) (tableBase + tileIdx + logicalLine);
+
+ // this looks bad, but it's about as readable as it's going to get
+ var color = (uint) (
+ (
+ (
+ (
+ // fetch upper bit from 2nd bit plane
+ ReadByte(address + 8) & (0x80 >> logicalX)
+ ) >> (7 - logicalX)
+ ) << 1 // this is the upper bit of the color number
+ ) |
+ (
+ (
+ ReadByte(address) & (0x80 >> logicalX)
+ ) >> (7 - logicalX)
+ )); // << 0, this is the lower bit of the color number
+
+ if (color > 0)
+ {
+ var backgroundPixel = _priority[_bufferPos];
+ // Sprite 0 hits...
+ if (!(!_isSprite0[idx / 4] || // do not occur on not-0 sprite
+ x < 8 && !F.DrawLeftSprites || // or if left clipping is enabled
+ backgroundPixel == 0 || // or if bg pixel is transparent
+ F.Sprite0Hit || // or if it fired this frame already
+ x == 255)) // or if x is 255, "for an obscure reason related to the pixel pipeline"
+ {
+ F.Sprite0Hit = true;
+ }
+
+ if (F.DrawBackground && (front || backgroundPixel == 0))
+ {
+ if (scanLine != -1)
+ {
+ //RawBitmap[_bufferPos] = _palette[ReadByte(0x3F10 + palette * 4 + color) & 0x3F];
+
+ byte pIdx = (byte)(ReadByte(0x3F10 + palette * 4 + color) & 0x3F);
+ RawBitmap[_bufferPos] = _palette[pIdx];
+ RawBitmap_paletteIdxCache[_bufferPos] = pIdx;
+ }
+ }
+ }
+ }
+ }
+
+ public void ProcessFrame()
+ {
+ RawBitmap.Fill(0u);
+ RawBitmap_paletteIdxCache.Fill((byte)0);
+ _priority.Fill(0u);
+ _bufferPos = 0;
+
+ for (var i = -1; i < _scanLineCount; i++)
+ {
+ ProcessScanLine(i);
+ }
+ }
+
+ public void ProcessScanLine(int line)
+ {
+ for (var i = 0; i < _cyclesPerLine; i++)
+ {
+ ProcessCycle(line, i);
+ }
+ }
+
+ private int _cpuClocksSinceVBL;
+ private int _ppuClocksSinceVBL;
+
+ public void ProcessCycle(int scanLine, int cycle)
+ {
+ var visibleCycle = 1 <= cycle && cycle <= 256;
+ var prefetchCycle = 321 <= cycle && cycle <= 336;
+ var fetchCycle = visibleCycle || prefetchCycle;
+
+ if (F.VBlankStarted)
+ {
+ _ppuClocksSinceVBL++;
+ }
+
+ if (0 <= scanLine && scanLine < 240 || scanLine == -1)
+ {
+ if (visibleCycle)
+ {
+ ProcessPixel(cycle - 1, scanLine);
+ }
+
+ // During pixels 280 through 304 of this scanline, the vertical scroll bits are reloaded TODO: if rendering is enabled.
+ if (scanLine == -1 && 280 <= cycle && cycle <= 304)
+ {
+ ReloadScrollY();
+ }
+
+ if (fetchCycle)
+ {
+ _tileShiftRegister <<= 4;
+
+ // See https://wiki.nesdev.com/w/images/d/d1/Ntsc_timing.png
+ // Takes 8 cycles for tile to be read, 2 per "step"
+ switch (cycle & 7)
+ {
+ case 1: // NT
+ NextNameTableByte();
+ break;
+ case 3: // AT
+ NextAttributeByte();
+ break;
+ case 5: // Tile low
+ NextTileByte(false);
+ break;
+ case 7: // Tile high
+ NextTileByte(true);
+ break;
+ case 0: // 2nd cycle of tile high fetch
+ if (cycle == 256)
+ IncrementScrollY();
+ else
+ IncrementScrollX();
+ // Begin rendering a brand new tile
+ ShiftTileRegister();
+ break;
+ }
+ }
+
+ if (cycle == 257)
+ {
+ ReloadScrollX();
+ // 257 - 320
+ // The tile data for the sprites on the next scanline are fetched here.
+ // TODO: stagger this over all the cycles as opposed to only on 257
+ CountSpritesOnLine(scanLine + 1);
+ }
+ }
+
+ // TODO: this is a hack; VBlank should be cleared on dot 1 of the pre-render line,
+ // but for some reason we're at 2272-2273 CPU clocks at that time
+ // (i.e., our PPU timing is off somewhere by 6-9 PPU cycles per frame)
+ if (F.VBlankStarted && _cpuClocksSinceVBL == 2270)
+ {
+ F.VBlankStarted = false;
+ _cpuClocksSinceVBL = 0;
+ }
+
+ if (cycle == 1)
+ {
+ if (scanLine == 241)
+ {
+ F.VBlankStarted = true;
+ if (F.NMIEnabled)
+ {
+ _emulator.CPU.TriggerInterrupt(CPU.InterruptType.NMI);
+ }
+ }
+
+ // Happens at the same time as 1st cycle of NT byte fetch
+ if (scanLine == -1)
+ {
+ // Console.WriteLine(_ppuClocksSinceVBL);
+ _ppuClocksSinceVBL = 0;
+ F.VBlankStarted = false;
+ F.Sprite0Hit = false;
+ F.SpriteOverflow = false;
+ }
+ }
+
+ _emulator.Mapper.ProcessCycle(scanLine, cycle);
+
+ if (_cpuSyncCounter + 1 == 3)
+ {
+ if (F.VBlankStarted)
+ {
+ _cpuClocksSinceVBL++;
+ }
+
+ _emulator.CPU.TickFromPPU();
+ _cpuSyncCounter = 0;
+ }
+ else
+ {
+ _cpuSyncCounter++;
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Core.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Core.cs.meta
new file mode 100644
index 0000000..db54fa8
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Core.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c6d671ac51126cd43981be89a0309422
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Memory.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Memory.cs
new file mode 100644
index 0000000..10afa10
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Memory.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ partial class PPU
+ {
+ private readonly byte[] _oam = new byte[0x100];
+ private readonly byte[] _vRam = new byte[0x2000];
+ private readonly byte[] _paletteRAM = new byte[0x20];
+
+ private static readonly uint[][] _vRamMirrorLookup =
+ {
+ new uint[]{0, 0, 1, 1}, // H
+ new uint[]{0, 1, 0, 1}, // V
+ new uint[]{0, 1, 2, 3}, // All
+ new uint[]{0, 0, 0, 0}, // Upper
+ new uint[]{1, 1, 1, 1}, // Lower
+ };
+
+ private int _lastWrittenRegister;
+
+ public void WriteRegister(uint reg, byte val)
+ {
+ reg &= 0xF;
+ _lastWrittenRegister = val & 0xFF;
+ switch (reg)
+ {
+ case 0x0000:
+ PPUCTRL = val;
+ return;
+ case 0x0001:
+ PPUMASK = val;
+ return;
+ case 0x0002: return;
+ case 0x0003:
+ OAMADDR = val;
+ return;
+ case 0x0004:
+ OAMDATA = val;
+ return;
+ case 0x005:
+ PPUSCROLL = val;
+ return;
+ case 0x0006:
+ PPUADDR = val;
+ return;
+ case 0x0007:
+ PPUDATA = val;
+ return;
+ }
+
+ throw new NotImplementedException($"{reg:X4} = {val:X2}");
+ }
+
+ public byte ReadRegister(uint reg)
+ {
+ reg &= 0xF;
+ switch (reg)
+ {
+ case 0x0000: return (byte)_lastWrittenRegister;
+ case 0x0001: return (byte)_lastWrittenRegister;
+ case 0x0002:
+ return (byte)PPUSTATUS;
+ case 0x0003:
+ return (byte)OAMADDR;
+ case 0x0004:
+ return (byte)OAMDATA;
+ case 0x0005: return (byte)_lastWrittenRegister;
+ case 0x0006: return (byte)_lastWrittenRegister;
+ case 0x0007:
+ return (byte)PPUDATA;
+ }
+ throw new NotImplementedException(reg.ToString("X2"));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public uint GetVRamMirror(long address)
+ {
+ long entry;
+ var table = Math.DivRem(address - 0x2000, 0x400, out entry);
+ return _vRamMirrorLookup[(int)_emulator.Cartridge.MirroringMode][table] * 0x400 + (uint)entry;
+ }
+
+ protected override void InitializeMemoryMap()
+ {
+ base.InitializeMemoryMap();
+
+ MapReadHandler(0x2000, 0x2FFF, address => _vRam[GetVRamMirror(address)]);
+ MapReadHandler(0x3000, 0x3EFF, address => _vRam[GetVRamMirror(address - 0x1000)]);
+ MapReadHandler(0x3F00, 0x3FFF, address =>
+ {
+ if (address == 0x3F10 || address == 0x3F14 || address == 0x3F18 || address == 0x3F0C)
+ {
+ address -= 0x10;
+ }
+
+ return _paletteRAM[(address - 0x3F00) & 0x1F];
+ });
+
+ MapWriteHandler(0x2000, 0x2FFF, (address, val) => _vRam[GetVRamMirror(address)] = val);
+ MapWriteHandler(0x3000, 0x3EFF, (address, val) => _vRam[GetVRamMirror(address - 0x1000)] = val);
+ MapWriteHandler(0x3F00, 0x3FFF, (address, val) =>
+ {
+ if (address == 0x3F10 || address == 0x3F14 || address == 0x3F18 || address == 0x3F0C)
+ {
+ address -= 0x10;
+ }
+
+ _paletteRAM[(address - 0x3F00) & 0x1F] = val;
+ });
+
+ _emulator.Mapper.InitializeMemoryMap(this);
+ }
+
+ public void PerformDMA(uint from)
+ {
+ //Console.WriteLine("OAM DMA");
+ from <<= 8;
+ for (uint i = 0; i <= 0xFF; i++)
+ {
+ _oam[F.OAMAddress] = (byte)_emulator.CPU.ReadByte(from);
+ from++;
+ F.OAMAddress++;
+ }
+
+ _emulator.CPU.Cycle += 513 + _emulator.CPU.Cycle % 2;
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Memory.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Memory.cs.meta
new file mode 100644
index 0000000..cf6d09f
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Memory.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fa442177f52c5ff4a805fb4e1a30703b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Registers.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Registers.cs
new file mode 100644
index 0000000..338d1dc
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Registers.cs
@@ -0,0 +1,261 @@
+using System;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ partial class PPU
+ {
+ public class PPUFlags
+ {
+ /* PPUCTRL register */
+ public bool NMIEnabled;
+ public bool IsMaster;
+ public bool TallSpritesEnabled;
+ public uint PatternTableAddress;
+ public uint SpriteTableAddress;
+ public uint VRAMIncrement;
+
+ /* PPUMASK register */
+ public bool GrayscaleEnabled;
+ public bool DrawLeftBackground;
+ public bool DrawLeftSprites;
+ public bool DrawBackground;
+ public bool DrawSprites;
+ // Flipped for PAL/Dendy
+ public bool EmphasizeRed;
+ public bool EmphasizeGreen;
+ public bool EmphasizeBlue;
+
+ /* PPUSTATUS register */
+ public bool VBlankStarted;
+ public bool Sprite0Hit;
+ public bool SpriteOverflow;
+ public bool AddressLatch;
+
+ /* PPUADDR register */
+ private uint _busAddress;
+ public uint BusAddress
+ {
+ get => _busAddress;
+ set => _busAddress = value & 0x3FFF;
+ }
+
+ /* PPUDATA register */
+ public uint BusData;
+
+ /* OAMADDR register */
+ private uint _oamAddress;
+ public uint OAMAddress
+ {
+ get => _oamAddress;
+ set => _oamAddress = value & 0xFF;
+ }
+
+ /* PPUSCROLL registers */
+ [Obsolete]
+ public uint ScrollX;
+ [Obsolete]
+ public uint ScrollY;
+
+ public bool RenderingEnabled => DrawBackground || DrawSprites;
+ }
+
+ public PPUFlags F = new PPUFlags();
+
+ private uint _v;
+ public uint V
+ {
+ get => _v;
+ set => _v = value & 0x7FFF;
+ }
+ public uint T, X;
+
+ public uint CoarseX => V & 0x1F;
+
+ public uint CoarseY => (V >> 5) & 0x1F;
+
+ public uint FineY => (V >> 12) & 0x7;
+
+ public void ReloadScrollX() => V = (V & 0xFBE0) | (T & 0x041F);
+
+ public void ReloadScrollY() => V = (V & 0x841F) | (T & 0x7BE0);
+
+ public void IncrementScrollX()
+ {
+ if ((V & 0x001F) == 31) // if coarse X == 31
+ {
+ V &= ~0x001Fu; // coarse X = 0
+ V ^= 0x0400; // switch horizontal nametable
+ }
+ else
+ {
+ V += 1; // increment coarse X
+ }
+ }
+
+ public void IncrementScrollY()
+ {
+ if ((V & 0x7000) != 0x7000) // if fine Y < 7
+ {
+ V += 0x1000; // increment fine Y
+ }
+ else
+ {
+ V &= ~0x7000u; // fine Y = 0
+
+ uint y = (V & 0x03E0) >> 5; // let y = coarse Y
+ if (y == 29)
+ {
+ y = 0; // coarse Y = 0
+ V ^= 0x0800;
+ }
+ // switch vertical nametable
+ else if (y == 31)
+ {
+ y = 0; // coarse Y = 0, nametable not switched
+ }
+ else
+ {
+ y += 1; // increment coarse Y
+ }
+
+ V = (V & ~0x03E0u) | (y << 5); // put coarse Y back into v
+ }
+ }
+
+ public uint PPUCTRL
+ {
+ set
+ {
+ F.NMIEnabled = (value & 0x80) > 0;
+ F.IsMaster = (value & 0x40) > 0;
+ F.TallSpritesEnabled = (value & 0x20) > 0;
+ F.PatternTableAddress = (value & 0x10) > 0 ? 0x1000u : 0x0000;
+ F.SpriteTableAddress = (value & 0x08) > 0 ? 0x1000u : 0x0000;
+ F.VRAMIncrement = (value & 0x04) > 0 ? 32u : 1;
+ // yyy NN YYYYY XXXXX
+ // ||| || ||||| +++++--coarse X scroll
+ // ||| || +++++--------coarse Y scroll
+ // ||| ++--------------nametable select
+ // +++-----------------fine Y scroll
+ T = (T & 0xF3FF) | ((value & 0x3) << 10); // Bits 10-11 hold the base address of the nametable minus $2000
+ }
+ }
+
+ public uint PPUMASK
+ {
+ set
+ {
+ F.GrayscaleEnabled = (value & 0x1) > 0;
+ F.DrawLeftBackground = (value & 0x2) > 0;
+ F.DrawLeftSprites = (value & 0x4) > 0;
+ F.DrawBackground = (value & 0x8) > 0;
+ F.DrawSprites = (value & 0x10) > 0;
+ F.EmphasizeRed = (value & 0x20) > 0;
+ F.EmphasizeGreen = (value & 0x40) > 0;
+ F.EmphasizeBlue = (value & 0x80) > 0;
+ }
+ }
+
+ /** $2002 **/
+ public uint PPUSTATUS
+ {
+ get
+ {
+ F.AddressLatch = false;
+ var ret = (F.VBlankStarted.AsByte() << 7) |
+ (F.Sprite0Hit.AsByte() << 6) |
+ (F.SpriteOverflow.AsByte() << 5) |
+ (_lastWrittenRegister & 0x1F);
+ F.VBlankStarted = false;
+ return (uint)ret;
+ }
+ }
+
+ /** $2006 **/
+ public uint PPUADDR
+ {
+ set
+ {
+ if (F.AddressLatch)
+ {
+ T = (T & 0xFF00) | value;
+ F.BusAddress = T;
+ V = T;
+ }
+ else
+ {
+ T = (T & 0x80FF) | ((value & 0x3F) << 8);
+ }
+
+ F.AddressLatch ^= true;
+ }
+ }
+
+ /** $2005 **/
+ public uint PPUSCROLL
+ {
+ set
+ {
+ if (F.AddressLatch)
+ {
+ F.ScrollY = value;
+ T = (T & 0x8FFF) | ((value & 0x7) << 12);
+ T = (T & 0xFC1F) | (value & 0xF8) << 2;
+ }
+ else
+ {
+ F.ScrollX = value;
+ X = value & 0x7;
+ T = (T & 0xFFE0) | (value >> 3);
+ }
+
+ F.AddressLatch ^= true;
+ }
+ }
+
+ private uint _readBuffer;
+ public uint PPUDATA
+ {
+ get
+ {
+ uint ret = ReadByte(F.BusAddress);
+ if (F.BusAddress < 0x3F00)
+ {
+ uint temp = _readBuffer;
+ _readBuffer = ret;
+ ret = temp;
+ }
+ else
+ {
+ // Palette read should also read VRAM into read buffer
+ _readBuffer = ReadByte(F.BusAddress - 0x1000);
+ }
+
+ F.BusAddress += F.VRAMIncrement;
+ return ret;
+ }
+ set
+ {
+ F.BusData = value;
+ WriteByte(F.BusAddress, value);
+ F.BusAddress += F.VRAMIncrement;
+ }
+ }
+
+ public uint OAMADDR
+ {
+ get => F.OAMAddress;
+ set => F.OAMAddress = value;
+ }
+
+ public uint OAMDATA
+ {
+ get => _oam[F.OAMAddress];
+ set
+ {
+ _oam[F.OAMAddress] = (byte)value;
+ F.OAMAddress++;
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Registers.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Registers.cs.meta
new file mode 100644
index 0000000..24948ac
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.Registers.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0db554be3f472c940bde6ce34fc9ed7e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.cs
new file mode 100644
index 0000000..bd1fdb2
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.cs
@@ -0,0 +1,10 @@
+namespace AxibugEmuOnline.Client.UNES
+{
+ public sealed partial class PPU : Addressable
+ {
+ public PPU(Emulator emulator) : base(emulator, 0x3FFF)
+ {
+ InitializeMemoryMap();
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.cs.meta
new file mode 100644
index 0000000..5006815
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/PPU.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bd14757720b242e4b92425c9d6aae16a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/Utility.cs b/AxibugEmuOnline.Client/Assets/Runtime/Core/Utility.cs
new file mode 100644
index 0000000..0455b41
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/Utility.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ public static class Utility
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte AsByte(this bool to)
+ {
+ unsafe
+ {
+ var result = *((byte*) &to);
+ return result;
+ }
+ }
+
+ public static void Fill(this T[] arr, T value)
+ {
+ for (var i = 0; i < arr.Length; i++)
+ {
+ arr[i] = value;
+ }
+ }
+
+ public static void Map(this IEnumerable enumerator, Action go)
+ {
+ foreach (var e in enumerator)
+ {
+ go(e);
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Core/Utility.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Core/Utility.cs.meta
new file mode 100644
index 0000000..2d1f3ff
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Core/Utility.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b326828d1b42abc419d9fe125eed8205
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Input.meta b/AxibugEmuOnline.Client/Assets/Runtime/Input.meta
new file mode 100644
index 0000000..9b5bc16
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Input.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1d2ce2c96b7d7ad4cb099e5ce0ea885d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Input/BaseInput.cs b/AxibugEmuOnline.Client/Assets/Runtime/Input/BaseInput.cs
new file mode 100644
index 0000000..1a96398
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Input/BaseInput.cs
@@ -0,0 +1,11 @@
+using System;
+using UnityEngine;
+
+namespace AxibugEmuOnline.Client.UNES.Input
+{
+ public abstract class BaseInput
+ {
+ public abstract void HandlerKeyDown(Action onKeyDown);
+ public abstract void HandlerKeyUp(Action onKeyUp);
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Input/BaseInput.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Input/BaseInput.cs.meta
new file mode 100644
index 0000000..fefd734
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Input/BaseInput.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b60ded3f5a662a14a8a50795bae8121b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Input/DefaultInput.cs b/AxibugEmuOnline.Client/Assets/Runtime/Input/DefaultInput.cs
new file mode 100644
index 0000000..43cbb96
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Input/DefaultInput.cs
@@ -0,0 +1,49 @@
+using System;
+using UnityEngine;
+
+namespace AxibugEmuOnline.Client.UNES.Input
+{
+ public class DefaultInput : BaseInput
+ {
+ private KeyCode[] _keyCodes
+ {
+ get
+ {
+ if (_keyCodeCache == null)
+ {
+ var array = Enum.GetValues(typeof(KeyCode));
+ _keyCodeCache = new KeyCode[array.Length];
+ for (var i = 0; i < array.Length; i++)
+ {
+ _keyCodeCache[i] = (KeyCode)array.GetValue(i);
+ }
+ }
+
+ return _keyCodeCache;
+ }
+ }
+ private KeyCode[] _keyCodeCache;
+
+ public override void HandlerKeyDown(Action onKeyDown)
+ {
+ foreach (var keyCode in _keyCodes)
+ {
+ if (UnityEngine.Input.GetKeyDown(keyCode))
+ {
+ onKeyDown(keyCode);
+ }
+ }
+ }
+
+ public override void HandlerKeyUp(Action onKeyUp)
+ {
+ foreach (var keyCode in _keyCodes)
+ {
+ if (UnityEngine.Input.GetKeyUp(keyCode))
+ {
+ onKeyUp(keyCode);
+ }
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Input/DefaultInput.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Input/DefaultInput.cs.meta
new file mode 100644
index 0000000..01b9f33
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Input/DefaultInput.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 028fda8a78366f347b421062b3604d20
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper.meta
new file mode 100644
index 0000000..f05ed2d
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d22acc98b0a7fa042bb19b9894899fb7
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/AxROM.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/AxROM.cs
new file mode 100644
index 0000000..76e4b7b
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/AxROM.cs
@@ -0,0 +1,26 @@
+using static AxibugEmuOnline.Client.UNES.Cartridge.VRAMMirroringMode;
+
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(7)]
+ public class AxROM : BaseMapper
+ {
+ protected int _bankOffset;
+ private readonly Cartridge.VRAMMirroringMode[] _mirroringModes = { Lower, Upper };
+
+ public AxROM(Emulator emulator) : base(emulator)
+ {
+ _emulator.Cartridge.MirroringMode = _mirroringModes[0];
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ cpu.MapReadHandler(0x8000, 0xFFFF, address => _prgROM[_bankOffset + (address - 0x8000)]);
+ cpu.MapWriteHandler(0x8000, 0xFFFF, (address, val) =>
+ {
+ _bankOffset = (val & 0x7) * 0x8000;
+ _emulator.Cartridge.MirroringMode = _mirroringModes[(val >> 4) & 0x1];
+ });
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/AxROM.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/AxROM.cs.meta
new file mode 100644
index 0000000..67ab997
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/AxROM.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 632b907c0f30abb47b218545e40ff06d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/BaseMapper.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/BaseMapper.cs
new file mode 100644
index 0000000..05d3e3f
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/BaseMapper.cs
@@ -0,0 +1,67 @@
+using System;
+using System.IO;
+
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public class MapperDef : Attribute
+ {
+ public int Id;
+ public string Name;
+ public string Description;
+
+ public MapperDef()
+ {
+
+ }
+
+ public MapperDef(int id)
+ {
+ Id = id;
+ }
+ }
+
+ public abstract class BaseMapper
+ {
+ protected readonly Emulator _emulator;
+ protected readonly byte[] _prgROM;
+ protected readonly byte[] _prgRAM = new byte[0x2000];
+ protected readonly byte[] _chrROM;
+ protected readonly uint _lastBankOffset;
+
+ protected BaseMapper(Emulator emulator)
+ {
+ _emulator = emulator;
+ var cart = emulator.Cartridge;
+ _prgROM = cart.PRGROM;
+ _chrROM = cart.CHRROM;
+ _lastBankOffset = (uint) _prgROM.Length - 0x4000;
+ }
+
+ public virtual void InitializeMemoryMap(CPU cpu)
+ {
+
+ }
+
+ public virtual void InitializeMemoryMap(PPU ppu)
+ {
+ ppu.MapReadHandler(0x0000, 0x1FFF, address => _chrROM[address]);
+ ppu.MapWriteHandler(0x0000, 0x1FFF, (address, val) => _chrROM[address] = val);
+ }
+
+ public virtual void ProcessCycle(int scanLine, int cycle)
+ {
+
+ }
+
+ public virtual byte[] GetSaveData()
+ {
+ return _prgRAM;
+ }
+
+ public virtual void LoadSaveData(byte[] saveData)
+ {
+ Array.Copy(saveData, _prgRAM, saveData.Length);
+ }
+ }
+}
\ No newline at end of file
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/BaseMapper.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/BaseMapper.cs.meta
new file mode 100644
index 0000000..6e44d0d
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/BaseMapper.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a468573a1ef266542a055638aeef33cb
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/CNROM.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/CNROM.cs
new file mode 100644
index 0000000..0ac1eda
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/CNROM.cs
@@ -0,0 +1,32 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(3)]
+ public class CNROM : BaseMapper
+ {
+ protected int _bankOffset;
+
+ public CNROM(Emulator emulator) : base(emulator)
+ {
+ }
+
+ public override void InitializeMemoryMap(PPU ppu)
+ {
+ ppu.MapReadHandler(0x0000, 0x1FFF, address => _chrROM[_bankOffset + address]);
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ if (_prgROM.Length == 0x8000)
+ {
+ cpu.MapReadHandler(0x8000, 0xFFFF, address => _prgROM[address - 0x8000]);
+ }
+ else
+ {
+ cpu.MapReadHandler(0x8000, 0xBFFF, address => _prgROM[address - 0x8000]);
+ cpu.MapReadHandler(0xC000, 0xFFFF, address => _prgROM[address - 0xC000]);
+ }
+
+ cpu.MapWriteHandler(0x8000, 0xFFFF, (address, val) => _bankOffset = (val & 0x3) * 0x2000);
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/CNROM.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/CNROM.cs.meta
new file mode 100644
index 0000000..15d040c
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/CNROM.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fdaa43625af49e54aacf69d2ed92544f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Camerica.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Camerica.cs
new file mode 100644
index 0000000..8f0cf99
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Camerica.cs
@@ -0,0 +1,27 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(71)]
+ public class Camerica : BaseMapper
+ {
+ protected int _prgBankOffset;
+
+ public Camerica(Emulator emulator) : base(emulator)
+ {
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ cpu.MapReadHandler(0x8000, 0xBFFF, address => _prgROM[_prgBankOffset + (address - 0x8000)]);
+ cpu.MapReadHandler(0xC000, 0xFFFF, address => _prgROM[_prgROM.Length - 0x4000 + (address - 0xC000)]);
+
+ // Actually starts at 0x8000, but use 0x9000 for compatibility w/o submapper
+ cpu.MapWriteHandler(0x9000, 0x9FFF, (address, val) =>
+ {
+ // TODO: Fire Hawk mirroring
+ });
+
+ // The number of bits available vary: 4 for the BF9093, 3 for the BF9097, and 2 for the BF9096.
+ cpu.MapWriteHandler(0xC000, 0xFFFF, (address, val) => _prgBankOffset = (val & 0xF) * 0x4000 % _prgROM.Length);
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Camerica.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Camerica.cs.meta
new file mode 100644
index 0000000..2d735de
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Camerica.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a879b12017bd97949a00317043b96f54
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/ColorDreams.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/ColorDreams.cs
new file mode 100644
index 0000000..fae07ee
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/ColorDreams.cs
@@ -0,0 +1,29 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(11)]
+ public class ColorDreams : BaseMapper
+ {
+ protected int _prgBankOffset;
+ protected int _chrBankOffset;
+
+ public ColorDreams(Emulator emulator) : base(emulator)
+ {
+ }
+
+ public override void InitializeMemoryMap(PPU ppu)
+ {
+ ppu.MapReadHandler(0x0000, 0x1FFF, address => _chrROM[_chrBankOffset + address]);
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ cpu.MapReadHandler(0x8000, 0xFFFF, address => _prgROM[_prgBankOffset + (address - 0x8000)]);
+
+ cpu.MapWriteHandler(0x8000, 0xFFFF, (address, val) =>
+ {
+ _prgBankOffset = (val & 0x3) * 0x8000;
+ _chrBankOffset = (val >> 4) * 0x2000;
+ });
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/ColorDreams.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/ColorDreams.cs.meta
new file mode 100644
index 0000000..172a6aa
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/ColorDreams.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 50a4c8ff195db574585c43ba079d8195
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/DxROM.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/DxROM.cs
new file mode 100644
index 0000000..b4f8c62
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/DxROM.cs
@@ -0,0 +1,39 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(206)]
+ public class DxROM : MMC3
+ {
+ public DxROM(Emulator emulator) : base(emulator)
+ {
+ _prgBankingMode = PRGBankingMode.SwitchFix;
+ _chrBankingMode = CHRBankingMode.TwoFour;
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ cpu.MapReadHandler(0x8000, 0xFFFF, address => _prgROM[_prgBankOffsets[(address - 0x8000) / 0x2000] + address % 0x2000]);
+
+ cpu.MapWriteHandler(0x8000, 0x9FFF, (address, val) =>
+ {
+ if ((address & 0x1) == 0)
+ {
+ _currentBank = val & 0x7u;
+ }
+ else
+ {
+ if (_currentBank <= 1) val &= 0x1F;
+ else if (_currentBank <= 5) val &= 0x3F;
+ else val &= 0xF;
+
+ _banks[_currentBank] = val;
+ UpdateOffsets();
+ }
+ });
+ }
+
+ public override void InitializeMemoryMap(PPU ppu)
+ {
+ ppu.MapReadHandler(0x0000, 0x1FFF, address => _chrROM[_chrBankOffsets[address / 0x400] + address % 0x400]);
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/DxROM.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/DxROM.cs.meta
new file mode 100644
index 0000000..dd2b1c3
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/DxROM.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b8ae98e170ab70143bc7284dc01b9627
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/GxROM.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/GxROM.cs
new file mode 100644
index 0000000..92a063a
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/GxROM.cs
@@ -0,0 +1,29 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(66)]
+ public class GxROM : BaseMapper
+ {
+ protected int _prgBankOffset;
+ protected int _chrBankOffset;
+
+ public GxROM(Emulator emulator) : base(emulator)
+ {
+ }
+
+ public override void InitializeMemoryMap(PPU ppu)
+ {
+ ppu.MapReadHandler(0x0000, 0x1FFF, address => _chrROM[_chrBankOffset + address]);
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ cpu.MapReadHandler(0x8000, 0xFFFF, address => _prgROM[_prgBankOffset + (address - 0x8000)]);
+
+ cpu.MapWriteHandler(0x8000, 0xFFFF, (address, val) =>
+ {
+ _prgBankOffset = ((val >> 4) & 0x3) * 0x8000;
+ _chrBankOffset = (val & 0x3) * 0x2000;
+ });
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/GxROM.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/GxROM.cs.meta
new file mode 100644
index 0000000..2810704
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/GxROM.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9a06e1a753215b64886b90ecde825cec
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Jaleco.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Jaleco.cs
new file mode 100644
index 0000000..b85b047
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Jaleco.cs
@@ -0,0 +1,29 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(140)]
+ public class Jaleco : BaseMapper
+ {
+ protected int _prgBankOffset;
+ protected int _chrBankOffset;
+
+ public Jaleco(Emulator emulator) : base(emulator)
+ {
+ }
+
+ public override void InitializeMemoryMap(PPU ppu)
+ {
+ ppu.MapReadHandler(0x0000, 0x1FFF, address => _chrROM[_chrBankOffset + address]);
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ cpu.MapReadHandler(0x8000, 0xFFFF, address => _prgROM[_prgBankOffset + (address - 0x8000)]);
+
+ cpu.MapWriteHandler(0x6000, 0x7FFF, (address, val) =>
+ {
+ _prgBankOffset = ((val >> 4) & 0x3) * 0x8000;
+ _chrBankOffset = (val & 0x3) * 0x2000;
+ });
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Jaleco.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Jaleco.cs.meta
new file mode 100644
index 0000000..836c62f
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Jaleco.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ac2f62d216cf70643919c921900c6e70
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC1.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC1.cs
new file mode 100644
index 0000000..a420edf
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC1.cs
@@ -0,0 +1,170 @@
+using static AxibugEmuOnline.Client.UNES.Cartridge.VRAMMirroringMode;
+
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(1)]
+ public class MMC1 : BaseMapper
+ {
+ // TODO: are MMC1 and MMC1A even different chip types?
+ public enum ChipType { MMC1, MMC1A, MMC1B, MMC1C }
+ public enum CHRBankingMode { Single, Double }
+ public enum PRGBankingMode { Switch32Kb, Switch16KbFixFirst, Switch16KbFixLast }
+
+ private readonly Cartridge.VRAMMirroringMode[] _mirroringModes = { Lower, Upper, Vertical, Horizontal };
+
+ private readonly ChipType _type;
+ private CHRBankingMode _chrBankingMode;
+ private PRGBankingMode _prgBankingMode;
+
+ private uint _serialData;
+ private int _serialPos;
+
+ private uint _control;
+
+ private readonly uint[] _chrBankOffsets = new uint[2];
+ private readonly uint[] _chrBanks = new uint[2];
+
+ private readonly uint[] _prgBankOffsets = new uint[2];
+ private uint _prgBank;
+
+ private bool _prgRAMEnabled;
+
+ private uint? _lastWritePC;
+
+ public MMC1(Emulator emulator) : this(emulator, ChipType.MMC1B)
+ {
+
+ }
+
+ public MMC1(Emulator emulator, ChipType chipType) : base(emulator)
+ {
+ _type = chipType;
+ if (chipType == ChipType.MMC1B) _prgRAMEnabled = true;
+ UpdateControl(0x0F);
+ _emulator.Cartridge.MirroringMode = Horizontal;
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ cpu.MapReadHandler(0x6000, 0x7FFF, address => _prgRAM[address - 0x6000]);
+ cpu.MapReadHandler(0x8000, 0xFFFF, address => _prgROM[_prgBankOffsets[(address - 0x8000) / 0x4000] + address % 0x4000]);
+
+ cpu.MapWriteHandler(0x6000, 0x7FFF, (address, val) =>
+ {
+ // PRG RAM is always enabled on MMC1A
+ if (_type == ChipType.MMC1A || _prgRAMEnabled)
+ _prgRAM[address - 0x6000] = val;
+ });
+
+ cpu.MapWriteHandler(0x8000, 0xFFFF, (address, val) =>
+ {
+ // Explicitly ignore the second write happening on consecutive cycles
+ // of an RMW instruction
+ var cycle = _emulator.CPU.PC;
+ if (cycle == _lastWritePC)
+ return;
+ _lastWritePC = cycle;
+
+ if ((val & 0x80) > 0)
+ {
+ _serialData = 0;
+ _serialPos = 0;
+ UpdateControl(_control | 0x0C);
+ }
+ else
+ {
+ _serialData |= (uint)((val & 0x1) << _serialPos);
+ _serialPos++;
+
+ if (_serialPos == 5)
+ {
+ // Address is incompletely decoded
+ address &= 0x6000;
+ if (address == 0x0000)
+ UpdateControl(_serialData);
+ else if (address == 0x2000)
+ UpdateCHRBank(0, _serialData);
+ else if (address == 0x4000)
+ UpdateCHRBank(1, _serialData);
+ else if (address == 0x6000)
+ UpdatePRGBank(_serialData);
+
+ _serialData = 0;
+ _serialPos = 0;
+ }
+ }
+ });
+ }
+
+ public override void InitializeMemoryMap(PPU ppu)
+ {
+ ppu.MapReadHandler(0x0000, 0x1FFF, address => _chrROM[_chrBankOffsets[address / 0x1000] + address % 0x1000]);
+ ppu.MapWriteHandler(0x0000, 0x1FFF, (address, val) => _chrROM[_chrBankOffsets[address / 0x1000] + address % 0x1000] = val);
+ }
+
+ private void UpdateControl(uint value)
+ {
+ _control = value;
+
+ _emulator.Cartridge.MirroringMode = _mirroringModes[value & 0x3];
+
+ _chrBankingMode = (CHRBankingMode)((value >> 4) & 0x1);
+
+ var prgMode = (value >> 2) & 0x3;
+ // Both 0 and 1 are 32Kb switch
+ if (prgMode == 0) prgMode = 1;
+ _prgBankingMode = (PRGBankingMode)(prgMode - 1);
+
+ UpdateCHRBank(1, _chrBanks[1]);
+ UpdateCHRBank(0, _chrBanks[0]);
+ UpdatePRGBank(_prgBank);
+ }
+
+ private void UpdatePRGBank(uint value)
+ {
+ _prgBank = value;
+
+ _prgRAMEnabled = (value & 0x10) == 0;
+ value &= 0xF;
+
+ switch (_prgBankingMode)
+ {
+ case PRGBankingMode.Switch32Kb:
+ value >>= 1;
+ value *= 0x4000;
+ _prgBankOffsets[0] = value;
+ _prgBankOffsets[1] = value + 0x4000;
+ break;
+ case PRGBankingMode.Switch16KbFixFirst:
+ _prgBankOffsets[0] = 0;
+ _prgBankOffsets[1] = value * 0x4000;
+ break;
+ case PRGBankingMode.Switch16KbFixLast:
+ _prgBankOffsets[0] = value * 0x4000;
+ _prgBankOffsets[1] = _lastBankOffset;
+ break;
+ }
+ }
+
+ private void UpdateCHRBank(uint bank, uint value)
+ {
+ _chrBanks[bank] = value;
+
+ // TODO FIXME: I feel like this branch should only be taken
+ // when bank == 0, but this breaks Final Fantasy
+ // When can banking mode change without UpdateCHRBank being called?
+ if (_chrBankingMode == CHRBankingMode.Single)
+ {
+ value = _chrBanks[0];
+ value >>= 1;
+ value *= 0x1000;
+ _chrBankOffsets[0] = value;
+ _chrBankOffsets[1] = value + 0x1000;
+ }
+ else
+ {
+ _chrBankOffsets[bank] = value * 0x1000;
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC1.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC1.cs.meta
new file mode 100644
index 0000000..4c02868
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC1.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d3ab06f7819df254491a7afa6a82bb43
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC2.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC2.cs
new file mode 100644
index 0000000..2aa4a06
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC2.cs
@@ -0,0 +1,33 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(Id = 9, Description = "Mike Tyson's Punch-Out!!")]
+ public class MMC2 : MMC4
+ {
+ public MMC2(Emulator emulator) : base(emulator)
+ {
+
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ base.InitializeMemoryMap(cpu);
+
+ cpu.MapReadHandler(0x8000, 0xBFFF, address => _prgROM[_prgBankOffset + (address - 0x8000)]);
+ cpu.MapReadHandler(0xA000, 0xFFFF, address => _prgROM[_prgROM.Length - 0x4000 - 0x2000 + (address - 0xA000)]);
+
+ cpu.MapWriteHandler(0xA000, 0xAFFF, (address, val) => _prgBankOffset = (val & 0xF) * 0x2000);
+ }
+
+ protected override void GetLatch(uint address, out uint latch, out bool? on)
+ {
+ base.GetLatch(address, out latch, out on);
+
+ // For MMC2, only 0xFD8 and 0xFE8 trigger the latch,
+ // not the whole range like in MMC4
+ if (latch == 0 && (address & 0x3) != 0)
+ {
+ on = null;
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC2.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC2.cs.meta
new file mode 100644
index 0000000..3270f4a
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC2.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 522a51dcf1b92d2498013b4d6f9accb0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC3.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC3.cs
new file mode 100644
index 0000000..ff9bc22
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC3.cs
@@ -0,0 +1,182 @@
+using static AxibugEmuOnline.Client.UNES.Cartridge.VRAMMirroringMode;
+
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(4)]
+ public class MMC3 : BaseMapper
+ {
+ // Different PRG RAM write/enable controls
+ public enum ChipType { MMC3, MMC6 }
+ public enum CHRBankingMode { TwoFour, FourTwo }
+ public enum PRGBankingMode { SwitchFix, FixSwitch }
+
+ private readonly Cartridge.VRAMMirroringMode[] _mirroringModes = { Vertical, Horizontal };
+
+ private readonly ChipType _type;
+ protected CHRBankingMode _chrBankingMode;
+ protected PRGBankingMode _prgBankingMode;
+
+
+ protected readonly uint[] _chrBankOffsets = new uint[8];
+ protected uint[] _prgBankOffsets;
+ protected readonly uint[] _banks = new uint[8];
+ protected uint _currentBank;
+
+ private uint _irqReloadValue;
+ private uint _irqCounter;
+ protected bool _irqEnabled;
+
+ private bool _prgRAMEnabled;
+
+ public MMC3(Emulator emulator) : this(emulator, ChipType.MMC3)
+ {
+
+ }
+
+ public MMC3(Emulator emulator, ChipType chipType) : base(emulator)
+ {
+ _type = chipType;
+ _prgBankOffsets = new uint[] { 0, 0x2000, _lastBankOffset, _lastBankOffset + 0x2000 };
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ cpu.MapReadHandler(0x6000, 0x7FFF, address => _prgRAM[address - 0x6000]);
+ cpu.MapReadHandler(0x8000, 0xFFFF, address => _prgROM[_prgBankOffsets[(address - 0x8000) / 0x2000] + address % 0x2000]);
+
+ cpu.MapWriteHandler(0x6000, 0xFFFF, WriteByte);
+ }
+
+ public override void InitializeMemoryMap(PPU ppu)
+ {
+ ppu.MapReadHandler(0x0000, 0x1FFF, address => _chrROM[_chrBankOffsets[address / 0x400] + address % 0x400]);
+ ppu.MapWriteHandler(0x0000, 0x1FFF, (address, val) => _chrROM[_chrBankOffsets[address / 0x400] + address % 0x400] = val);
+ }
+
+ public override void ProcessCycle(int scanLine, int cycle)
+ {
+ if (_emulator.PPU.F.RenderingEnabled && cycle == 260 && (0 <= scanLine && scanLine < 240 || scanLine == -1))
+ {
+ if (_irqCounter == 0)
+ {
+ _irqCounter = _irqReloadValue;
+ }
+ else
+ {
+ _irqCounter--;
+ if (_irqEnabled && _irqCounter == 0)
+ {
+ _emulator.CPU.TriggerInterrupt(CPU.InterruptType.IRQ);
+ }
+ }
+ }
+ }
+
+ protected void WriteByte(uint addr, byte value)
+ {
+ bool even = (addr & 0x1) == 0;
+
+ if (addr < 0x8000)
+ {
+ if (_prgRAMEnabled)
+ {
+ _prgRAM[addr - 0x6000] = value;
+ }
+ }
+ else if (addr < 0xA000)
+ {
+ if (even)
+ {
+ _currentBank = value & 0x7u;
+ _prgBankingMode = (PRGBankingMode)((value >> 6) & 0x1);
+ _chrBankingMode = (CHRBankingMode)((value >> 7) & 0x1);
+ }
+ else
+ {
+ _banks[_currentBank] = value;
+ }
+
+ UpdateOffsets();
+ }
+ else if (addr < 0xC000)
+ {
+ if (even)
+ {
+ _emulator.Cartridge.MirroringMode = _mirroringModes[value & 0x1];
+ }
+ else
+ {
+ _prgRAMEnabled = (value & 0xC0) == 0x80;
+ }
+ }
+ else if (addr < 0xE000)
+ {
+ if (even)
+ {
+ _irqReloadValue = value;
+ }
+ else
+ {
+ _irqCounter = 0;
+ }
+ }
+ else
+ {
+ _irqEnabled = !even;
+ }
+ }
+
+ protected void UpdateOffsets()
+ {
+ switch (_prgBankingMode)
+ {
+ case PRGBankingMode.SwitchFix:
+ _prgBankOffsets[0] = _banks[6] * 0x2000;
+ _prgBankOffsets[1] = _banks[7] * 0x2000;
+ _prgBankOffsets[2] = _lastBankOffset;
+ _prgBankOffsets[3] = _lastBankOffset + 0x2000;
+ break;
+ case PRGBankingMode.FixSwitch:
+ _prgBankOffsets[0] = _lastBankOffset;
+ _prgBankOffsets[1] = _banks[7] * 0x2000;
+ _prgBankOffsets[2] = _banks[6] * 0x2000;
+ _prgBankOffsets[3] = _lastBankOffset + 0x2000;
+ break;
+ }
+
+ switch (_chrBankingMode)
+ {
+ case CHRBankingMode.TwoFour:
+ _chrBankOffsets[0] = _banks[0] & 0xFE;
+ _chrBankOffsets[1] = _banks[0] | 0x01;
+ _chrBankOffsets[2] = _banks[1] & 0xFE;
+ _chrBankOffsets[3] = _banks[1] | 0x01;
+ _chrBankOffsets[4] = _banks[2];
+ _chrBankOffsets[5] = _banks[3];
+ _chrBankOffsets[6] = _banks[4];
+ _chrBankOffsets[7] = _banks[5];
+ break;
+ case CHRBankingMode.FourTwo:
+ _chrBankOffsets[0] = _banks[2];
+ _chrBankOffsets[1] = _banks[3];
+ _chrBankOffsets[2] = _banks[4];
+ _chrBankOffsets[3] = _banks[5];
+ _chrBankOffsets[4] = _banks[0] & 0xFE;
+ _chrBankOffsets[5] = _banks[0] | 0x01;
+ _chrBankOffsets[6] = _banks[1] & 0xFE;
+ _chrBankOffsets[7] = _banks[1] | 0x01;
+ break;
+ }
+
+ for (var i = 0; i < _prgBankOffsets.Length; i++)
+ {
+ _prgBankOffsets[i] %= (uint)_prgROM.Length;
+ }
+
+ for (var i = 0; i < _chrBankOffsets.Length; i++)
+ {
+ _chrBankOffsets[i] = (uint) (_chrBankOffsets[i] * 0x400 % _chrROM.Length);
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC3.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC3.cs.meta
new file mode 100644
index 0000000..0adfec7
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC3.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 82fb09fbeba58ae458f58e52898fedb1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC4.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC4.cs
new file mode 100644
index 0000000..9d817e1
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC4.cs
@@ -0,0 +1,73 @@
+using static AxibugEmuOnline.Client.UNES.Cartridge.VRAMMirroringMode;
+
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(10)]
+ public class MMC4 : BaseMapper
+ {
+ protected readonly Cartridge.VRAMMirroringMode[] _mirroringModes = { Vertical, Horizontal };
+
+ protected int _prgBankOffset;
+ protected int[,] _chrBankOffsets = new int[2, 2];
+ protected bool[] _latches = new bool[2];
+
+ public MMC4(Emulator emulator) : base(emulator)
+ {
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ cpu.MapReadHandler(0x6000, 0x7FFF, address => _prgRAM[address - 0x6000]);
+ cpu.MapReadHandler(0x8000, 0xBFFF, address => _prgROM[_prgBankOffset + (address - 0x8000)]);
+ cpu.MapReadHandler(0xC000, 0xFFFF, address => _prgROM[_prgROM.Length - 0x4000 + (address - 0xC000)]);
+
+ cpu.MapWriteHandler(0x6000, 0x7FFF, (address, val) => _prgRAM[address - 0x6000] = val);
+ cpu.MapWriteHandler(0xA000, 0xAFFF, (address, val) => _prgBankOffset = (val & 0xF) * 0x4000);
+ cpu.MapWriteHandler(0xB000, 0xEFFF, (address, val) =>
+ {
+ var bank = (address - 0xB000) / 0x2000;
+ var latch = ((address & 0x1FFF) == 0).AsByte();
+ _chrBankOffsets[bank, latch] = (val & 0x1F) * 0x1000;
+ });
+
+ cpu.MapWriteHandler(0xF000, 0xFFFF, (address, val) => _emulator.Cartridge.MirroringMode = _mirroringModes[val & 0x1]);
+ }
+
+ public override void InitializeMemoryMap(PPU ppu)
+ {
+ ppu.MapReadHandler(0x0000, 0x1FFF, address =>
+ {
+ var bank = address / 0x1000;
+ var ret = _chrROM[_chrBankOffsets[bank, _latches[bank].AsByte()] + address % 0x1000];
+ if ((address & 0x08) > 0)
+ {
+ GetLatch(address, out uint latch, out bool? on);
+
+ if (on != null)
+ {
+ _latches[latch] = (bool)on;
+ }
+ }
+
+ return ret;
+ });
+ }
+
+ protected virtual void GetLatch(uint address, out uint latch, out bool? on)
+ {
+ latch = (address >> 12) & 0x1;
+ on = null;
+
+ address = (address >> 4) & 0xFF;
+
+ if (address == 0xFE)
+ {
+ on = true;
+ }
+ else if (address == 0xFD)
+ {
+ on = false;
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC4.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC4.cs.meta
new file mode 100644
index 0000000..81f2dfe
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/MMC4.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 54d8f341641616d45b20c65ec4a75ff2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper094.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper094.cs
new file mode 100644
index 0000000..4baaefe
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper094.cs
@@ -0,0 +1,17 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(Id = 94, Description = "Senjou no Ookami")]
+ public class Mapper094 : UxROM
+ {
+ public Mapper094(Emulator emulator) : base(emulator)
+ {
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ base.InitializeMemoryMap(cpu);
+
+ cpu.MapWriteHandler(0x8000, 0xFFFF, (address, val) => _bankOffset = (val & 0x1C) << 12);
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper094.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper094.cs.meta
new file mode 100644
index 0000000..70a9d00
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper094.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0abeb567769f715418ae9125bd029206
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper155.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper155.cs
new file mode 100644
index 0000000..44fb15b
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper155.cs
@@ -0,0 +1,11 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(Id = 155, Description = "MMC1A")]
+ public class Mapper155 : MMC1
+ {
+ // Mapper for games requiring MMC1A
+ public Mapper155(Emulator emulator) : base(emulator, ChipType.MMC1A)
+ {
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper155.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper155.cs.meta
new file mode 100644
index 0000000..a775951
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper155.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: dc90c4e04b12a134c884ca3431375aac
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper180.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper180.cs
new file mode 100644
index 0000000..e7a81fe
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper180.cs
@@ -0,0 +1,21 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ // Mapper used strictly for Crazy Climber; logic is slighly different
+ [MapperDef(Id = 180, Description = "Crazy Climber")]
+ public class Mapper180 : UxROM
+ {
+ public Mapper180(Emulator emulator) : base(emulator)
+ {
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ base.InitializeMemoryMap(cpu);
+
+ // $8000-$C000 is fixed to *first* bank
+ cpu.MapReadHandler(0x8000, 0xBFFF, address => _prgROM[address - 0x8000]);
+ // $C000-$FFFF is switchable, controlled the same as UxROM
+ cpu.MapReadHandler(0xC000, 0xFFFF, address => _prgROM[_bankOffset + (address - 0xC000)]);
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper180.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper180.cs.meta
new file mode 100644
index 0000000..41282e8
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Mapper180.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0ed79f8d409c2864bab9bc85848bc36a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/NROM.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/NROM.cs
new file mode 100644
index 0000000..bf7ad3d
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/NROM.cs
@@ -0,0 +1,23 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(0)]
+ public class NROM : BaseMapper
+ {
+ private readonly byte[] _addressSpace = new byte[0x2000 + 0x8000]; // Space for $2000 VRAM + $8000 PRG
+
+ public NROM(Emulator emulator) : base(emulator)
+ {
+ for (var i = 0; i < 0x8000; i++)
+ {
+ var offset = _emulator.Cartridge.PRGROMSize == 0x4000 ? i & 0xBFFF : i;
+ _addressSpace[0x2000 + i] = _prgROM[offset];
+ }
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ cpu.MapReadHandler(0x6000, 0xFFFF, address => _addressSpace[address - 0x6000]);
+ cpu.MapWriteHandler(0x6000, 0x7FFF, (address, val) => _addressSpace[address - 0x6000] = val);
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/NROM.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/NROM.cs.meta
new file mode 100644
index 0000000..f09b0e2
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/NROM.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7781217265cd36043acd57aa3a049d25
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Nina003006.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Nina003006.cs
new file mode 100644
index 0000000..d26761b
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Nina003006.cs
@@ -0,0 +1,32 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(79)]
+ public class Nina003006 : BaseMapper
+ {
+ protected int _prgBankOffset;
+ protected int _chrBankOffset;
+
+ public Nina003006(Emulator emulator) : base(emulator)
+ {
+ }
+
+ public override void InitializeMemoryMap(PPU ppu)
+ {
+ ppu.MapReadHandler(0x0000, 0x1FFF, address => _chrROM[_chrBankOffset + address]);
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ cpu.MapReadHandler(0x8000, 0xFFFF, address => _prgROM[_prgBankOffset + (address - 0x8000)]);
+
+ cpu.MapWriteHandler(0x4000, 0x5FFF, (address, val) =>
+ {
+ if ((address & 0b1110_0001_0000_0000) == 0b0100_0001_0000_0000)
+ {
+ _prgBankOffset = ((val >> 4) & 0x3) * 0x8000;
+ _chrBankOffset = (val & 0x3) * 0x2000;
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Nina003006.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Nina003006.cs.meta
new file mode 100644
index 0000000..128d6ad
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/Nina003006.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4a935d078b54a6a4b9434e3478863251
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/UxROM.cs b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/UxROM.cs
new file mode 100644
index 0000000..fa24992
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/UxROM.cs
@@ -0,0 +1,22 @@
+namespace AxibugEmuOnline.Client.UNES.Mapper
+{
+ [MapperDef(2)]
+ public class UxROM : BaseMapper
+ {
+ protected int _bankOffset;
+
+ public UxROM(Emulator emulator) : base(emulator)
+ {
+ }
+
+ public override void InitializeMemoryMap(CPU cpu)
+ {
+ cpu.MapReadHandler(0x6000, 0x7FFF, address => _prgRAM[address - 0x6000]);
+ cpu.MapReadHandler(0x8000, 0xBFFF, address => _prgROM[_bankOffset + (address - 0x8000)]);
+ cpu.MapReadHandler(0xC000, 0xFFFF, address => _prgROM[_prgROM.Length - 0x4000 + (address - 0xC000)]);
+
+ cpu.MapWriteHandler(0x6000, 0x7FFF, (address, val) => _prgRAM[address - 0x6000] = val);
+ cpu.MapWriteHandler(0x8000, 0xFFFF, (address, val) => _bankOffset = (val & 0xF) * 0x4000);
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Mapper/UxROM.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/UxROM.cs.meta
new file mode 100644
index 0000000..9b0f9dc
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Mapper/UxROM.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9fa6ddb08aec2014fada61a92af067ad
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Renderer.meta b/AxibugEmuOnline.Client/Assets/Runtime/Renderer.meta
new file mode 100644
index 0000000..ede6704
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Renderer.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ff3612d2884e82145a83b869175fe872
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Renderer/IRenderer.cs b/AxibugEmuOnline.Client/Assets/Runtime/Renderer/IRenderer.cs
new file mode 100644
index 0000000..de133db
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Renderer/IRenderer.cs
@@ -0,0 +1,13 @@
+namespace AxibugEmuOnline.Client.UNES.Renderer
+{
+ public interface IRenderer
+ {
+ string Name { get; }
+
+ void HandleRender();
+
+ void Init(UNESBehaviour nes);
+
+ void End();
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Renderer/IRenderer.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Renderer/IRenderer.cs.meta
new file mode 100644
index 0000000..5bad6e1
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Renderer/IRenderer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6e02ede35ded0294fb7f13f60387557f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Renderer/UnityRenderer.cs b/AxibugEmuOnline.Client/Assets/Runtime/Renderer/UnityRenderer.cs
new file mode 100644
index 0000000..d48923b
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Renderer/UnityRenderer.cs
@@ -0,0 +1,71 @@
+using System.Runtime.InteropServices;
+using UnityEngine;
+
+namespace AxibugEmuOnline.Client.UNES.Renderer
+{
+ public class UnityRenderer : IRenderer
+ {
+ public UNESBehaviour UnesBehaviour;
+ public RenderTexture RenderTexture;
+
+ public string Name => "Unity";
+
+ private Texture2D _drawTexture;
+ private Color[] _pixelCache;
+
+ private Texture2D _clearTexture;
+
+ public void Init(UNESBehaviour nes)
+ {
+ UnesBehaviour = nes;
+ RenderTexture = nes.RenderTexture;
+ _drawTexture = new Texture2D(UNESBehaviour.GameWidth, UNESBehaviour.GameHeight);
+ _pixelCache = new Color[UNESBehaviour.GameWidth * UNESBehaviour.GameHeight];
+
+ _clearTexture = new Texture2D(1, 1);
+ _clearTexture.SetPixel(0, 0, Color.clear);
+ _clearTexture.Apply();
+ }
+
+ public void HandleRender()
+ {
+ if (RenderTexture.filterMode != UnesBehaviour.FilterMode)
+ {
+ RenderTexture.filterMode = UnesBehaviour.FilterMode;
+ }
+
+ for (var y = 0; y < UNESBehaviour.GameHeight; y++)
+ {
+ for (var x = 0; x < UNESBehaviour.GameWidth; x++)
+ {
+ var rawIndex = UNESBehaviour.GameWidth * y + x;
+ var color = GetColor(UnesBehaviour.RawBitmap[rawIndex]);
+ var texIndex = UNESBehaviour.GameWidth * (UNESBehaviour.GameHeight - y - 1) + x;
+ _pixelCache[texIndex] = color;
+ }
+ }
+
+ _drawTexture.SetPixels(_pixelCache);
+ _drawTexture.Apply();
+
+ Graphics.Blit(_drawTexture, RenderTexture);
+ }
+
+ public Color GetColor(uint value)
+ {
+ var r = 0xFF0000 & value;
+ r >>= 16;
+ var b = 0xFF & value;
+ var g = 0xFF00 & value;
+ g >>= 8;
+ var color = new Color(r / 255f, g / 255f, b / 255f);
+ return color;
+ }
+
+ public void End()
+ {
+ Graphics.Blit(_clearTexture, RenderTexture);
+ }
+ }
+}
+
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/Renderer/UnityRenderer.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/Renderer/UnityRenderer.cs.meta
new file mode 100644
index 0000000..13e31a8
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/Renderer/UnityRenderer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1f751adeb00750a43a0d2534a2301da2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/UNESBehaviour.cs b/AxibugEmuOnline.Client/Assets/Runtime/UNESBehaviour.cs
new file mode 100644
index 0000000..ac1fc93
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/UNESBehaviour.cs
@@ -0,0 +1,281 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using UnityEngine;
+using AxibugEmuOnline.Client.UNES.Controller;
+using AxibugEmuOnline.Client.UNES.Input;
+using AxibugEmuOnline.Client.UNES.Renderer;
+using AxibugEmuOnline.Client.ClientCore;
+using AxibugEmuOnline.Client.Sample;
+using System.Collections.Generic;
+using System.Collections;
+using System.Linq;
+
+namespace AxibugEmuOnline.Client.UNES
+{
+ public class UNESBehaviour : MonoBehaviour
+ {
+ [Header("Render")]
+ public RenderTexture RenderTexture;
+ public const int GameWidth = 256;
+ public const int GameHeight = 240;
+ public FilterMode FilterMode = FilterMode.Point;
+ public bool LogicThread = true;
+
+ [Header("Input")]
+ public KeyConfig KeyConfig;
+
+ public BaseInput Input { get; set; }
+ public IRenderer Renderer { get; set; }
+ public uint[] RawBitmap { get; set; } = new uint[GameWidth * GameHeight];
+ public bool Ready { get; set; }
+ public bool GameStarted { get; set; }
+
+ private bool _rendererRunning = true;
+ private IController _controller;
+ private Emulator _emu;
+ private bool _suspended;
+ private Thread _renderThread;
+ private int _activeSpeed = 1;
+
+ public void Boot(byte[] romData)
+ {
+ InitInput();
+ InitRenderer();
+ BootCartridge(romData);
+ }
+
+ public void Boot_Obs()
+ {
+ InitInput();
+ InitRenderer();
+ BootCartridge_Obs();
+ }
+
+ public void LoadSaveData(byte[] saveData)
+ {
+ _emu?.Mapper.LoadSaveData(saveData);
+ }
+
+ public byte[] GetSaveData()
+ {
+ return _emu?.Mapper.GetSaveData();
+ }
+
+ Queue _queue = new Queue();
+
+
+ private void BootCartridge(byte[] romData)
+ {
+ _emu = new Emulator(romData, _controller);
+ if (LogicThread)
+ {
+ _renderThread = new Thread(() =>
+ {
+ GameStarted = true;
+ var s = new Stopwatch();
+ var s0 = new Stopwatch();
+ while (_rendererRunning)
+ {
+ if (_suspended)
+ {
+ Thread.Sleep(100);
+ continue;
+ }
+
+ s.Restart();
+ for (var i = 0; i < 60 && !_suspended; i++)
+ {
+ s0.Restart();
+ lock (RawBitmap)
+ {
+ _emu.PPU.ProcessFrame();
+ RawBitmap = _emu.PPU.RawBitmap;
+ //塞进发送队列
+ _queue.Enqueue(_emu.PPU.RawBitmap_paletteIdxCache);
+ }
+
+ s0.Stop();
+ Thread.Sleep(Math.Max((int)(980 / 60.0 - s0.ElapsedMilliseconds), 0) / _activeSpeed);
+ }
+
+ s.Stop();
+ }
+ });
+
+ _renderThread.Start();
+ }
+ else
+ {
+ GameStarted = true;
+ }
+ }
+
+ private void BootCartridge_Obs()
+ {
+ if (LogicThread)
+ {
+ _renderThread = new Thread(() =>
+ {
+ GameStarted = true;
+ var s = new Stopwatch();
+ var s0 = new Stopwatch();
+ while (_rendererRunning)
+ {
+ if (_suspended)
+ {
+ Thread.Sleep(100);
+ continue;
+ }
+
+ s.Restart();
+ for (var i = 0; i < 60 && !_suspended; i++)
+ {
+ s0.Restart();
+ lock (RawBitmap)
+ {
+ RawBitmap = AppAxibugEmuOnline.game.RawBitmap;
+ }
+
+ s0.Stop();
+ Thread.Sleep(Math.Max((int)(980 / 60.0 - s0.ElapsedMilliseconds), 0) / _activeSpeed);
+ }
+
+ s.Stop();
+ }
+ });
+
+ _renderThread.Start();
+ }
+ else
+ {
+ GameStarted = true;
+ }
+ }
+
+ #region Monobehaviour
+
+ public void Awake()
+ {
+ }
+
+ public void OnEnable()
+ {
+
+ }
+
+ public void OnDisable()
+ {
+ _rendererRunning = false;
+ Renderer?.End();
+ }
+
+ public void Update()
+ {
+ if (!GameStarted) return;
+ UpdateInput();
+
+ if (UNESTest.instance.PlayerP1)
+ {
+ UpdateRender();
+ }
+ else
+ {
+ UpdateRender_Obs();
+ }
+ }
+
+ #endregion
+
+ #region Input
+
+ private void InitInput()
+ {
+ _controller = new NesController(this);
+ Input = new DefaultInput();
+ }
+
+ public void UpdateInput()
+ {
+ Input.HandlerKeyDown(keyCode =>
+ {
+ switch (keyCode)
+ {
+ case KeyCode.F2:
+ _suspended = false;
+ break;
+ case KeyCode.F3:
+ _suspended = true;
+ break;
+ default:
+ _controller.PressKey(keyCode);
+ break;
+ }
+ });
+
+ Input.HandlerKeyUp(keyCode =>
+ {
+ _controller.ReleaseKey(keyCode);
+ });
+ }
+
+ #endregion
+
+ #region Render
+
+ public void UpdateRender()
+ {
+ while (_queue.Count() > 0)
+ {
+ //发送
+ AppAxibugEmuOnline.game.SendScreen(_queue.Dequeue());
+ }
+ if (!_rendererRunning) return;
+ if (_suspended) return;
+
+ if (!LogicThread)
+ {
+ _emu.PPU.ProcessFrame();
+ RawBitmap = _emu.PPU.RawBitmap;
+ Renderer.HandleRender();
+
+ }
+ else
+ {
+ lock (RawBitmap)
+ {
+ Renderer.HandleRender();
+ }
+ }
+ }
+
+ public void UpdateRender_Obs()
+ {
+ if (!_rendererRunning) return;
+ if (_suspended) return;
+
+ if (!LogicThread)
+ {
+ RawBitmap = AppAxibugEmuOnline.game.RawBitmap;
+ Renderer.HandleRender();
+ }
+ else
+ {
+ lock (RawBitmap)
+ {
+ Renderer.HandleRender();
+ }
+ }
+ }
+
+ private void InitRenderer()
+ {
+ Renderer?.End();
+
+ Renderer = new UnityRenderer();
+ Renderer.Init(this);
+ }
+
+ #endregion
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Runtime/UNESBehaviour.cs.meta b/AxibugEmuOnline.Client/Assets/Runtime/UNESBehaviour.cs.meta
new file mode 100644
index 0000000..99998ce
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Runtime/UNESBehaviour.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5948479853ec1194497c60babb6bc213
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Scene.meta b/AxibugEmuOnline.Client/Assets/Scene.meta
new file mode 100644
index 0000000..a2af5ba
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Scene.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 97287d4650544eb4daa041e4a384a06b
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Scene/AxibugEmuOnline.Client.unity b/AxibugEmuOnline.Client/Assets/Scene/AxibugEmuOnline.Client.unity
new file mode 100644
index 0000000..40d817e
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Scene/AxibugEmuOnline.Client.unity
@@ -0,0 +1,660 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!29 &1
+OcclusionCullingSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_OcclusionBakeSettings:
+ smallestOccluder: 5
+ smallestHole: 0.25
+ backfaceThreshold: 100
+ m_SceneGUID: 00000000000000000000000000000000
+ m_OcclusionCullingData: {fileID: 0}
+--- !u!104 &2
+RenderSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 9
+ m_Fog: 0
+ m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
+ m_FogMode: 3
+ m_FogDensity: 0.01
+ m_LinearFogStart: 0
+ m_LinearFogEnd: 300
+ m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
+ m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
+ m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
+ m_AmbientIntensity: 1
+ m_AmbientMode: 0
+ m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
+ m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
+ m_HaloStrength: 0.5
+ m_FlareStrength: 1
+ m_FlareFadeSpeed: 3
+ m_HaloTexture: {fileID: 0}
+ m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
+ m_DefaultReflectionMode: 0
+ m_DefaultReflectionResolution: 128
+ m_ReflectionBounces: 1
+ m_ReflectionIntensity: 1
+ m_CustomReflection: {fileID: 0}
+ m_Sun: {fileID: 0}
+ m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
+ m_UseRadianceAmbientProbe: 0
+--- !u!157 &3
+LightmapSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 12
+ m_GIWorkflowMode: 1
+ m_GISettings:
+ serializedVersion: 2
+ m_BounceScale: 1
+ m_IndirectOutputScale: 1
+ m_AlbedoBoost: 1
+ m_EnvironmentLightingMode: 0
+ m_EnableBakedLightmaps: 1
+ m_EnableRealtimeLightmaps: 0
+ m_LightmapEditorSettings:
+ serializedVersion: 12
+ m_Resolution: 2
+ m_BakeResolution: 40
+ m_AtlasSize: 1024
+ m_AO: 0
+ m_AOMaxDistance: 1
+ m_CompAOExponent: 1
+ m_CompAOExponentDirect: 0
+ m_ExtractAmbientOcclusion: 0
+ m_Padding: 2
+ m_LightmapParameters: {fileID: 0}
+ m_LightmapsBakeMode: 1
+ m_TextureCompression: 1
+ m_FinalGather: 0
+ m_FinalGatherFiltering: 1
+ m_FinalGatherRayCount: 256
+ m_ReflectionCompression: 2
+ m_MixedBakeMode: 2
+ m_BakeBackend: 1
+ m_PVRSampling: 1
+ m_PVRDirectSampleCount: 32
+ m_PVRSampleCount: 512
+ m_PVRBounces: 2
+ m_PVREnvironmentSampleCount: 256
+ m_PVREnvironmentReferencePointCount: 2048
+ m_PVRFilteringMode: 1
+ m_PVRDenoiserTypeDirect: 1
+ m_PVRDenoiserTypeIndirect: 1
+ m_PVRDenoiserTypeAO: 1
+ m_PVRFilterTypeDirect: 0
+ m_PVRFilterTypeIndirect: 0
+ m_PVRFilterTypeAO: 0
+ m_PVREnvironmentMIS: 1
+ m_PVRCulling: 1
+ m_PVRFilteringGaussRadiusDirect: 1
+ m_PVRFilteringGaussRadiusIndirect: 5
+ m_PVRFilteringGaussRadiusAO: 2
+ m_PVRFilteringAtrousPositionSigmaDirect: 0.5
+ m_PVRFilteringAtrousPositionSigmaIndirect: 2
+ m_PVRFilteringAtrousPositionSigmaAO: 1
+ m_ExportTrainingData: 0
+ m_TrainingDataDestination: TrainingData
+ m_LightProbeSampleCountMultiplier: 4
+ m_LightingDataAsset: {fileID: 0}
+ m_LightingSettings: {fileID: 0}
+--- !u!196 &4
+NavMeshSettings:
+ serializedVersion: 2
+ m_ObjectHideFlags: 0
+ m_BuildSettings:
+ serializedVersion: 2
+ agentTypeID: 0
+ agentRadius: 0.5
+ agentHeight: 2
+ agentSlope: 45
+ agentClimb: 0.4
+ ledgeDropHeight: 0
+ maxJumpAcrossDistance: 0
+ minRegionArea: 2
+ manualCellSize: 0
+ cellSize: 0.16666667
+ manualTileSize: 0
+ tileSize: 256
+ accuratePlacement: 0
+ maxJobWorkers: 0
+ preserveTilesOutsideBounds: 0
+ debug:
+ m_Flags: 0
+ m_NavMeshData: {fileID: 0}
+--- !u!1 &537454904
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 537454906}
+ - component: {fileID: 537454905}
+ m_Layer: 0
+ m_Name: Directional Light
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!108 &537454905
+Light:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 537454904}
+ m_Enabled: 1
+ serializedVersion: 10
+ m_Type: 1
+ m_Shape: 0
+ m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
+ m_Intensity: 1
+ m_Range: 10
+ m_SpotAngle: 30
+ m_InnerSpotAngle: 21.80208
+ m_CookieSize: 10
+ m_Shadows:
+ m_Type: 2
+ m_Resolution: -1
+ m_CustomResolution: -1
+ m_Strength: 1
+ m_Bias: 0.05
+ m_NormalBias: 0.4
+ m_NearPlane: 0.2
+ m_CullingMatrixOverride:
+ e00: 1
+ e01: 0
+ e02: 0
+ e03: 0
+ e10: 0
+ e11: 1
+ e12: 0
+ e13: 0
+ e20: 0
+ e21: 0
+ e22: 1
+ e23: 0
+ e30: 0
+ e31: 0
+ e32: 0
+ e33: 1
+ m_UseCullingMatrixOverride: 0
+ m_Cookie: {fileID: 0}
+ m_DrawHalo: 0
+ m_Flare: {fileID: 0}
+ m_RenderMode: 0
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingLayerMask: 1
+ m_Lightmapping: 4
+ m_LightShadowCasterMode: 0
+ m_AreaSize: {x: 1, y: 1}
+ m_BounceIntensity: 1
+ m_ColorTemperature: 6570
+ m_UseColorTemperature: 0
+ m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
+ m_UseBoundingSphereOverride: 0
+ m_UseViewFrustumForShadowCasterCull: 1
+ m_ShadowRadius: 0
+ m_ShadowAngle: 0
+--- !u!4 &537454906
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 537454904}
+ m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
+ m_LocalPosition: {x: 0, y: 3, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 1
+ m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
+--- !u!1 &589359925
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 589359928}
+ - component: {fileID: 589359927}
+ - component: {fileID: 589359926}
+ m_Layer: 0
+ m_Name: EventSystem
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!114 &589359926
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 589359925}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_HorizontalAxis: Horizontal
+ m_VerticalAxis: Vertical
+ m_SubmitButton: Submit
+ m_CancelButton: Cancel
+ m_InputActionsPerSecond: 10
+ m_RepeatDelay: 0.5
+ m_ForceModuleActive: 0
+--- !u!114 &589359927
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 589359925}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_FirstSelected: {fileID: 0}
+ m_sendNavigationEvents: 1
+ m_DragThreshold: 10
+--- !u!4 &589359928
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 589359925}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 4
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &622947734
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 622947735}
+ - component: {fileID: 622947737}
+ - component: {fileID: 622947736}
+ m_Layer: 5
+ m_Name: Game
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &622947735
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 622947734}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 1361633018}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 0}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &622947736
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 622947734}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 0
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_Texture: {fileID: 8400000, guid: 6645567e4c11d9447b1aee2406f681c5, type: 2}
+ m_UVRect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+--- !u!222 &622947737
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 622947734}
+ m_CullTransparentMesh: 0
+--- !u!1 &1355724343
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1355724346}
+ - component: {fileID: 1355724345}
+ - component: {fileID: 1355724344}
+ m_Layer: 0
+ m_Name: Main Camera
+ m_TagString: MainCamera
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!81 &1355724344
+AudioListener:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1355724343}
+ m_Enabled: 1
+--- !u!20 &1355724345
+Camera:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1355724343}
+ m_Enabled: 1
+ serializedVersion: 2
+ m_ClearFlags: 1
+ m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
+ m_projectionMatrixMode: 1
+ m_GateFitMode: 2
+ m_FOVAxisMode: 0
+ m_SensorSize: {x: 36, y: 24}
+ m_LensShift: {x: 0, y: 0}
+ m_FocalLength: 50
+ m_NormalizedViewPortRect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ near clip plane: 0.3
+ far clip plane: 1000
+ field of view: 60
+ orthographic: 0
+ orthographic size: 5
+ m_Depth: -1
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingPath: -1
+ m_TargetTexture: {fileID: 0}
+ m_TargetDisplay: 0
+ m_TargetEye: 3
+ m_HDR: 1
+ m_AllowMSAA: 1
+ m_AllowDynamicResolution: 0
+ m_ForceIntoRT: 0
+ m_OcclusionCulling: 1
+ m_StereoConvergence: 10
+ m_StereoSeparation: 0.022
+--- !u!4 &1355724346
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1355724343}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 1, z: -10}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1361633014
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1361633018}
+ - component: {fileID: 1361633017}
+ - component: {fileID: 1361633016}
+ - component: {fileID: 1361633015}
+ m_Layer: 5
+ m_Name: UI
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!114 &1361633015
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1361633014}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_IgnoreReversedGraphics: 1
+ m_BlockingObjects: 0
+ m_BlockingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+--- !u!114 &1361633016
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1361633014}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_UiScaleMode: 0
+ m_ReferencePixelsPerUnit: 100
+ m_ScaleFactor: 1
+ m_ReferenceResolution: {x: 800, y: 600}
+ m_ScreenMatchMode: 0
+ m_MatchWidthOrHeight: 0
+ m_PhysicalUnit: 3
+ m_FallbackScreenDPI: 96
+ m_DefaultSpriteDPI: 96
+ m_DynamicPixelsPerUnit: 1
+ m_PresetInfoIsWorld: 0
+--- !u!223 &1361633017
+Canvas:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1361633014}
+ m_Enabled: 1
+ serializedVersion: 3
+ m_RenderMode: 0
+ m_Camera: {fileID: 0}
+ m_PlaneDistance: 100
+ m_PixelPerfect: 0
+ m_ReceivesEvents: 1
+ m_OverrideSorting: 0
+ m_OverridePixelPerfect: 0
+ m_SortingBucketNormalizedSize: 0
+ m_AdditionalShaderChannelsFlag: 0
+ m_SortingLayerID: 0
+ m_SortingOrder: 0
+ m_TargetDisplay: 0
+--- !u!224 &1361633018
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1361633014}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 0, y: 0, z: 0}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 622947735}
+ m_Father: {fileID: 0}
+ m_RootOrder: 3
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 0, y: 0}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 0}
+ m_Pivot: {x: 0, y: 0}
+--- !u!1 &1434955706
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1434955708}
+ - component: {fileID: 1434955709}
+ - component: {fileID: 1434955707}
+ m_Layer: 0
+ m_Name: UNES
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!114 &1434955707
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1434955706}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5948479853ec1194497c60babb6bc213, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ RenderTexture: {fileID: 8400000, guid: 6645567e4c11d9447b1aee2406f681c5, type: 2}
+ FilterMode: 1
+ LogicThread: 1
+ KeyConfig:
+ Up: 119
+ Down: 115
+ Left: 97
+ Right: 100
+ A: 107
+ B: 106
+ Start: 98
+ Select: 118
+ Debug: 112
+--- !u!4 &1434955708
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1434955706}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 2
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &1434955709
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1434955706}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 8e94e6b3864b9034b89b70aa411a2894, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ RomFile: Mario
+ PlayerP1: 0
+--- !u!1 &1498586261
+GameObject:
+ m_ObjectHideFlags: 3
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1498586263}
+ - component: {fileID: 1498586262}
+ m_Layer: 0
+ m_Name: UndoProRecords
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!114 &1498586262
+MonoBehaviour:
+ m_ObjectHideFlags: 3
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1498586261}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: df9ab10aeab793d47a27405557d0b929, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+--- !u!4 &1498586263
+Transform:
+ m_ObjectHideFlags: 3
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1498586261}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 5
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
diff --git a/AxibugEmuOnline.Client/Assets/Scene/AxibugEmuOnline.Client.unity.meta b/AxibugEmuOnline.Client/Assets/Scene/AxibugEmuOnline.Client.unity.meta
new file mode 100644
index 0000000..fa040cd
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Scene/AxibugEmuOnline.Client.unity.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: eb0c18a619175384d95147898a43054b
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script.meta b/AxibugEmuOnline.Client/Assets/Script.meta
new file mode 100644
index 0000000..6d4a585
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: cb2f15a5cdbcfcc498fecad6768c5ec9
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/AppAxibugEmuOnline.cs b/AxibugEmuOnline.Client/Assets/Script/AppAxibugEmuOnline.cs
new file mode 100644
index 0000000..11c7815
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/AppAxibugEmuOnline.cs
@@ -0,0 +1,47 @@
+using AxibugEmuOnline.Client.Manager;
+using AxibugEmuOnline.Client.Network;
+using System;
+using UnityEngine;
+using static AxibugEmuOnline.Client.Manager.LogManager;
+
+namespace AxibugEmuOnline.Client.ClientCore
+{
+ public class AppAxibugEmuOnline
+ {
+ public static string TokenStr;
+ public static long RID = -1;
+ public static string IP;
+ public static int Port;
+ public static LogManager log;
+ public static NetworkHelper networkHelper;
+ public static AppLogin login;
+ public static AppChat chat;
+ public static UserDataManager user;
+ public static AppGame game;
+
+ public static void Init()
+ {
+ log = new LogManager();
+ LogManager.OnLog += OnNoSugarNetLog;
+ networkHelper = new NetworkHelper();
+ login = new AppLogin();
+ chat = new AppChat();
+ user = new UserDataManager();
+ game = new AppGame();
+ }
+
+ public static bool Connect(string IP, int port)
+ {
+ return networkHelper.Init(IP, port);
+ }
+
+ public static void Close()
+ {
+ AppAxibugEmuOnline.log.Info("停止");
+ }
+ static void OnNoSugarNetLog(int LogLevel, string msg)
+ {
+ Debug.Log("[AxibugEmuOnline]:"+msg);
+ }
+ }
+}
\ No newline at end of file
diff --git a/AxibugEmuOnline.Client/Assets/Script/AppAxibugEmuOnline.cs.meta b/AxibugEmuOnline.Client/Assets/Script/AppAxibugEmuOnline.cs.meta
new file mode 100644
index 0000000..308de77
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/AppAxibugEmuOnline.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 37a06c0960dd38d48a93b9642efa11f6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Common.meta b/AxibugEmuOnline.Client/Assets/Script/Common.meta
new file mode 100644
index 0000000..5c2638c
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Common.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 22c07079f08c63e45929f98330d60397
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Common/Helper.cs b/AxibugEmuOnline.Client/Assets/Script/Common/Helper.cs
new file mode 100644
index 0000000..f6dfde4
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Common/Helper.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace AxibugEmuOnline.Client.Common
+{
+ public static class Helper
+ {
+ public static long GetNowTimeStamp()
+ {
+ return GetTimeStamp(DateTime.Now);
+ }
+
+ ///
+ /// 获取时间戳
+ ///
+ ///
+ public static long GetTimeStamp(DateTime dt)
+ {
+ TimeSpan ts = dt - new DateTime(1970, 1, 1, 0, 0, 0, 0);
+ return Convert.ToInt64(ts.TotalSeconds);
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Script/Common/Helper.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Common/Helper.cs.meta
new file mode 100644
index 0000000..685e18e
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Common/Helper.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ee9183acb0656474293cd9015f34b254
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Common/ProtoBufHelper.cs b/AxibugEmuOnline.Client/Assets/Script/Common/ProtoBufHelper.cs
new file mode 100644
index 0000000..4ff0eab
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Common/ProtoBufHelper.cs
@@ -0,0 +1,21 @@
+using Google.Protobuf;
+using System;
+
+namespace AxibugEmuOnline.Client.Common
+{
+ public static class ProtoBufHelper
+ {
+ public static byte[] Serizlize(IMessage msg)
+ {
+ return msg.ToByteArray();
+ }
+ public static T DeSerizlize(byte[] bytes)
+ {
+ var msgType = typeof(T);
+ object msg = Activator.CreateInstance(msgType);
+ ((IMessage)msg).MergeFrom(bytes);
+ return (T)msg;
+ }
+ }
+
+}
diff --git a/AxibugEmuOnline.Client/Assets/Script/Common/ProtoBufHelper.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Common/ProtoBufHelper.cs.meta
new file mode 100644
index 0000000..e3edf51
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Common/ProtoBufHelper.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6977f12ad405a314eacc4d9fbe1c631f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Event.meta b/AxibugEmuOnline.Client/Assets/Script/Event.meta
new file mode 100644
index 0000000..fdb98cb
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Event.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7fc94f95c97ea2947aae884b009b742c
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Event/EEvent.cs b/AxibugEmuOnline.Client/Assets/Script/Event/EEvent.cs
new file mode 100644
index 0000000..3896d91
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Event/EEvent.cs
@@ -0,0 +1,8 @@
+namespace AxibugEmuOnline.Client.Event
+{
+ public enum EEvent
+ {
+ // 添加你自己需要的事件类型
+ OnChatMsg
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Script/Event/EEvent.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Event/EEvent.cs.meta
new file mode 100644
index 0000000..8578a44
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Event/EEvent.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cb895131261b61748978c83bf9b27920
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Event/EventSystem.cs b/AxibugEmuOnline.Client/Assets/Script/Event/EventSystem.cs
new file mode 100644
index 0000000..f729da0
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Event/EventSystem.cs
@@ -0,0 +1,242 @@
+using AxibugEmuOnline.Client.ClientCore;
+using System;
+using System.Collections.Generic;
+
+namespace AxibugEmuOnline.Client.Event
+{
+ public class EventData
+ {
+ private static long BaseUid = 0;
+ private static long GenUid()
+ {
+ return (++BaseUid);
+ }
+
+ ///
+ /// 唯一id
+ ///
+ public long uid { get; private set; }
+
+ ///
+ /// 回调
+ ///
+ public Delegate callback { get; private set; }
+
+ public EventData(Delegate d)
+ {
+ uid = GenUid();
+ callback = d;
+ }
+ }
+
+ public class EventSystem
+ {
+ private static EventSystem instance = new EventSystem();
+ public static EventSystem Instance { get { return instance; } }
+
+ private Dictionary> eventDic = new Dictionary>(128);
+
+ private EventSystem() { }
+
+
+ #region RegisterEvent
+ public void RegisterEvent(EEvent evt, Action callback)
+ {
+ InterRegisterEvent(evt, callback);
+ }
+
+ public void RegisterEvent(EEvent evt, Action callback)
+ {
+ InterRegisterEvent(evt, callback);
+ }
+
+ public void RegisterEvent(EEvent evt, Action callback)
+ {
+ InterRegisterEvent(evt, callback);
+ }
+
+ public void RegisterEvent(EEvent evt, Action callback)
+ {
+ InterRegisterEvent(evt, callback);
+ }
+
+ public void RegisterEvent(EEvent evt, Action callback)
+ {
+ InterRegisterEvent(evt, callback);
+ }
+
+ private void InterRegisterEvent(EEvent evt, Delegate callback)
+ {
+ if (eventDic.ContainsKey(evt))
+ {
+ if (eventDic[evt].IndexOf(callback) < 0)
+ {
+ eventDic[evt].Add(callback);
+ }
+ }
+ else
+ {
+ eventDic.Add(evt, new List() { callback });
+ }
+ }
+ #endregion
+
+ #region UnregisterEvent
+
+ public void UnregisterEvent(EEvent evt, Action callback)
+ {
+ Delegate tempDelegate = callback;
+ InterUnregisterEvent(evt, tempDelegate);
+ }
+
+ public void UnregisterEvent(EEvent evt, Action callback)
+ {
+ Delegate tempDelegate = callback;
+ InterUnregisterEvent(evt, tempDelegate);
+ }
+
+ public void UnregisterEvent(EEvent evt, Action callback)
+ {
+ Delegate tempDelegate = callback;
+ InterUnregisterEvent(evt, tempDelegate);
+ }
+
+ public void UnregisterEvent(EEvent evt, Action callback)
+ {
+ Delegate tempDelegate = callback;
+ InterUnregisterEvent(evt, tempDelegate);
+ }
+
+ public void UnregisterEvent(EEvent evt, Action callback)
+ {
+ Delegate tempDelegate = callback;
+ InterUnregisterEvent(evt, tempDelegate);
+ }
+
+ private void InterUnregisterEvent(EEvent evt, Delegate callback)
+ {
+ if (eventDic.ContainsKey(evt))
+ {
+ eventDic[evt].Remove(callback);
+ if (eventDic[evt].Count == 0) eventDic.Remove(evt);
+ }
+ }
+ #endregion
+
+ #region PostEvent
+ public void PostEvent(EEvent evt, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
+ {
+ List eventList = GetEventList(evt);
+ if (eventList != null)
+ {
+ foreach (Delegate callback in eventList)
+ {
+ try
+ {
+ ((Action)callback)(arg1, arg2, arg3, arg4);
+ }
+ catch (Exception e)
+ {
+ AppAxibugEmuOnline.log.Error(e.Message);
+ }
+ }
+ }
+ }
+
+ public void PostEvent(EEvent evt, T1 arg1, T2 arg2, T3 arg3)
+ {
+ List eventList = GetEventList(evt);
+ if (eventList != null)
+ {
+ foreach (Delegate callback in eventList)
+ {
+ try
+ {
+ ((Action)callback)(arg1, arg2, arg3);
+ }
+ catch (Exception e)
+ {
+ AppAxibugEmuOnline.log.Error(e.Message);
+ }
+ }
+ }
+ }
+
+ public void PostEvent(EEvent evt, T1 arg1, T2 arg2)
+ {
+ List eventList = GetEventList(evt);
+ if (eventList != null)
+ {
+ foreach (Delegate callback in eventList)
+ {
+ try
+ {
+ ((Action)callback)(arg1, arg2);
+ }
+ catch (Exception e)
+ {
+ AppAxibugEmuOnline.log.Error(e.Message);
+ }
+ }
+ }
+ }
+
+ public void PostEvent(EEvent evt, T arg)
+ {
+ List eventList = GetEventList(evt);
+ if (eventList != null)
+ {
+ foreach (Delegate callback in eventList)
+ {
+ try
+ {
+ ((Action)callback)(arg);
+ }
+ catch (Exception e)
+ {
+ AppAxibugEmuOnline.log.Error(e.Message + ", method name : " + callback.Method);
+ }
+ }
+ }
+
+ }
+
+ public void PostEvent(EEvent evt)
+ {
+ List eventList = GetEventList(evt);
+ if (eventList != null)
+ {
+ foreach (Delegate callback in eventList)
+ {
+ try
+ {
+ ((Action)callback)();
+ }
+ catch (Exception e)
+ {
+ AppAxibugEmuOnline.log.Error(e.Message);
+ }
+ }
+ }
+ }
+ #endregion
+
+ ///
+ /// 获取所有事件
+ ///
+ ///
+ ///
+ private List GetEventList(EEvent evt)
+ {
+ if (eventDic.ContainsKey(evt))
+ {
+ List tempList = eventDic[evt];
+ if (null != tempList)
+ {
+ return tempList;
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Script/Event/EventSystem.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Event/EventSystem.cs.meta
new file mode 100644
index 0000000..f540b19
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Event/EventSystem.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0a03cd2c9855b3647b7b2cd9cb9119ca
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager.meta b/AxibugEmuOnline.Client/Assets/Script/Manager.meta
new file mode 100644
index 0000000..7cf740f
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Manager.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 437646a1943eaf34dbe1a56eb38494bb
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/AppChat.cs b/AxibugEmuOnline.Client/Assets/Script/Manager/AppChat.cs
new file mode 100644
index 0000000..a1b5333
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Manager/AppChat.cs
@@ -0,0 +1,31 @@
+using AxibugEmuOnline.Client.ClientCore;
+using AxibugEmuOnline.Client.Common;
+using AxibugEmuOnline.Client.Event;
+using AxibugEmuOnline.Client.Network;
+using AxibugProtobuf;
+
+namespace AxibugEmuOnline.Client.Manager
+{
+ public class AppChat
+ {
+ public AppChat()
+ {
+ NetMsg.Instance.RegNetMsgEvent((int)CommandID.CmdChatmsg, RecvChatMsg);
+ }
+
+ public void SendChatMsg(string ChatMsg)
+ {
+ Protobuf_ChatMsg msg = new Protobuf_ChatMsg()
+ {
+ ChatMsg = ChatMsg,
+ };
+ AppAxibugEmuOnline.networkHelper.SendToServer((int)CommandID.CmdChatmsg, ProtoBufHelper.Serizlize(msg));
+ }
+
+ public void RecvChatMsg(byte[] reqData)
+ {
+ Protobuf_ChatMsg_RESP msg = ProtoBufHelper.DeSerizlize(reqData);
+ EventSystem.Instance.PostEvent(EEvent.OnChatMsg, msg.NickName, msg.ChatMsg);
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/AppChat.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Manager/AppChat.cs.meta
new file mode 100644
index 0000000..be2baff
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Manager/AppChat.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f7e9dbada150393488fa949affc5fe3a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/AppGame.cs b/AxibugEmuOnline.Client/Assets/Script/Manager/AppGame.cs
new file mode 100644
index 0000000..3258c36
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Manager/AppGame.cs
@@ -0,0 +1,76 @@
+using AxibugEmuOnline.Client.ClientCore;
+using AxibugEmuOnline.Client.Common;
+using AxibugEmuOnline.Client.Network;
+using AxibugProtobuf;
+using AxibugEmuOnline.Client.UNES;
+using Google.Protobuf;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+
+namespace AxibugEmuOnline.Client.Manager
+{
+ public class AppGame
+ {
+ public AppGame()
+ {
+ NetMsg.Instance.RegNetMsgEvent((int)CommandID.CmdScreen, OnScreen);
+ }
+ Protobuf_Screnn_Frame _Protobuf_Screnn_Frame = new Protobuf_Screnn_Frame();
+
+ public uint[] RawBitmap { get; set; } = new uint[UNESBehaviour.GameWidth * UNESBehaviour.GameHeight];
+ // TODO: use real chroma/luma decoding
+ private readonly uint[] _palette = {
+ 0x7C7C7C, 0x0000FC, 0x0000BC, 0x4428BC, 0x940084, 0xA80020, 0xA81000, 0x881400,
+ 0x503000, 0x007800, 0x006800, 0x005800, 0x004058, 0x000000, 0x000000, 0x000000,
+ 0xBCBCBC, 0x0078F8, 0x0058F8, 0x6844FC, 0xD800CC, 0xE40058, 0xF83800, 0xE45C10,
+ 0xAC7C00, 0x00B800, 0x00A800, 0x00A844, 0x008888, 0x000000, 0x000000, 0x000000,
+ 0xF8F8F8, 0x3CBCFC, 0x6888FC, 0x9878F8, 0xF878F8, 0xF85898, 0xF87858, 0xFCA044,
+ 0xF8B800, 0xB8F818, 0x58D854, 0x58F898, 0x00E8D8, 0x787878, 0x000000, 0x000000,
+ 0xFCFCFC, 0xA4E4FC, 0xB8B8F8, 0xD8B8F8, 0xF8B8F8, 0xF8A4C0, 0xF0D0B0, 0xFCE0A8,
+ 0xF8D878, 0xD8F878, 0xB8F8B8, 0xB8F8D8, 0x00FCFC, 0xF8D8F8, 0x000000, 0x000000
+ };
+ public void SendScreen(byte[] ScreenData)
+ {
+ byte[] comData = CompressByteArray(ScreenData);
+ _Protobuf_Screnn_Frame.FrameID = 0;
+ _Protobuf_Screnn_Frame.RawBitmap = ByteString.CopyFrom(comData);
+ AppAxibugEmuOnline.networkHelper.SendToServer((int)CommandID.CmdScreen, ProtoBufHelper.Serizlize(_Protobuf_Screnn_Frame));
+ }
+
+ public void OnScreen(byte[] reqData)
+ {
+ Protobuf_Screnn_Frame msg = ProtoBufHelper.DeSerizlize(reqData);
+ lock (RawBitmap)
+ {
+ byte[] data = DecompressByteArray(msg.RawBitmap.ToArray());
+ for (int i = 0; i < data.Length; i++)
+ {
+ RawBitmap[i] = _palette[data[i]];
+ }
+ }
+ }
+
+ public static byte[] CompressByteArray(byte[] bytesToCompress)
+ {
+ using (var compressedMemoryStream = new MemoryStream())
+ using (var gzipStream = new GZipStream(compressedMemoryStream, CompressionMode.Compress))
+ {
+ gzipStream.Write(bytesToCompress, 0, bytesToCompress.Length);
+ gzipStream.Close();
+ return compressedMemoryStream.ToArray();
+ }
+ }
+
+ public static byte[] DecompressByteArray(byte[] compressedBytes)
+ {
+ using (var compressedMemoryStream = new MemoryStream(compressedBytes))
+ using (var gzipStream = new GZipStream(compressedMemoryStream, CompressionMode.Decompress))
+ using (var resultMemoryStream = new MemoryStream())
+ {
+ gzipStream.CopyTo(resultMemoryStream);
+ return resultMemoryStream.ToArray();
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/AppGame.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Manager/AppGame.cs.meta
new file mode 100644
index 0000000..22078bd
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Manager/AppGame.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5f7ca4d4fcf6c0f41a6ead44beed31dd
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/AppLogin.cs b/AxibugEmuOnline.Client/Assets/Script/Manager/AppLogin.cs
new file mode 100644
index 0000000..4185a62
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Manager/AppLogin.cs
@@ -0,0 +1,46 @@
+using AxibugEmuOnline.Client.ClientCore;
+using AxibugEmuOnline.Client.Common;
+using AxibugEmuOnline.Client.Network;
+using AxibugProtobuf;
+using System;
+
+namespace AxibugEmuOnline.Client.Manager
+{
+ public class AppLogin
+ {
+ static string LastLoginGuid = "";
+ public AppLogin()
+ {
+ NetMsg.Instance.RegNetMsgEvent((int)CommandID.CmdLogin, RecvLoginMsg);
+ }
+
+ public void Login()
+ {
+ AppAxibugEmuOnline.log.Debug("-->Login");
+ if(string.IsNullOrEmpty(LastLoginGuid))
+ LastLoginGuid = Guid.NewGuid().ToString();
+
+ AppAxibugEmuOnline.user.userdata.Account = LastLoginGuid;
+ Protobuf_Login msg = new Protobuf_Login()
+ {
+ LoginType = 0,
+ Account = AppAxibugEmuOnline.user.userdata.Account,
+ };
+ AppAxibugEmuOnline.networkHelper.SendToServer((int)CommandID.CmdLogin, ProtoBufHelper.Serizlize(msg));
+ }
+
+ public void RecvLoginMsg(byte[] reqData)
+ {
+ Protobuf_Login_RESP msg = ProtoBufHelper.DeSerizlize(reqData);
+ if (msg.Status == LoginResultStatus.Ok)
+ {
+ AppAxibugEmuOnline.log.Info("登录成功");
+ AppAxibugEmuOnline.user.InitMainUserData(AppAxibugEmuOnline.user.userdata.Account,msg.UID);
+ }
+ else
+ {
+ AppAxibugEmuOnline.log.Info("登录失败");
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/AppLogin.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Manager/AppLogin.cs.meta
new file mode 100644
index 0000000..aeed966
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Manager/AppLogin.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8bd5c1c74a8a42749809545ab685696e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/LogManager.cs b/AxibugEmuOnline.Client/Assets/Script/Manager/LogManager.cs
new file mode 100644
index 0000000..6326887
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Manager/LogManager.cs
@@ -0,0 +1,53 @@
+namespace AxibugEmuOnline.Client.Manager
+{
+ public class LogManager
+ {
+ public enum E_LogType:byte
+ {
+ Info = 0,
+ Debug = 1,
+ Warning = 2,
+ Error = 3,
+ }
+ ///
+ /// 日志
+ ///
+ ///
+ public delegate void OnLogHandler(int debuglv,string msg);
+
+ ///
+ /// 内部输出
+ ///
+ public static event OnLogHandler OnLog;
+
+ public void Info(string str)
+ {
+ Log(E_LogType.Info, str);
+ }
+
+ public void Debug(string str)
+ {
+ Log(E_LogType.Debug, str);
+ }
+
+ public void Warning(string str)
+ {
+ Log(E_LogType.Warning, str);
+ }
+
+ public void Error(string str)
+ {
+ Log(E_LogType.Error, str);
+ }
+
+ public void Log(E_LogType logtype,string str)
+ {
+ OnLog?.Invoke((int)logtype, str);
+ }
+
+ public void Log(int logtype, string str)
+ {
+ OnLog?.Invoke(logtype, str);
+ }
+ }
+}
\ No newline at end of file
diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/LogManager.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Manager/LogManager.cs.meta
new file mode 100644
index 0000000..c6bb5f5
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Manager/LogManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: dc0abf46c60a4d64381ea53f9ddc89b4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/UserDataManager.cs b/AxibugEmuOnline.Client/Assets/Script/Manager/UserDataManager.cs
new file mode 100644
index 0000000..6816739
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Manager/UserDataManager.cs
@@ -0,0 +1,58 @@
+using AxibugEmuOnline.Client.ClientCore;
+using AxibugProtobuf;
+
+namespace AxibugEmuOnline.Client.Manager
+{
+ public class UserDataBase
+ {
+ public long UID { get; set; }
+ public string Account { get; set; }
+ }
+
+ public class MainUserDataBase : UserDataBase
+ {
+ public bool IsLoggedIn { get; set; } = false;
+ }
+
+ public class UserDataManager
+ {
+ public UserDataManager()
+ {
+ //注册重连成功事件,以便后续自动登录
+ AppAxibugEmuOnline.networkHelper.OnReConnected += OnReConnected;
+ }
+ public MainUserDataBase userdata { get;private set; } = new MainUserDataBase();
+ public bool IsLoggedIn => userdata.IsLoggedIn;
+
+ public void InitMainUserData(string UName,long UID)
+ {
+ userdata.Account = UName;
+ userdata.IsLoggedIn = true;
+ userdata.UID = UID;
+ //以及其他数据初始化
+ //...
+ }
+
+ ///
+ /// 登出
+ ///
+ public void LoginOutData()
+ {
+ userdata.IsLoggedIn = false;
+ //以及其他数据清理
+ //...
+ }
+
+ ///
+ /// 当重连成功
+ ///
+ public void OnReConnected()
+ {
+ //如果之前已登录,则重新登录
+ if (userdata.IsLoggedIn)
+ {
+ AppAxibugEmuOnline.login.Login();
+ }
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/UserDataManager.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Manager/UserDataManager.cs.meta
new file mode 100644
index 0000000..d925551
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Manager/UserDataManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: db9b113b5261d564aa86816443a02e25
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Network.meta b/AxibugEmuOnline.Client/Assets/Script/Network.meta
new file mode 100644
index 0000000..86d9106
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Network.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 06db94811d3239e4e820a598c934ddb5
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Network/NetMsg.cs b/AxibugEmuOnline.Client/Assets/Script/Network/NetMsg.cs
new file mode 100644
index 0000000..c696c9a
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Network/NetMsg.cs
@@ -0,0 +1,98 @@
+using AxibugEmuOnline.Client.ClientCore;
+using System;
+using System.Collections.Generic;
+
+namespace AxibugEmuOnline.Client.Network
+{
+
+ public class NetMsg
+ {
+ private static NetMsg instance = new NetMsg();
+ public static NetMsg Instance { get { return instance; } }
+
+ private Dictionary> netEventDic = new Dictionary>(128);
+
+ private NetMsg() { }
+
+
+ #region RegisterMsgEvent
+
+ public void RegNetMsgEvent(int cmd, Action callback)
+ {
+ InterRegNetMsgEvent(cmd, callback);
+ }
+
+ private void InterRegNetMsgEvent(int cmd, Delegate callback)
+ {
+ if (netEventDic.ContainsKey(cmd))
+ {
+ if (netEventDic[cmd].IndexOf(callback) < 0)
+ {
+ netEventDic[cmd].Add(callback);
+ }
+ }
+ else
+ {
+ netEventDic.Add(cmd, new List() { callback });
+ }
+ }
+ #endregion
+
+ #region UnregisterCMD
+
+ public void UnregisterCMD(int evt, Action callback)
+ {
+ Delegate tempDelegate = callback;
+ InterUnregisterCMD(evt, tempDelegate);
+ }
+
+ private void InterUnregisterCMD(int cmd, Delegate callback)
+ {
+ if (netEventDic.ContainsKey(cmd))
+ {
+ netEventDic[cmd].Remove(callback);
+ if (netEventDic[cmd].Count == 0) netEventDic.Remove(cmd);
+ }
+ }
+ #endregion
+
+ #region PostEvent
+ public void PostNetMsgEvent(int cmd, byte[] arg)
+ {
+ List eventList = GetNetEventDicList(cmd);
+ if (eventList != null)
+ {
+ foreach (Delegate callback in eventList)
+ {
+ try
+ {
+ ((Action)callback)(arg);
+ }
+ catch (Exception e)
+ {
+ AppAxibugEmuOnline.log.Error(e.Message);
+ }
+ }
+ }
+ }
+ #endregion
+
+ ///
+ /// 获取所有事件
+ ///
+ ///
+ ///
+ private List GetNetEventDicList(int cmd)
+ {
+ if (netEventDic.ContainsKey(cmd))
+ {
+ List tempList = netEventDic[cmd];
+ if (null != tempList)
+ {
+ return tempList;
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Script/Network/NetMsg.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Network/NetMsg.cs.meta
new file mode 100644
index 0000000..a288a04
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Network/NetMsg.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1e196e9f86b5d194a8ac78430e1c9d5c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Network/NetworkHelper.cs b/AxibugEmuOnline.Client/Assets/Script/Network/NetworkHelper.cs
new file mode 100644
index 0000000..ab6b305
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Network/NetworkHelper.cs
@@ -0,0 +1,131 @@
+using AxibugEmuOnline.Client.ClientCore;
+using HaoYueNet.ClientNetworkNet.Standard2;
+using System;
+using System.Threading;
+
+namespace AxibugEmuOnline.Client.Network
+{
+ ///
+ /// 继承网络库,以支持网络功能
+ ///
+ public class NetworkHelper : NetworkHelperCore
+ {
+ public NetworkHelper()
+ {
+ //指定接收服务器数据事件
+ OnReceiveData += GetDataCallBack;
+ //断开连接
+ OnClose += OnConnectClose;
+ OnConnected += NetworkConnected;
+ //网络库调试信息输出事件,用于打印网络内容
+ OnLogOut += NetworkDeBugLog;
+ }
+
+ public delegate void OnReConnectedHandler();
+ ///
+ /// 重连成功事件
+ ///
+ public event OnReConnectedHandler OnReConnected;
+ ///
+ /// 是否自动重连
+ ///
+ public bool bAutoReConnect = false;
+ ///
+ /// 重连尝试时间
+ ///
+ const int ReConnectTryTime = 1000;
+
+ public void NetworkConnected(bool IsConnect)
+ {
+ NetworkDeBugLog($"NetworkConnected:{IsConnect}");
+ if (IsConnect)
+ {
+ //从未登录过
+ if (!AppAxibugEmuOnline.user.IsLoggedIn)
+ {
+ //首次登录
+ AppAxibugEmuOnline.login.Login();
+ }
+ }
+ else
+ {
+ //连接失败
+ NetworkDeBugLog("连接失败!");
+
+
+ //自动重连开关
+ if (bAutoReConnect)
+ ReConnect();
+ }
+ }
+
+ public void NetworkDeBugLog(string str)
+ {
+ //用于Unity内的输出
+ //Debug.Log("NetCoreDebug >> "+str);
+ AppAxibugEmuOnline.log.Info("NetCoreDebug >> " + str);
+ }
+
+ ///
+ /// 接受包回调
+ ///
+ /// 协议ID
+ /// 错误编号
+ /// 业务数据
+ public void GetDataCallBack(int CMDID, int ERRCODE, byte[] data)
+ {
+ //NetworkDeBugLog("收到消息 CMDID =>" + CMDID + " ERRCODE =>" + ERRCODE + " 数据长度=>" + data.Length);
+ try
+ {
+ //抛出网络数据
+ NetMsg.Instance.PostNetMsgEvent(CMDID, data);
+ }
+ catch (Exception ex)
+ {
+ NetworkDeBugLog("逻辑处理错误:" + ex.ToString());
+ }
+ }
+
+ ///
+ /// 关闭连接
+ ///
+ public void OnConnectClose()
+ {
+ NetworkDeBugLog("OnConnectClose");
+
+
+ //自动重连开关
+ if (bAutoReConnect)
+ ReConnect();
+ }
+
+
+ bool bInReConnecting = false;
+ ///
+ /// 自动重连
+ ///
+ void ReConnect()
+ {
+ if (bInReConnecting)
+ return;
+ bInReConnecting = true;
+
+ bool bflagDone = false;
+ do
+ {
+ //等待时间
+ Thread.Sleep(ReConnectTryTime);
+ AppAxibugEmuOnline.log.Info($"尝试自动重连{LastConnectIP}:{LastConnectPort}……");
+ //第一步
+ if (Init(LastConnectIP, LastConnectPort))
+ {
+ AppAxibugEmuOnline.log.Info($"自动重连成功!");
+ bflagDone = true;
+ AppAxibugEmuOnline.log.Info($"触发重连后的自动逻辑!");
+ OnReConnected?.Invoke();
+ }
+ } while (!bflagDone);
+ bInReConnecting = false;
+ }
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Script/Network/NetworkHelper.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Network/NetworkHelper.cs.meta
new file mode 100644
index 0000000..4e948d9
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Network/NetworkHelper.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ffc8e86b25d7476429d566e86268e99f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Protobuf.meta b/AxibugEmuOnline.Client/Assets/Script/Protobuf.meta
new file mode 100644
index 0000000..c612430
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Protobuf.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 982424ed2d905a74e94341fa6547e304
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/Protobuf/ProtobufAxibugEmuOnline.cs b/AxibugEmuOnline.Client/Assets/Script/Protobuf/ProtobufAxibugEmuOnline.cs
new file mode 100644
index 0000000..b23e13b
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Protobuf/ProtobufAxibugEmuOnline.cs
@@ -0,0 +1,1398 @@
+//
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: protobuf_AxibugEmuOnline.proto
+//
+#pragma warning disable 1591, 0612, 3021
+#region Designer generated code
+
+using pb = global::Google.Protobuf;
+using pbc = global::Google.Protobuf.Collections;
+using pbr = global::Google.Protobuf.Reflection;
+using scg = global::System.Collections.Generic;
+namespace AxibugProtobuf {
+
+ /// Holder for reflection information generated from protobuf_AxibugEmuOnline.proto
+ public static partial class ProtobufAxibugEmuOnlineReflection {
+
+ #region Descriptor
+ /// File descriptor for protobuf_AxibugEmuOnline.proto
+ public static pbr::FileDescriptor Descriptor {
+ get { return descriptor; }
+ }
+ private static pbr::FileDescriptor descriptor;
+
+ static ProtobufAxibugEmuOnlineReflection() {
+ byte[] descriptorData = global::System.Convert.FromBase64String(
+ string.Concat(
+ "Ch5wcm90b2J1Zl9BeGlidWdFbXVPbmxpbmUucHJvdG8SDkF4aWJ1Z1Byb3Rv",
+ "YnVmIiMKEFByb3RvYnVmX0NoYXRNc2cSDwoHQ2hhdE1zZxgBIAEoCSJIChVQ",
+ "cm90b2J1Zl9DaGF0TXNnX1JFU1ASEAoITmlja05hbWUYASABKAkSDwoHQ2hh",
+ "dE1zZxgCIAEoCRIMCgREYXRlGAMgASgDIpEBCg5Qcm90b2J1Zl9Mb2dpbhIs",
+ "Cglsb2dpblR5cGUYASABKA4yGS5BeGlidWdQcm90b2J1Zi5Mb2dpblR5cGUS",
+ "LgoKZGV2aWNlVHlwZRgCIAEoDjIaLkF4aWJ1Z1Byb3RvYnVmLkRldmljZVR5",
+ "cGUSDwoHQWNjb3VudBgDIAEoCRIQCghQYXNzd29yZBgEIAEoCSKMAQoTUHJv",
+ "dG9idWZfTG9naW5fUkVTUBINCgVUb2tlbhgBIAEoCRIVCg1MYXN0TG9naW5E",
+ "YXRlGAIgASgJEg8KB1JlZ0RhdGUYAyABKAkSMQoGU3RhdHVzGAQgASgOMiEu",
+ "QXhpYnVnUHJvdG9idWYuTG9naW5SZXN1bHRTdGF0dXMSCwoDVUlEGAUgASgD",
+ "IjsKFVByb3RvYnVmX1NjcmVubl9GcmFtZRIPCgdGcmFtZUlEGAEgASgFEhEK",
+ "CVJhd0JpdG1hcBgCIAEoDCpOCglDb21tYW5kSUQSDgoKQ01EX0RFRkFVTBAA",
+ "Eg4KCUNNRF9MT0dJThDRDxIQCgtDTURfQ0hBVE1TRxChHxIPCgpDTURfU2Ny",
+ "ZWVuEIknKisKCUVycm9yQ29kZRIQCgxFUlJPUl9ERUZBVUwQABIMCghFUlJP",
+ "Ul9PSxABKhwKCUxvZ2luVHlwZRIPCgtCYXNlRGVmYXVsdBAAKksKCkRldmlj",
+ "ZVR5cGUSFgoSRGV2aWNlVHlwZV9EZWZhdWx0EAASBgoCUEMQARILCgdBbmRy",
+ "b2lkEAISBwoDSU9TEAMSBwoDUFNWEAQqTgoRTG9naW5SZXN1bHRTdGF0dXMS",
+ "IQodTG9naW5SZXN1bHRTdGF0dXNfQmFzZURlZmF1bHQQABIGCgJPSxABEg4K",
+ "CkFjY291bnRFcnIQAkICSAFiBnByb3RvMw=="));
+ descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
+ new pbr::FileDescriptor[] { },
+ new pbr::GeneratedClrTypeInfo(new[] {typeof(global::AxibugProtobuf.CommandID), typeof(global::AxibugProtobuf.ErrorCode), typeof(global::AxibugProtobuf.LoginType), typeof(global::AxibugProtobuf.DeviceType), typeof(global::AxibugProtobuf.LoginResultStatus), }, null, new pbr::GeneratedClrTypeInfo[] {
+ new pbr::GeneratedClrTypeInfo(typeof(global::AxibugProtobuf.Protobuf_ChatMsg), global::AxibugProtobuf.Protobuf_ChatMsg.Parser, new[]{ "ChatMsg" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::AxibugProtobuf.Protobuf_ChatMsg_RESP), global::AxibugProtobuf.Protobuf_ChatMsg_RESP.Parser, new[]{ "NickName", "ChatMsg", "Date" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::AxibugProtobuf.Protobuf_Login), global::AxibugProtobuf.Protobuf_Login.Parser, new[]{ "LoginType", "DeviceType", "Account", "Password" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::AxibugProtobuf.Protobuf_Login_RESP), global::AxibugProtobuf.Protobuf_Login_RESP.Parser, new[]{ "Token", "LastLoginDate", "RegDate", "Status", "UID" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::AxibugProtobuf.Protobuf_Screnn_Frame), global::AxibugProtobuf.Protobuf_Screnn_Frame.Parser, new[]{ "FrameID", "RawBitmap" }, null, null, null, null)
+ }));
+ }
+ #endregion
+
+ }
+ #region Enums
+ public enum CommandID {
+ ///
+ ///缺省不使用
+ ///
+ [pbr::OriginalName("CMD_DEFAUL")] CmdDefaul = 0,
+ ///
+ ///自动登录上行 | 下行 对应 Protobuf_Login | Protobuf_Login_RESP
+ ///
+ [pbr::OriginalName("CMD_LOGIN")] CmdLogin = 2001,
+ ///
+ ///广播信息上行 | 下行 对应 Protobuf_ChatMsg | Protobuf_ChatMsg_RESP
+ ///
+ [pbr::OriginalName("CMD_CHATMSG")] CmdChatmsg = 4001,
+ ///
+ ///画面同步 | 同步广播 对应 Protobuf_Screnn_Frame
+ ///
+ [pbr::OriginalName("CMD_Screen")] CmdScreen = 5001,
+ }
+
+ public enum ErrorCode {
+ ///
+ ///缺省不使用
+ ///
+ [pbr::OriginalName("ERROR_DEFAUL")] ErrorDefaul = 0,
+ ///
+ ///成功
+ ///
+ [pbr::OriginalName("ERROR_OK")] ErrorOk = 1,
+ }
+
+ public enum LoginType {
+ ///
+ ///缺省不使用
+ ///
+ [pbr::OriginalName("BaseDefault")] BaseDefault = 0,
+ }
+
+ public enum DeviceType {
+ ///
+ ///缺省不使用
+ ///
+ [pbr::OriginalName("DeviceType_Default")] Default = 0,
+ [pbr::OriginalName("PC")] Pc = 1,
+ [pbr::OriginalName("Android")] Android = 2,
+ [pbr::OriginalName("IOS")] Ios = 3,
+ [pbr::OriginalName("PSV")] Psv = 4,
+ }
+
+ public enum LoginResultStatus {
+ ///
+ ///缺省不使用
+ ///
+ [pbr::OriginalName("LoginResultStatus_BaseDefault")] BaseDefault = 0,
+ [pbr::OriginalName("OK")] Ok = 1,
+ [pbr::OriginalName("AccountErr")] AccountErr = 2,
+ }
+
+ #endregion
+
+ #region Messages
+ ///
+ ///聊天 上行
+ ///
+ public sealed partial class Protobuf_ChatMsg : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Protobuf_ChatMsg());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::AxibugProtobuf.ProtobufAxibugEmuOnlineReflection.Descriptor.MessageTypes[0]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_ChatMsg() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_ChatMsg(Protobuf_ChatMsg other) : this() {
+ chatMsg_ = other.chatMsg_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_ChatMsg Clone() {
+ return new Protobuf_ChatMsg(this);
+ }
+
+ /// Field number for the "ChatMsg" field.
+ public const int ChatMsgFieldNumber = 1;
+ private string chatMsg_ = "";
+ ///
+ ///消息
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string ChatMsg {
+ get { return chatMsg_; }
+ set {
+ chatMsg_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override bool Equals(object other) {
+ return Equals(other as Protobuf_ChatMsg);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public bool Equals(Protobuf_ChatMsg other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (ChatMsg != other.ChatMsg) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (ChatMsg.Length != 0) hash ^= ChatMsg.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (ChatMsg.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(ChatMsg);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (ChatMsg.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(ChatMsg);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int CalculateSize() {
+ int size = 0;
+ if (ChatMsg.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(ChatMsg);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(Protobuf_ChatMsg other) {
+ if (other == null) {
+ return;
+ }
+ if (other.ChatMsg.Length != 0) {
+ ChatMsg = other.ChatMsg;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 10: {
+ ChatMsg = input.ReadString();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 10: {
+ ChatMsg = input.ReadString();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ ///
+ ///聊天 下行
+ ///
+ public sealed partial class Protobuf_ChatMsg_RESP : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Protobuf_ChatMsg_RESP());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::AxibugProtobuf.ProtobufAxibugEmuOnlineReflection.Descriptor.MessageTypes[1]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_ChatMsg_RESP() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_ChatMsg_RESP(Protobuf_ChatMsg_RESP other) : this() {
+ nickName_ = other.nickName_;
+ chatMsg_ = other.chatMsg_;
+ date_ = other.date_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_ChatMsg_RESP Clone() {
+ return new Protobuf_ChatMsg_RESP(this);
+ }
+
+ /// Field number for the "NickName" field.
+ public const int NickNameFieldNumber = 1;
+ private string nickName_ = "";
+ ///
+ ///昵称
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string NickName {
+ get { return nickName_; }
+ set {
+ nickName_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "ChatMsg" field.
+ public const int ChatMsgFieldNumber = 2;
+ private string chatMsg_ = "";
+ ///
+ ///消息
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string ChatMsg {
+ get { return chatMsg_; }
+ set {
+ chatMsg_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "Date" field.
+ public const int DateFieldNumber = 3;
+ private long date_;
+ ///
+ ///时间
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public long Date {
+ get { return date_; }
+ set {
+ date_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override bool Equals(object other) {
+ return Equals(other as Protobuf_ChatMsg_RESP);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public bool Equals(Protobuf_ChatMsg_RESP other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (NickName != other.NickName) return false;
+ if (ChatMsg != other.ChatMsg) return false;
+ if (Date != other.Date) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (NickName.Length != 0) hash ^= NickName.GetHashCode();
+ if (ChatMsg.Length != 0) hash ^= ChatMsg.GetHashCode();
+ if (Date != 0L) hash ^= Date.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (NickName.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(NickName);
+ }
+ if (ChatMsg.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(ChatMsg);
+ }
+ if (Date != 0L) {
+ output.WriteRawTag(24);
+ output.WriteInt64(Date);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (NickName.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(NickName);
+ }
+ if (ChatMsg.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(ChatMsg);
+ }
+ if (Date != 0L) {
+ output.WriteRawTag(24);
+ output.WriteInt64(Date);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int CalculateSize() {
+ int size = 0;
+ if (NickName.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(NickName);
+ }
+ if (ChatMsg.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(ChatMsg);
+ }
+ if (Date != 0L) {
+ size += 1 + pb::CodedOutputStream.ComputeInt64Size(Date);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(Protobuf_ChatMsg_RESP other) {
+ if (other == null) {
+ return;
+ }
+ if (other.NickName.Length != 0) {
+ NickName = other.NickName;
+ }
+ if (other.ChatMsg.Length != 0) {
+ ChatMsg = other.ChatMsg;
+ }
+ if (other.Date != 0L) {
+ Date = other.Date;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 10: {
+ NickName = input.ReadString();
+ break;
+ }
+ case 18: {
+ ChatMsg = input.ReadString();
+ break;
+ }
+ case 24: {
+ Date = input.ReadInt64();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 10: {
+ NickName = input.ReadString();
+ break;
+ }
+ case 18: {
+ ChatMsg = input.ReadString();
+ break;
+ }
+ case 24: {
+ Date = input.ReadInt64();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ ///
+ ///登录数据上行
+ ///
+ public sealed partial class Protobuf_Login : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Protobuf_Login());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::AxibugProtobuf.ProtobufAxibugEmuOnlineReflection.Descriptor.MessageTypes[2]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_Login() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_Login(Protobuf_Login other) : this() {
+ loginType_ = other.loginType_;
+ deviceType_ = other.deviceType_;
+ account_ = other.account_;
+ password_ = other.password_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_Login Clone() {
+ return new Protobuf_Login(this);
+ }
+
+ /// Field number for the "loginType" field.
+ public const int LoginTypeFieldNumber = 1;
+ private global::AxibugProtobuf.LoginType loginType_ = global::AxibugProtobuf.LoginType.BaseDefault;
+ ///
+ ///登录操作类型 [0]皓月通行证 [3] 皓月BF3 [4] 皓月BF4
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public global::AxibugProtobuf.LoginType LoginType {
+ get { return loginType_; }
+ set {
+ loginType_ = value;
+ }
+ }
+
+ /// Field number for the "deviceType" field.
+ public const int DeviceTypeFieldNumber = 2;
+ private global::AxibugProtobuf.DeviceType deviceType_ = global::AxibugProtobuf.DeviceType.Default;
+ ///
+ ///设备类型 [0]PC [1]AndroidPad预留 [3]IPad预留
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public global::AxibugProtobuf.DeviceType DeviceType {
+ get { return deviceType_; }
+ set {
+ deviceType_ = value;
+ }
+ }
+
+ /// Field number for the "Account" field.
+ public const int AccountFieldNumber = 3;
+ private string account_ = "";
+ ///
+ ///用户名
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string Account {
+ get { return account_; }
+ set {
+ account_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "Password" field.
+ public const int PasswordFieldNumber = 4;
+ private string password_ = "";
+ ///
+ ///密码
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string Password {
+ get { return password_; }
+ set {
+ password_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override bool Equals(object other) {
+ return Equals(other as Protobuf_Login);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public bool Equals(Protobuf_Login other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (LoginType != other.LoginType) return false;
+ if (DeviceType != other.DeviceType) return false;
+ if (Account != other.Account) return false;
+ if (Password != other.Password) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (LoginType != global::AxibugProtobuf.LoginType.BaseDefault) hash ^= LoginType.GetHashCode();
+ if (DeviceType != global::AxibugProtobuf.DeviceType.Default) hash ^= DeviceType.GetHashCode();
+ if (Account.Length != 0) hash ^= Account.GetHashCode();
+ if (Password.Length != 0) hash ^= Password.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (LoginType != global::AxibugProtobuf.LoginType.BaseDefault) {
+ output.WriteRawTag(8);
+ output.WriteEnum((int) LoginType);
+ }
+ if (DeviceType != global::AxibugProtobuf.DeviceType.Default) {
+ output.WriteRawTag(16);
+ output.WriteEnum((int) DeviceType);
+ }
+ if (Account.Length != 0) {
+ output.WriteRawTag(26);
+ output.WriteString(Account);
+ }
+ if (Password.Length != 0) {
+ output.WriteRawTag(34);
+ output.WriteString(Password);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (LoginType != global::AxibugProtobuf.LoginType.BaseDefault) {
+ output.WriteRawTag(8);
+ output.WriteEnum((int) LoginType);
+ }
+ if (DeviceType != global::AxibugProtobuf.DeviceType.Default) {
+ output.WriteRawTag(16);
+ output.WriteEnum((int) DeviceType);
+ }
+ if (Account.Length != 0) {
+ output.WriteRawTag(26);
+ output.WriteString(Account);
+ }
+ if (Password.Length != 0) {
+ output.WriteRawTag(34);
+ output.WriteString(Password);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int CalculateSize() {
+ int size = 0;
+ if (LoginType != global::AxibugProtobuf.LoginType.BaseDefault) {
+ size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) LoginType);
+ }
+ if (DeviceType != global::AxibugProtobuf.DeviceType.Default) {
+ size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) DeviceType);
+ }
+ if (Account.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(Account);
+ }
+ if (Password.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(Password);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(Protobuf_Login other) {
+ if (other == null) {
+ return;
+ }
+ if (other.LoginType != global::AxibugProtobuf.LoginType.BaseDefault) {
+ LoginType = other.LoginType;
+ }
+ if (other.DeviceType != global::AxibugProtobuf.DeviceType.Default) {
+ DeviceType = other.DeviceType;
+ }
+ if (other.Account.Length != 0) {
+ Account = other.Account;
+ }
+ if (other.Password.Length != 0) {
+ Password = other.Password;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ LoginType = (global::AxibugProtobuf.LoginType) input.ReadEnum();
+ break;
+ }
+ case 16: {
+ DeviceType = (global::AxibugProtobuf.DeviceType) input.ReadEnum();
+ break;
+ }
+ case 26: {
+ Account = input.ReadString();
+ break;
+ }
+ case 34: {
+ Password = input.ReadString();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ LoginType = (global::AxibugProtobuf.LoginType) input.ReadEnum();
+ break;
+ }
+ case 16: {
+ DeviceType = (global::AxibugProtobuf.DeviceType) input.ReadEnum();
+ break;
+ }
+ case 26: {
+ Account = input.ReadString();
+ break;
+ }
+ case 34: {
+ Password = input.ReadString();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ ///
+ ///登录数据下行
+ ///
+ public sealed partial class Protobuf_Login_RESP : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Protobuf_Login_RESP());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::AxibugProtobuf.ProtobufAxibugEmuOnlineReflection.Descriptor.MessageTypes[3]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_Login_RESP() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_Login_RESP(Protobuf_Login_RESP other) : this() {
+ token_ = other.token_;
+ lastLoginDate_ = other.lastLoginDate_;
+ regDate_ = other.regDate_;
+ status_ = other.status_;
+ uID_ = other.uID_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_Login_RESP Clone() {
+ return new Protobuf_Login_RESP(this);
+ }
+
+ /// Field number for the "Token" field.
+ public const int TokenFieldNumber = 1;
+ private string token_ = "";
+ ///
+ ///登录凭据 (本次登录之后,所有业务请求凭据,需要存储在内存中)
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string Token {
+ get { return token_; }
+ set {
+ token_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "LastLoginDate" field.
+ public const int LastLoginDateFieldNumber = 2;
+ private string lastLoginDate_ = "";
+ ///
+ ///上次登录时间(只用于呈现的字符串,若界面需求需要)
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string LastLoginDate {
+ get { return lastLoginDate_; }
+ set {
+ lastLoginDate_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "RegDate" field.
+ public const int RegDateFieldNumber = 3;
+ private string regDate_ = "";
+ ///
+ ///注册时间(只用于呈现的字符串,若界面需求需要)
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string RegDate {
+ get { return regDate_; }
+ set {
+ regDate_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "Status" field.
+ public const int StatusFieldNumber = 4;
+ private global::AxibugProtobuf.LoginResultStatus status_ = global::AxibugProtobuf.LoginResultStatus.BaseDefault;
+ ///
+ ///账号状态 (预留) [1]正常[0]被禁封
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public global::AxibugProtobuf.LoginResultStatus Status {
+ get { return status_; }
+ set {
+ status_ = value;
+ }
+ }
+
+ /// Field number for the "UID" field.
+ public const int UIDFieldNumber = 5;
+ private long uID_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public long UID {
+ get { return uID_; }
+ set {
+ uID_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override bool Equals(object other) {
+ return Equals(other as Protobuf_Login_RESP);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public bool Equals(Protobuf_Login_RESP other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (Token != other.Token) return false;
+ if (LastLoginDate != other.LastLoginDate) return false;
+ if (RegDate != other.RegDate) return false;
+ if (Status != other.Status) return false;
+ if (UID != other.UID) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (Token.Length != 0) hash ^= Token.GetHashCode();
+ if (LastLoginDate.Length != 0) hash ^= LastLoginDate.GetHashCode();
+ if (RegDate.Length != 0) hash ^= RegDate.GetHashCode();
+ if (Status != global::AxibugProtobuf.LoginResultStatus.BaseDefault) hash ^= Status.GetHashCode();
+ if (UID != 0L) hash ^= UID.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (Token.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(Token);
+ }
+ if (LastLoginDate.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(LastLoginDate);
+ }
+ if (RegDate.Length != 0) {
+ output.WriteRawTag(26);
+ output.WriteString(RegDate);
+ }
+ if (Status != global::AxibugProtobuf.LoginResultStatus.BaseDefault) {
+ output.WriteRawTag(32);
+ output.WriteEnum((int) Status);
+ }
+ if (UID != 0L) {
+ output.WriteRawTag(40);
+ output.WriteInt64(UID);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (Token.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(Token);
+ }
+ if (LastLoginDate.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(LastLoginDate);
+ }
+ if (RegDate.Length != 0) {
+ output.WriteRawTag(26);
+ output.WriteString(RegDate);
+ }
+ if (Status != global::AxibugProtobuf.LoginResultStatus.BaseDefault) {
+ output.WriteRawTag(32);
+ output.WriteEnum((int) Status);
+ }
+ if (UID != 0L) {
+ output.WriteRawTag(40);
+ output.WriteInt64(UID);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int CalculateSize() {
+ int size = 0;
+ if (Token.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(Token);
+ }
+ if (LastLoginDate.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(LastLoginDate);
+ }
+ if (RegDate.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(RegDate);
+ }
+ if (Status != global::AxibugProtobuf.LoginResultStatus.BaseDefault) {
+ size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Status);
+ }
+ if (UID != 0L) {
+ size += 1 + pb::CodedOutputStream.ComputeInt64Size(UID);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(Protobuf_Login_RESP other) {
+ if (other == null) {
+ return;
+ }
+ if (other.Token.Length != 0) {
+ Token = other.Token;
+ }
+ if (other.LastLoginDate.Length != 0) {
+ LastLoginDate = other.LastLoginDate;
+ }
+ if (other.RegDate.Length != 0) {
+ RegDate = other.RegDate;
+ }
+ if (other.Status != global::AxibugProtobuf.LoginResultStatus.BaseDefault) {
+ Status = other.Status;
+ }
+ if (other.UID != 0L) {
+ UID = other.UID;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 10: {
+ Token = input.ReadString();
+ break;
+ }
+ case 18: {
+ LastLoginDate = input.ReadString();
+ break;
+ }
+ case 26: {
+ RegDate = input.ReadString();
+ break;
+ }
+ case 32: {
+ Status = (global::AxibugProtobuf.LoginResultStatus) input.ReadEnum();
+ break;
+ }
+ case 40: {
+ UID = input.ReadInt64();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 10: {
+ Token = input.ReadString();
+ break;
+ }
+ case 18: {
+ LastLoginDate = input.ReadString();
+ break;
+ }
+ case 26: {
+ RegDate = input.ReadString();
+ break;
+ }
+ case 32: {
+ Status = (global::AxibugProtobuf.LoginResultStatus) input.ReadEnum();
+ break;
+ }
+ case 40: {
+ UID = input.ReadInt64();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ public sealed partial class Protobuf_Screnn_Frame : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Protobuf_Screnn_Frame());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::AxibugProtobuf.ProtobufAxibugEmuOnlineReflection.Descriptor.MessageTypes[4]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_Screnn_Frame() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_Screnn_Frame(Protobuf_Screnn_Frame other) : this() {
+ frameID_ = other.frameID_;
+ rawBitmap_ = other.rawBitmap_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public Protobuf_Screnn_Frame Clone() {
+ return new Protobuf_Screnn_Frame(this);
+ }
+
+ /// Field number for the "FrameID" field.
+ public const int FrameIDFieldNumber = 1;
+ private int frameID_;
+ ///
+ ///帧编号
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int FrameID {
+ get { return frameID_; }
+ set {
+ frameID_ = value;
+ }
+ }
+
+ /// Field number for the "RawBitmap" field.
+ public const int RawBitmapFieldNumber = 2;
+ private pb::ByteString rawBitmap_ = pb::ByteString.Empty;
+ ///
+ ///渲染层画面
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public pb::ByteString RawBitmap {
+ get { return rawBitmap_; }
+ set {
+ rawBitmap_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override bool Equals(object other) {
+ return Equals(other as Protobuf_Screnn_Frame);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public bool Equals(Protobuf_Screnn_Frame other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (FrameID != other.FrameID) return false;
+ if (RawBitmap != other.RawBitmap) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (FrameID != 0) hash ^= FrameID.GetHashCode();
+ if (RawBitmap.Length != 0) hash ^= RawBitmap.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (FrameID != 0) {
+ output.WriteRawTag(8);
+ output.WriteInt32(FrameID);
+ }
+ if (RawBitmap.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteBytes(RawBitmap);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (FrameID != 0) {
+ output.WriteRawTag(8);
+ output.WriteInt32(FrameID);
+ }
+ if (RawBitmap.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteBytes(RawBitmap);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int CalculateSize() {
+ int size = 0;
+ if (FrameID != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(FrameID);
+ }
+ if (RawBitmap.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeBytesSize(RawBitmap);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(Protobuf_Screnn_Frame other) {
+ if (other == null) {
+ return;
+ }
+ if (other.FrameID != 0) {
+ FrameID = other.FrameID;
+ }
+ if (other.RawBitmap.Length != 0) {
+ RawBitmap = other.RawBitmap;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ FrameID = input.ReadInt32();
+ break;
+ }
+ case 18: {
+ RawBitmap = input.ReadBytes();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ FrameID = input.ReadInt32();
+ break;
+ }
+ case 18: {
+ RawBitmap = input.ReadBytes();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ #endregion
+
+}
+
+#endregion Designer generated code
diff --git a/AxibugEmuOnline.Client/Assets/Script/Protobuf/ProtobufAxibugEmuOnline.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Protobuf/ProtobufAxibugEmuOnline.cs.meta
new file mode 100644
index 0000000..fd85ae8
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/Protobuf/ProtobufAxibugEmuOnline.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 49b94fc04c9e78343a56ad63eda4db56
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/Assets/Script/UNESTest.cs b/AxibugEmuOnline.Client/Assets/Script/UNESTest.cs
new file mode 100644
index 0000000..1776792
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/UNESTest.cs
@@ -0,0 +1,45 @@
+using UnityEngine;
+using AxibugEmuOnline.Client.UNES;
+using AxibugEmuOnline.Client.ClientCore;
+
+namespace AxibugEmuOnline.Client.Sample
+{
+ public class UNESTest : MonoBehaviour
+ {
+ public string RomFile;
+
+ public bool PlayerP1;
+
+ public static UNESTest instance;
+ public UNESBehaviour UNES { get; set; }
+
+ public void Awake()
+ {
+ Screen.SetResolution(640, 480,false);
+
+ instance = this;
+ AppAxibugEmuOnline.Init();
+ AppAxibugEmuOnline.Connect("127.0.0.1", 10492);
+ }
+
+ public void Start()
+ {
+ UNES = GetComponent();
+ if (PlayerP1)
+ {
+ var data = Resources.Load(RomFile).bytes;
+ UNES.Boot(data);
+ }
+ else
+ {
+ UNES.Boot_Obs();
+ }
+ }
+
+ public void OnDisable()
+ {
+ AppAxibugEmuOnline.Close();
+ }
+
+ }
+}
diff --git a/AxibugEmuOnline.Client/Assets/Script/UNESTest.cs.meta b/AxibugEmuOnline.Client/Assets/Script/UNESTest.cs.meta
new file mode 100644
index 0000000..5c34fdc
--- /dev/null
+++ b/AxibugEmuOnline.Client/Assets/Script/UNESTest.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8e94e6b3864b9034b89b70aa411a2894
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/AxibugEmuOnline.Client/AxibugEmuOnline.Client.sln b/AxibugEmuOnline.Client/AxibugEmuOnline.Client.sln
new file mode 100644
index 0000000..84d1c1e
--- /dev/null
+++ b/AxibugEmuOnline.Client/AxibugEmuOnline.Client.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AxibugEmuOnline.Client.UNES", "AxibugEmuOnline.Client.UNES.csproj", "{02571108-3006-F6EF-03C9-9DCFC8015A55}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {02571108-3006-F6EF-03C9-9DCFC8015A55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {02571108-3006-F6EF-03C9-9DCFC8015A55}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {02571108-3006-F6EF-03C9-9DCFC8015A55}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {02571108-3006-F6EF-03C9-9DCFC8015A55}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/README.md b/README.md
index 8c754d4..e8f1112 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,60 @@
# AxibugEmuOnline
-用于游戏模拟器同步的联机.Net实现
\ No newline at end of file
+用于游戏模拟器同步的联机.Net实现
+
+
+AxibugEmuOnline.Server 是服务端 .Net 8
+
+AxibugEmuOnline.Client 是客户端 Unity
+
+
+
+
+##就是一种联机方式的探索
+
+验证了一下 把模拟器帧缓存 走公网同步,实现联机的另一种方式
+
+云游戏,但是不是视频流的方式,是同步模拟器帧缓存,+GZIP压缩。NES这种低分辨率+颜色查找表的方式。画面传输只需要9k/s
+
+
+##TODO:
+
+1.目前只同步了画面,操作CMD同步还没做。
+
+2.以及多用户自行创建房间,和玩家选择要加入的房间列表还没做。
+
+
+
+##简述客户端逻辑:
+
+Player1主机才跑模拟器实例,然后Player1 会把渲染层的数据上报服务器。服务器广播。
+
+Player2即二号手柄玩家,不运行模拟器实例,画面渲染来自网络同步的数据。
+
+*之前试过直接上报渲染层,但是这样会有6w左右大小的uint[]
+
+*初步优化之后,采用只上报每一个像素对应颜色查找表的下标,这样就是一个byte[]了
+
+*PorotoBuf 传输使用的是bytes,但是Porotbuff只会对数组里每一个byte进行位压缩,整个byte[]不压缩。于是C#先GZIP压缩之后,在扔给protobuf。对面再解压。超级马里奥最复杂的画面情况是9k每秒的样子/。
+
+
+
+##引用
+
+
+###本项目使用,我自构建的HaoYueNet高性能网络库作为基础而开发
+
+[HaoYueNet-Github](https://github.com/Sin365/HaoYueNet "HaoYueNet-Github")
+
+[HaoYueNet-自建Git站点](http://git.axibug.com/sin365/HaoYueNet "HaoYueNet-自建Git站点")
+
+###模拟器内核
+
+模拟器内核采用 Emulator.NES https://github.com/Xyene/Emulator.NES
+
+这是一个单机的 NES模拟器C#实现,我在此基础上做修改
+
+做帧缓,颜色查找缓存,存同步,加上网络库,实现服务端。达到如上效果。
+
+
+