commit bcc5fe1bcb9d8b838552fed514221ea29d6513ae
Author: Alexa <53471801+Alexa-RR@users.noreply.github.com>
Date: Thu May 21 13:26:46 2026 +0300
Add initial documentation and structure for DorkNet project
- Create README.md for project overview and setup instructions
- Add admin-ui/README.md for admin UI details and development setup
- Introduce client-mod/README.md explaining the MelonLoader mod functionality
- Establish docker/README.md for Docker setup and configuration
- Create docs/README.md to index project documentation
- Implement advanced-setup.md for Docker stack configuration
- Document architecture in architecture.md
- Provide easy-setup.md for hosting instructions
- Add FAQ.md for common questions and legal considerations
- Create joining-a-server.md for player connection instructions
- Establish troubleshooting.md for common issues and resolutions
- Introduce easy-app/README.md for the unified desktop application
- Add server/README.md for ASP.NET Core backend details
- Document tools/README.md for CLI helper scripts
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6cb2c37
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,52 @@
+# Build outputs
+**/bin/
+**/obj/
+**/node_modules/
+**/dist/
+**/.vite/
+**/build/
+
+# IDE
+.vs/
+.vscode/
+*.user
+*.suo
+*.csproj.user
+
+# Local config (NEVER commit)
+appsettings.json
+appsettings.Local.json
+appsettings.Development.json
+.env
+.env.local
+.env.*.local
+
+# Runtime data
+data/*.db
+data/*.db-shm
+data/*.db-wal
+data/*.db.bak.*
+data/images/
+keys/
+**/MelonLoader/UserData/dorknet-clientmod.json
+**/MelonLoader/Latest.log
+**/MelonLoader/Logs/
+**/output_log.txt
+**/crash_reports/
+
+# Decompilation dumps — never ship Rec Room IP derivative works
+Cpp2IL_ISIL/
+Cpp2IL_CS/
+**/Recroom_Release_Data/
+**/Recroom_Release.exe
+**/GameAssembly.dll
+**/global-metadata.dat
+**/version.dll
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Test outputs
+TestResults/
+*.coverage
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..c4ce71b
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,55 @@
+# Contributing to DorkNet
+
+Thanks for your interest. A few ground rules before you open a PR.
+
+## Code style
+
+- **C#**: nullable enabled. Match the file you're editing. No regions, no
+ multi-paragraph XML docs, no async-without-await.
+- **TypeScript / React**: existing patterns — function components, named
+ exports, tailwind classes, no CSS modules. Lint with `npm run lint` from
+ `admin-ui/`.
+- **No AI-co-author trailers in commit messages.** Use a real attribution
+ line or none at all.
+
+## Branches and commits
+
+- `main` is protected; PR from a feature branch.
+- Conventional commits welcome but not required. One-line summary should
+ describe the *why*, not the *what* — the diff already shows what.
+
+## Scope of contributions we welcome
+
+- Bug fixes against documented behavior
+- Feature parity with the 2020 client (anything reverse-engineered cleanly)
+- Performance work
+- Better docs, screenshots, video walkthroughs
+- New translations of the admin UI
+
+## Scope of contributions we don't accept
+
+- **Rec Room game assets, decompiled source, or modified game binaries.**
+ We never ship Rec Room IP. See [DISCLAIMER.md](DISCLAIMER.md). PRs that
+ add these files will be closed.
+- **Cheats, exploits, or features that would harm other public Rec Room
+ servers.** This project exists to let people self-host the 2020 backend
+ for themselves; it isn't a vehicle for griefing live games.
+- **Crypto / NFT / monetization plugins.** Hard no.
+
+## Reporting security issues
+
+Don't open a public issue. Email the maintainer at the address in the repo
+metadata, or use GitHub's private vulnerability reporting.
+
+## Setting up a dev environment
+
+See [docs/advanced-setup.md](docs/advanced-setup.md) for the full server
+stack. For just hacking on the admin UI:
+
+```bash
+cd admin-ui
+npm install
+npm run dev
+```
+
+Points at `http://localhost:5000` by default — start the server first.
diff --git a/DISCLAIMER.md b/DISCLAIMER.md
new file mode 100644
index 0000000..5739afb
--- /dev/null
+++ b/DISCLAIMER.md
@@ -0,0 +1,50 @@
+# Disclaimer
+
+DorkNet is an independent, fan-made, clean-room reimplementation of the
+backend protocol used by the **2020 release of Rec Room** (build
+`Recroom_Release` dated 2020.03.10).
+
+It is **not** affiliated with, endorsed by, or sponsored by:
+
+- Rec Room Inc.
+- Against Gravity (the original developer)
+- Photon (Exit Games GmbH)
+- Steam / Valve Corporation
+- Meta / Oculus
+
+**Rec Room™**, the Rec Room logo, and all in-game art, audio, models,
+locations, characters, and brand assets are the intellectual property of
+Rec Room Inc. and are used here only for descriptive interoperability.
+
+## What DorkNet does and doesn't ship
+
+DorkNet's repository contains:
+- Original server code (ASP.NET Core)
+- Original admin tooling (React)
+- Original MelonLoader mod (C#)
+- Patcher scripts that operate on the user's own local Rec Room install
+
+DorkNet's repository **does not** contain:
+- The Rec Room game executable, assets, or any redistributable Rec Room IP
+- Decompiled Rec Room source or IL2CPP dumps
+- Patched copies of `Recroom_Release.exe`, `global-metadata.dat`, or any
+ Rec Room game file
+
+You must acquire the 2020.03.10 Rec Room client yourself through whatever
+means are legal in your jurisdiction (e.g. SteamDB depot tools against an
+account that owns Rec Room). The patcher runs on your machine, modifies
+files locally, and never uploads anything anywhere.
+
+## Use at your own risk
+
+This is hobby software. There are no warranties of any kind. If running
+DorkNet violates a license you agreed to (e.g. Steam Subscriber Agreement,
+Rec Room Terms of Service), that's between you and the rights holder.
+DorkNet's authors take no responsibility for consequences arising from
+your use of the software.
+
+## Takedown requests
+
+If you represent Rec Room Inc. or another rights holder and wish to discuss
+this project, please open an issue on the public repository and we will
+respond promptly.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..be3f7b2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 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 Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are 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.
+
+ 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.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ 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 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 work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ 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 AGPL, see
+.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..59c9b5f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,84 @@
+# DorkNet
+
+A self-hostable reimplementation of the 2020 Rec Room backend. Bring your own
+client; run a private server for yourself and your friends.
+
+> ⚠️ **Not affiliated with Rec Room Inc. or Against Gravity.**
+> See [DISCLAIMER.md](DISCLAIMER.md). DorkNet ships no Rec Room game assets or
+> modified binaries — you supply your own legally-acquired Rec Room 2020.03.10
+> install.
+
+---
+
+## Two ways to run it
+
+### 🎮 Easy mode — desktop app
+
+One download. Double-click. Choose "host" or "join" on first launch.
+
+- **Host a server** for you and your friends — runs a local server + tunnel,
+ patches your Rec Room install, and gives you a shareable join code.
+- **Join a server** someone shared with you — paste the code, point at your
+ Rec Room install, hit play.
+
+→ **[Easy setup guide](docs/easy-setup.md)** — screenshots, takes 5 minutes.
+
+[Download the latest Easy app →](https://github.com/YOUR_GH_USERNAME/dorknet/releases/latest)
+
+### 🛠️ Advanced mode — Docker
+
+For people running a community server, doing custom modding, or contributing
+back. Postgres + Redis + the ASP.NET Core server behind an nginx reverse proxy.
+
+```bash
+git clone https://github.com/YOUR_GH_USERNAME/dorknet
+cd dorknet/docker
+cp .env.example .env # fill in 2-3 values
+docker compose up -d
+```
+
+→ **[Advanced setup guide](docs/advanced-setup.md)**
+
+---
+
+## What works / what doesn't
+
+| Feature | Status |
+| -------------------------------- | ------ |
+| Account creation + login | ✅ |
+| Dorms (per-player save) | ✅ |
+| Rec Room Originals (RecCenter, Paintball, Stunt Runner, etc.) | ✅ |
+| Maker Pen (build + save rooms) | ✅ |
+| Photon Cloud matchmaking | ✅ |
+| Room chat | ✅ |
+| DM / group chat | ✅ |
+| Friend lists + invites | ✅ |
+| Leaderboards | ✅ |
+| Player profiles + cheer badges | ✅ |
+| Store catalog + gifting | ✅ |
+| Game invites | ✅ |
+| Image upload (polaroids, room thumbnails) | ✅ |
+| VR (Quest/Index/etc.) | ⚠️ Photon-dependent; works in dev, untested at scale |
+| Mobile / Junior accounts | ⚠️ Partial — Junior chat-restrictions disabled by default in Easy mode |
+| Voice chat | ❌ Routes through Photon Voice; needs separate Photon Voice AppId |
+| Cross-server federation | ❌ Out of scope |
+
+---
+
+## License & legal
+
+- **Code**: [AGPL-3.0](LICENSE). Hosted forks must publish their source.
+- **"Rec Room"** is a trademark of Rec Room Inc. See [DISCLAIMER.md](DISCLAIMER.md).
+- DorkNet is a clean-room reimplementation of the 2020 backend protocol for
+ educational and personal use.
+
+---
+
+## Docs
+
+- [Easy setup](docs/easy-setup.md)
+- [Advanced setup](docs/advanced-setup.md)
+- [Joining a server](docs/joining-a-server.md)
+- [Architecture](docs/architecture.md)
+- [FAQ](docs/faq.md)
+- [Contributing](CONTRIBUTING.md)
diff --git a/admin-ui/README.md b/admin-ui/README.md
new file mode 100644
index 0000000..af71836
--- /dev/null
+++ b/admin-ui/README.md
@@ -0,0 +1,36 @@
+# admin-ui/
+
+React + Vite + Tailwind SPA. Talks to `/api/admin/v1/*` on the server.
+
+Embedded into:
+1. The standalone server's `wwwroot/admin/` (advanced mode), served at
+ `https://admin./`.
+2. The Easy desktop app via a Photino WebView (host mode admin tab).
+
+## Dev
+
+```bash
+npm install
+npm run dev # http://localhost:5173, proxies /api → :5000
+```
+
+Start the server first; Vite's proxy doesn't synthesize endpoints.
+
+## Build
+
+```bash
+npm run build # dist/ — copied into server/wwwroot/admin by the
+ # server's CI publish step, and into easy-app/Resources/
+ # by tools/build-easy-app.ps1
+```
+
+## Stack notes
+
+- React 18 + Vite 5 + TypeScript
+- Tailwind 3 with a custom dark `ink/brand` palette in `tailwind.config.js`
+- React Router 6
+- No external component library; primitives in `src/components/`
+- No state library beyond per-page `useState` + `useApi`
+
+See [docs/architecture.md](../docs/architecture.md) for how the SPA is
+delivered in both modes.
diff --git a/client-mod/README.md b/client-mod/README.md
new file mode 100644
index 0000000..1c39188
--- /dev/null
+++ b/client-mod/README.md
@@ -0,0 +1,57 @@
+# client-mod/
+
+MelonLoader 0.6.x mod that retargets the 2020 Rec Room client at a
+DorkNet server. Compiles to `DorkNet.ClientMod.dll`, which the patcher
+drops into the user's `Recroom_Release/Mods/` folder.
+
+## What it does
+
+Harmony patches applied at boot:
+
+- **`Uri ctor`** — rewrites every `.rec.net` URL to your configured
+ server host. Catches every hardcoded API endpoint the watch ships with.
+- **`PhotonNetwork.ConnectUsingSettings`** — overrides the baked-in
+ Photon Cloud AppId with yours.
+- **`NetworkingPeer.CallAuthenticate`** — injects `userid` + `LoginLock`
+ into Photon's CustomAuth so your server can identify the player.
+- **`BouncyCastle.Tls.*NotifyServerCertificate`** — bypasses TLS chain
+ validation. The 2020 client only trusts a baked-in cert set; we replace
+ the verifier so any cert from your server is accepted. (Mitigated by
+ also force-routing only to your configured host.)
+- **`PlayerEmotes.RemoveInvalidCharactersFromMessage`** — skips the
+ TMP font-validation step that NREs when the font asset isn't loaded
+ yet (private servers don't always have it cached at chat-send time).
+- **`AccountExtensions.CanLocalPlayerChat`** — forces chat-enabled for
+ Junior / restricted accounts (single-tenant private server, no chat
+ moderation needed).
+
+See `Mod.cs` for the full patch list with citations to the Cpp2IL
+disassembly that justifies each one.
+
+## Config
+
+The mod reads `MelonLoader/UserData/dorknet-clientmod.json`:
+
+```json
+{
+ "ServerHost": "abc-def.trycloudflare.com",
+ "PhotonAppId": "your-photon-realtime-appid",
+ "PhotonCloudRegion": "eu",
+ "InjectAuthValues": true,
+ "EnableTlsTrustBypass": true
+}
+```
+
+The patcher (desktop app and CLI) writes this file as part of the patch
+flow — users don't normally edit it by hand.
+
+## Building
+
+```bash
+dotnet build -c Release
+# → bin/Release/DorkNet.ClientMod.dll
+```
+
+Requires Il2Cpp interop assemblies from the user's MelonLoader install.
+Path defaults to `dist/RecRoom-Clean-2020.03.10/MelonLoader/`; override
+with `-p:MelonLoaderDir=`.
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 0000000..7c317ed
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,22 @@
+# docker/
+
+The Docker Compose stack for advanced mode. See
+[docs/advanced-setup.md](../docs/advanced-setup.md) for the full
+walkthrough.
+
+```
+docker/
+├── docker-compose.yml Postgres + Redis + server + nginx
+├── docker-compose.cloudflared.yml Optional: replace nginx with CF Tunnel
+├── Dockerfile.server Multi-stage: SDK build → admin-ui build → runtime
+├── nginx.conf Subdomain routing for *.your-domain
+└── .env.example Required environment variables
+```
+
+Quick start:
+
+```bash
+cp .env.example .env
+$EDITOR .env
+docker compose up -d
+```
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..54192c6
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,10 @@
+# DorkNet docs
+
+| Doc | For who |
+| -------------------------------------- | ---------------------------------------------------- |
+| [easy-setup.md](easy-setup.md) | Hosts who want the desktop-app one-click experience |
+| [advanced-setup.md](advanced-setup.md) | Hosts running a community server on a VPS |
+| [joining-a-server.md](joining-a-server.md) | Players whose friend gave them a join code |
+| [architecture.md](architecture.md) | Anyone hacking on the code |
+| [faq.md](faq.md) | Common questions before you ask in issues |
+| [troubleshooting.md](troubleshooting.md) | Known failure modes + fixes |
diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md
new file mode 100644
index 0000000..939589a
--- /dev/null
+++ b/docs/advanced-setup.md
@@ -0,0 +1,129 @@
+# Advanced mode — Docker stack
+
+For hosts running a community server (more than ~10 concurrent players),
+doing custom modding work, or contributing to DorkNet itself.
+
+If you just want to play with friends, the [easy-setup](easy-setup.md)
+desktop app is what you want.
+
+## What you get
+
+- **server**: the ASP.NET Core backend
+- **postgres**: the canonical store (the desktop app uses SQLite; for >10
+ concurrent players, Postgres is what you want)
+- **redis**: cross-replica presence cache (skip with `REDIS_URL=` empty
+ to fall back to in-memory; loses presence on restart but otherwise fine)
+- **nginx**: TLS termination + multi-subdomain routing
+ (`api.*`, `auth.*`, `accounts.*`, `match.*`, `chat.*`, `cdn.*`, `commerce.*`,
+ `img.*`, `leaderboard.*`, `notify.*`)
+
+Behind a proper TLS certificate (Let's Encrypt via Caddy or Cloudflare
+Tunnel) this should comfortably run 50+ concurrent players on a $20/mo VPS.
+
+## Prerequisites
+
+- A Linux VPS (Debian 12 / Ubuntu 22.04 / etc.)
+- A domain you control with wildcard DNS pointing at the VPS
+ (`*.your-server.example.com → 1.2.3.4`)
+- Docker + Docker Compose
+
+## Setup
+
+```bash
+git clone https://github.com/YOUR_GH_USERNAME/dorknet
+cd dorknet/docker
+
+cp .env.example .env
+$EDITOR .env # fill in the values noted below
+```
+
+### Required `.env` values
+
+```env
+# Your domain. Server expects to find itself at *.DORKNET_DOMAIN.
+DORKNET_DOMAIN=your-server.example.com
+
+# A long random string. Used to sign JWTs.
+JWT_SECRET=$(openssl rand -base64 64)
+
+# Postgres password. Random, written once, kept in this file.
+POSTGRES_PASSWORD=$(openssl rand -base64 32)
+
+# Photon Cloud AppIds. Free tier at https://dashboard.photonengine.com
+PHOTON_APP_ID=your-photon-realtime-appid
+PHOTON_VOICE_APP_ID=your-photon-voice-appid # optional
+PHOTON_CLOUD_REGION=eu # eu, us, asia, jp, sa, kr, in, au, etc.
+
+# Steam Web API key — only needed if you want Steam logins to work.
+# Free at https://steamcommunity.com/dev/apikey
+STEAM_API_KEY=
+```
+
+### TLS
+
+The bundled nginx config terminates TLS but expects certificates to exist
+on the host. You have two reasonable paths:
+
+1. **Caddy in front** (recommended for new hosts) — replace the nginx
+ service with a Caddyfile; Caddy auto-fetches Let's Encrypt certs.
+2. **Cloudflare Tunnel** — DNS your wildcard at Cloudflare, run
+ `cloudflared` as another container, never expose port 443 to the
+ internet. The repo includes a sample `docker-compose.cloudflared.yml`.
+
+### Run it
+
+```bash
+docker compose up -d
+docker compose logs -f server
+```
+
+First boot does:
+
+1. Schema initialization (`EnsureCreated` from the entity classes)
+2. Legacy data upgrades (no-op on a fresh install)
+3. Seeds RR-Original rooms + store catalog
+4. Applies canonical room overrides (Crescendo, Paintball merge, etc.)
+5. Downloads the bundled room thumbnail images
+
+You should see `[boot] migrations complete` within ~3 seconds. Then the
+server is ready at `https://api.your-server.example.com/healthz`.
+
+## Patching the client to point here
+
+Same patcher as Easy mode, but you'll run it from the command line:
+
+```powershell
+.\tools\patch-client.ps1 `
+ -RecRoomDir "C:\path\to\Recroom_Release" `
+ -ServerHost "your-server.example.com" `
+ -PhotonAppId "your-photon-realtime-appid" `
+ -PhotonRegion "eu"
+```
+
+## Admin UI
+
+`https://admin.your-server.example.com` — log in with the first account
+you create (it auto-promotes to admin) or with the bootstrap credentials
+in your `.env` if you set `BOOTSTRAP_ADMIN_*`.
+
+## Backups
+
+Postgres is the only stateful service.
+
+```bash
+docker compose exec postgres pg_dump -U dorknet dorknet > backup.sql
+```
+
+`data/images/` on the host is the image blob store. Back that up too.
+
+## Updating
+
+```bash
+cd dorknet
+git pull
+docker compose pull
+docker compose up -d
+```
+
+The server runs all idempotent migrations on boot, so version upgrades
+are zero-downtime as long as the schema changes are additive.
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 0000000..d5779cb
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,71 @@
+# Architecture
+
+```
+┌──────────────────────────────────────────────────────────────────┐
+│ Rec Room 2020.03.10 client (patched) │
+│ ┌────────────────────┐ ┌──────────────────────────────────┐ │
+│ │ IL2CPP game code │ │ MelonLoader + client-mod │ │
+│ │ • set_BaseUri │←───│ • rewrites *.rec.net hosts │ │
+│ │ • Photon client │ │ • injects Photon AppId │ │
+│ │ • BouncyCastle TLS│ │ • bypasses TLS trust check │ │
+│ └─────────┬──────────┘ └──────────────────────────────────┘ │
+└────────────┼─────────────────────────────────────────────────────┘
+ │ HTTPS to *.your-server.example.com
+ ▼
+┌──────────────────────────────────────────────────────────────────┐
+│ DorkNet server (ASP.NET Core) │
+│ │
+│ Subdomain-multiplexed routing: │
+│ api.* → REST endpoints (rooms, players, store, etc.) │
+│ auth.* → OAuth-style /connect/token, /photon/customauth │
+│ accounts.* → /account/bulk, /account/me, /account/{id}/bio │
+│ match.* → /goto/room/*, /player/heartbeat, /roominstance/*│
+│ chat.* → /thread, /thread/{id}/message │
+│ cdn.* → static asset serving (room blobs, images) │
+│ notify.* → SignalR hub for push notifications │
+│ img.* → signed image fetch + on-the-fly resize │
+│ leaderboard.* → /leaderboard/SetStat, GetPlayerRank, etc. │
+│ admin.* → React SPA + /api/admin/v1/* │
+│ │
+│ Storage: │
+│ • Postgres (advanced mode) / SQLite (easy mode) │
+│ • Redis presence cache (advanced) / in-memory (easy) │
+│ • data/images/ — room thumbnails + player polaroids │
+└──────────────┬───────────────────────────────────────────────────┘
+ │
+ │ The server does NOT host Photon. Players' clients
+ │ connect to Photon Cloud directly via the AppId
+ │ the server hands them.
+ ▼
+┌──────────────────────────────────────────────────────────────────┐
+│ Photon Cloud (external — Exit Games) │
+│ Each Rec Room instance = one Photon room. Photon handles │
+│ realtime sync between clients in the same instance. │
+│ Server only touches Photon to authenticate connections via │
+│ /photon/customauth. │
+└──────────────────────────────────────────────────────────────────┘
+```
+
+## Why subdomains?
+
+The 2020 Rec Room client routes every request through a hardcoded
+service map (`RecNet.Core.SendRequest(method, Service.Match, …)`) that
+picks a host per service. We mirror the official `*.rec.net` layout so
+the watch's URI synthesis works unmodified after a single root-host
+rewrite by the client mod.
+
+## Why SignalR?
+
+The 2020 client expects WebSocket push notifications from a long-poll-or-
+WebSocket "Notifications" service. SignalR's transport negotiation
+matches what the client expects after a small DTO-shape wrap; see
+`Services/NotificationService.cs`.
+
+## Why a separate Photon Cloud?
+
+The 2020 build is tightly coupled to Photon Realtime SDK 4 — replacing it
+would mean shipping a custom game client. Using Photon Cloud as-is means
+clients talk peer-to-peer through Photon's relay; our server only
+witnesses the auth handshake. That's also why voice chat needs a Photon
+Voice AppId — voice runs through Photon Voice (a separate service from
+Photon Realtime).
diff --git a/docs/easy-setup.md b/docs/easy-setup.md
new file mode 100644
index 0000000..f1f8527
--- /dev/null
+++ b/docs/easy-setup.md
@@ -0,0 +1,93 @@
+# Easy mode — host your own server
+
+> Built for non-technical hosts. If you can install Discord, you can do this.
+
+## What you'll need
+
+- A Windows PC (Mac and Linux support coming later)
+- A copy of **Rec Room 2020.03.10** — the specific old build DorkNet targets.
+ You acquire this yourself; see [Getting the 2020 client](#getting-the-2020-client) below.
+- ~5 GB free disk space
+- About 10 minutes
+
+## Step 1 — Download DorkNet
+
+Grab `dorknet.exe` from the [latest release](https://github.com/YOUR_GH_USERNAME/dorknet/releases/latest).
+
+> Windows SmartScreen may warn you. The exe is code-signed; click "More info"
+> → "Run anyway".
+
+## Step 2 — First launch
+
+When you open DorkNet for the first time, you'll see a welcome screen
+asking what you want to do.
+
+
+
+Click **🏠 Host a server**.
+
+## Step 3 — Patch your Rec Room install
+
+DorkNet needs to redirect Rec Room to talk to your server instead of the
+official one. This is a one-time patch.
+
+1. Click the **Patch game** tab.
+2. Click **Browse for Rec Room install...** — point it at the folder where
+ `Recroom_Release.exe` lives.
+3. Click **Patch**. Takes 30 seconds.
+
+
+
+✅ You're done with patching. You won't need to do this again unless you
+reinstall Rec Room.
+
+## Step 4 — Start the server + tunnel
+
+1. Click **Start server** on the Status tab.
+2. After ~5 seconds you'll see a public URL like
+ `https://abc-def-ghi.trycloudflare.com`. Anyone with that URL + a
+ matching join code can connect to you.
+
+
+
+## Step 5 — Share the join code with your friends
+
+1. Click the **Share** tab.
+2. Click **Copy join code**.
+3. Paste it to your friend in Discord / iMessage / wherever.
+
+That's it for hosting. Your friend opens DorkNet, picks "Join a server",
+pastes the code, and they're in.
+
+---
+
+## Getting the 2020 client
+
+DorkNet doesn't ship the Rec Room game itself — that's Rec Room Inc.'s
+intellectual property. You acquire your own copy through legal means.
+
+Common paths:
+- If you bought Rec Room on Steam before 2020, you can use SteamDB depot
+ tools to download the old build (manifest `4651244957411961725` for
+ `Recroom_Release_Data`).
+- Friends who already have a 2020 install can share files with you under
+ whatever license terms apply on their end.
+
+DorkNet's authors can't help you obtain the game. Search the web for
+"Steam depot 2020.03.10 Rec Room" if you need pointers.
+
+---
+
+## Troubleshooting
+
+See [troubleshooting.md](troubleshooting.md) for common issues.
+
+The most common ones:
+- **"This site can't be reached" when joining** — your tunnel URL changed.
+ Get the new one from the Status tab and share it again. (Cloudflare
+ Quick Tunnels give a new URL each session. See
+ [advanced-setup.md](advanced-setup.md) if you want a stable one.)
+- **Friend can't connect** — they need to use the same Photon region as
+ you. Settings → Photon region. Default is `eu`.
+- **Game launches but stays on the loading screen** — your friend isn't
+ patched. Make sure they ran the Patch tab in their copy of DorkNet.
diff --git a/docs/faq.md b/docs/faq.md
new file mode 100644
index 0000000..f9fc3ed
--- /dev/null
+++ b/docs/faq.md
@@ -0,0 +1,90 @@
+# FAQ
+
+### Is this legal?
+
+The DorkNet code itself is original and AGPL-licensed. The Rec Room game
+client is not ours — you supply your own copy that you've legally
+acquired. The patcher runs locally on your machine and never uploads
+modified game files anywhere.
+
+DorkNet's authors don't ship Rec Room IP and don't help users obtain it.
+See [DISCLAIMER.md](../DISCLAIMER.md) for the full position.
+
+### Will Rec Room Inc. shut this down?
+
+Possibly. Fan-server projects exist in a grey zone. AGPL doesn't shield
+from DMCA. We minimize friction by never distributing Rec Room IP, never
+interfering with their live servers, and being explicit that this is for
+educational / private use only.
+
+### Why the 2020 build specifically? Will newer versions work?
+
+2020.03.10 is the last build before Rec Room shipped server-side
+ObscuredInt anti-cheat changes that would require deeper client patching.
+It's old enough to be effectively abandonware on Rec Room's roadmap, new
+enough to have most of the features people remember.
+
+Newer builds (any Rec Room from late 2020 onward) aren't supported and
+won't be without significant additional reverse-engineering. PRs that
+add support for newer builds are welcome but expect scope creep.
+
+### Can I run a public server that anyone can join?
+
+Technically yes (just disable signup-disabled in admin settings). Legally
+risky — see the C&D answer above. Realistically, "public" private
+servers attract attention and shorten the project's lifespan.
+
+We recommend keeping servers small (friends + community), private
+(invite-only signups), and not advertised.
+
+### Will mobile / Junior accounts work?
+
+Mobile binaries from 2020 are platform-locked to App Store / Play Store
+servers and can't easily be retargeted. Quest standalone is possible in
+principle but hasn't been validated.
+
+Junior accounts work but DorkNet patches out the chat-permission gate by
+default since on a private server with friends, the chat moderation
+plumbing is overkill. Toggle in admin → Server settings if you need it.
+
+### Does this work with Quest VR?
+
+Desktop VR via SteamVR works. Standalone Quest builds aren't easily
+patchable (sideloaded APK signing is its own world). Quest Link / Air
+Link puts you back in the SteamVR case, which works.
+
+### How many players can one server handle?
+
+- **Easy mode (SQLite, single-instance)**: ~30 concurrent players in
+ practice. Photon does the heavy realtime lifting; the bottleneck is the
+ server's SignalR fanout + DB writes, both of which SQLite struggles
+ with past ~50.
+- **Advanced mode (Postgres + Redis + multi-replica)**: tested at 200+
+ in dev. Real bottleneck becomes Photon AppId quotas at that point.
+
+### Can I federate multiple DorkNet servers?
+
+Not supported and not planned. Each server has its own account database;
+players are server-local. Same as classic Minecraft realms.
+
+### Is there a Discord?
+
+No official one yet. If you start a community, link it from your fork.
+
+### What about voice chat?
+
+Routes through Photon Voice. You'll need a (free) Photon Voice AppId
+separate from your Photon Realtime AppId. Set `PHOTON_VOICE_APP_ID` in
+the server config; the client mod handles the rest.
+
+### My friend can't connect
+
+Most common causes, in order:
+1. **Tunnel URL stale.** Cloudflare Quick Tunnels rotate the URL on each
+ restart. Re-share the join code.
+2. **Photon region mismatch.** Both clients must use the same Photon
+ region — set in Settings.
+3. **Friend didn't patch.** They need to run the Patch tab pointed at
+ their own Rec Room install, with your join code.
+4. **Junior account / accessibility settings.** Server settings → relax
+ moderation gates if friends are on accounts with parental controls.
diff --git a/docs/joining-a-server.md b/docs/joining-a-server.md
new file mode 100644
index 0000000..d4ab35f
--- /dev/null
+++ b/docs/joining-a-server.md
@@ -0,0 +1,67 @@
+# Joining someone's DorkNet server
+
+> Built for players whose friend already runs a server and gave them a
+> code. If you want to *host* instead, see [easy-setup.md](easy-setup.md).
+
+## What you'll need
+
+- A Windows PC
+- A copy of **Rec Room 2020.03.10** (your own — see
+ [Getting the 2020 client](easy-setup.md#getting-the-2020-client))
+- The join code your friend gave you (looks like `dorknet://join?...`)
+- About 2 minutes
+
+## Step 1 — Download DorkNet
+
+Grab `dorknet.exe` from the [latest release](https://github.com/YOUR_GH_USERNAME/dorknet/releases/latest).
+
+## Step 2 — First launch
+
+On the welcome screen, click **🎮 Join a server**.
+
+
+
+## Step 3 — Paste the join code
+
+Your friend's code goes in the big text box. DorkNet decodes it and shows
+you what you're about to connect to so you can sanity-check:
+
+
+
+Make sure the server name matches what your friend told you. Codes from
+strangers can point anywhere — only paste codes from people you trust.
+
+## Step 4 — Patch your Rec Room install
+
+1. Click **Browse for Rec Room install...** — point at the folder with
+ `Recroom_Release.exe`.
+2. Click **Patch & launch**.
+
+DorkNet patches the game (one-time), then launches it pointed at your
+friend's server. You'll see the normal Rec Room login screen, log in with
+the account you made on your friend's server, and you're in.
+
+---
+
+## I already patched for a different server. Can I switch?
+
+Yes — DorkNet's patcher is reversible. Click **Repair / unpatch** in the
+Patch tab to undo, then re-patch with a new join code. Or just re-run
+**Patch & launch** with the new code; the patcher detects an existing
+patch and updates the destination.
+
+## My account doesn't exist on the new server
+
+Each DorkNet server runs its own account database. You'll need to create
+a new account the first time you join a server. Your friend can sign you
+up via their admin panel, or you can use the in-game "Create account"
+flow if the server has signups enabled.
+
+## Voice chat doesn't work
+
+Voice routes through Photon Voice, which requires a separate Photon Voice
+AppId. If your friend hasn't set one up, voice chat is text-only for now.
+
+## More troubleshooting
+
+See [troubleshooting.md](troubleshooting.md).
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
new file mode 100644
index 0000000..49d547a
--- /dev/null
+++ b/docs/troubleshooting.md
@@ -0,0 +1,96 @@
+# Troubleshooting
+
+If your symptom isn't here, check [the FAQ](faq.md) and then open an
+issue with: your OS, DorkNet version, server log (Settings → Send logs
+to clipboard), and the exact steps that triggered the problem.
+
+## Client / patcher
+
+### "Signature verification failed" on launch
+
+The image-signing public key baked into the patched `global-metadata.dat`
+doesn't match the private key on the server. Repair from the Patch tab —
+this re-fetches the server's current pubkey before patching.
+
+### "Check your internet connection" loop at startup
+
+The watch can't reach the server URL. Verify:
+1. The host string in `dorknet-clientmod.json` matches the server's
+ public URL (tunnel URL for Easy mode).
+2. The server responds to `https:///healthz` with `200 OK` from
+ the same machine the client runs on.
+3. No corporate proxy / DNS filter is intercepting `*.trycloudflare.com`.
+
+### Black screen after the loading bar fills
+
+The watch authenticated, joined Photon, but couldn't download the room
+data blob. Look in MelonLoader's `Latest.log` for
+`[DownloadRoomDataBlobCoroutine]` lines — the URL there tells you which
+asset the server failed to serve.
+
+### Save doesn't persist, MakerPen objects disappear after reload
+
+Known issue with stale `LocalRoomScene.DataBlobName`. The client mod's
+`SetRoomDataBlobName_Prefix` hijack should handle this — verify it's
+firing in MelonLoader's log. If not, the mod failed to load; check that
+`Mods\DorkNet.ClientMod.dll` is present and not blocked by Windows.
+
+## Server
+
+### `[boot] migrations failed`
+
+The schema patch SQL errored. Most likely cause: an existing column whose
+type doesn't match what we're trying to add. Look at the exact failure
+in the log; if it's a Postgres `cannot alter type` error, the prior
+column type needs a manual migration before DorkNet starts.
+
+### Photon CustomAuth returns 401
+
+Either:
+- The Photon AppId in `appsettings` doesn't match the AppId baked into
+ the patched client. They MUST match.
+- The user logging in doesn't have a row in the `Players` table — the
+ auth flow expects `userid` to be a real account.
+
+### Room chat doesn't send
+
+Run the client with `SaveDebugMod` enabled (or watch MelonLoader's log)
+and look for the `[chat-trace]` lines. The pipeline is:
+
+```
+SendChatEmote → RemoveInvalidCharactersFromMessage → PurifyString HTTP
+ → ProcessNewChatMessageReceived → Photon RPC
+```
+
+If any trace line is missing, that's where it died. The two most common
+fixes:
+- `[chat-fix] CanLocalPlayerChat returned false`: the account is flagged
+ as a Junior with chat disabled. Force-true via the client mod (default
+ behavior) or fix it server-side in admin.
+- `Received malformed RecNet response`: the `/api/sanitize/v1` endpoint
+ returned the wrong shape. Should be a bare JSON-quoted string. See
+ `SanitizeController.cs`.
+
+## Easy app
+
+### "Cloudflared exited unexpectedly"
+
+The bundled `cloudflared.exe` couldn't start. Most likely a Windows
+Defender false-positive — check Defender's quarantine. If you find a
+cleaner alternative tunnel provider, open an issue; we've been looking
+for one.
+
+### Patch button is greyed out
+
+DorkNet didn't find a Rec Room install at the path you selected. Make
+sure you pointed at the *folder* containing `Recroom_Release.exe`, not
+the exe itself, and that `Recroom_Release_Data/` exists alongside.
+
+### Mode switch lost my server data
+
+Switching from Host mode → Join mode preserves the SQLite database; it
+just stops auto-starting the server. Switch back to Host mode and your
+players / rooms / images are still there.
+
+If the data really is gone, check `%APPDATA%\DorkNet\dorknet.db` — that
+file is the entire host-side state, easy to copy elsewhere as a backup.
diff --git a/easy-app/README.md b/easy-app/README.md
new file mode 100644
index 0000000..af6894d
--- /dev/null
+++ b/easy-app/README.md
@@ -0,0 +1,112 @@
+# DorkNet Easy App
+
+The unified desktop app for hosts and guests. One binary, two modes.
+
+## Project layout
+
+```
+easy-app/
+├── Shell/ PhotinoNET window host, mode router, settings store,
+│ auto-updater, system tray, log viewer.
+├── HostMode/ Tunnel manager (cloudflared wrapper), server lifecycle
+│ (start/stop the in-process ASP.NET Core), share-code
+│ generation.
+├── JoinMode/ Join-code parser (dorknet:// URL handler), connection
+│ sanity-check, server reachability ping.
+├── Shared/ Patcher library (port of tools/patch-client.ps1 to C#),
+│ ConfigManager, RecRoomInstallFinder (Steam library scan),
+│ ImageSigningKeyManager (auto-generate on first run).
+└── Resources/ Icons, admin-ui dist (bundled at build time),
+ cloudflared.exe, MelonLoader/, mod DLLs.
+```
+
+## Build flow
+
+`Resources/admin-ui-dist/` is the production build output from
+`../admin-ui/` — copied in by a pre-build task so the Photino webview can
+serve it as the host-mode admin tab.
+
+```pwsh
+# One-shot dev build
+pwsh tools/build-easy-app.ps1
+
+# Release artifact
+dotnet publish easy-app -c Release -r win-x64 --self-contained \
+ -p:PublishSingleFile=true -p:PublishTrimmed=false
+# → bin/Release/net9.0/win-x64/publish/dorknet.exe (~80 MB)
+```
+
+## Why PhotinoNET
+
+- The admin UI is already a webview-friendly React SPA. Embedding it in a
+ Photino window is essentially free.
+- Single .NET project. No Node toolchain in the desktop build, no Electron.
+- Bundled binary ~50 MB before the runtime, ~80 MB with self-contained
+ .NET. Acceptable for one-time downloads.
+
+## Why not Electron / Tauri / Avalonia
+
+- **Electron**: doubles the binary size, adds Node into the supply chain.
+- **Tauri**: Rust dependency we don't otherwise need; mixing it with the
+ existing .NET server complicates the build matrix.
+- **Avalonia**: native, beautiful, but means re-implementing the admin
+ UI a second time. The point of unified Host mode is *reuse* the admin
+ SPA verbatim.
+
+## Modes — first-run decision
+
+`Shared/ConfigManager` writes `%APPDATA%\DorkNet\mode.json`:
+
+```json
+{ "mode": "host" }
+```
+
+`Shell` reads this on startup. Missing or `null` → first-run wizard.
+Settings → "Switch mode" sets `mode` to the other value, restarts the
+process clean (Photino re-init).
+
+## Host mode lifecycle
+
+1. User clicks Start server.
+2. `HostMode.ServerLauncher` boots the ASP.NET Core host in-process,
+ wired to a SQLite DB at `%APPDATA%\DorkNet\dorknet.db`.
+3. `HostMode.TunnelManager` spawns `cloudflared tunnel --url
+ http://localhost:5000`, reads the public URL off stderr.
+4. UI surfaces the URL + a "Copy join code" button. Join code is
+ base64(`{host, photonAppId, photonRegion, name}`).
+
+## Join mode lifecycle
+
+1. User pastes a join code (or clicks a `dorknet://` URL — the installer
+ registers the protocol handler).
+2. `JoinMode.CodeParser` decodes, ping-checks the host, shows a
+ "Connecting to X" preview.
+3. User points at their Rec Room install (auto-detected via
+ `RecRoomInstallFinder` if Steam owns it).
+4. `Shared.Patcher` patches `global-metadata.dat`, drops MelonLoader,
+ writes `dorknet-clientmod.json` with the decoded host + Photon AppId.
+5. UI offers a "Launch Rec Room" button — runs `Recroom_Release.exe`
+ directly, no Steam intermediary needed.
+
+## Auto-updates
+
+[Velopack](https://velopack.io) is the path of least resistance for .NET
+desktop. Drop-in: declare a GitHub Release feed, ship updates as deltas.
+
+## Testing
+
+QA matrix to maintain:
+- Fresh Windows 10 VM, no Rec Room → install Steam → buy / depot-download
+ the 2020 build → DorkNet host mode → patch + start + tunnel → connect
+ from a second VM via Join mode. End-to-end smoke test.
+- Mac M1 + Linux: deferred. PhotinoNET supports them but we need separate
+ cloudflared binaries and a packaging step per OS.
+
+## Open questions
+
+- [ ] Code-signing cert. SmartScreen warning is a major adoption friction.
+ EV cert is ~$200/yr; standard cert is cheaper but takes longer to build
+ reputation.
+- [ ] Crash reporting. Sentry has a generous free tier and a clean .NET SDK.
+- [ ] Telemetry — opt-in? Off by default? Right now: no plans, but
+ document the position so contributors know.
diff --git a/server/README.md b/server/README.md
new file mode 100644
index 0000000..2195600
--- /dev/null
+++ b/server/README.md
@@ -0,0 +1,57 @@
+# server/
+
+The ASP.NET Core backend.
+
+Built two ways:
+- **Standalone Postgres-backed** for advanced mode (Docker), via
+ `dotnet run` or the `dorknet-server` container.
+- **In-process SQLite-backed** for the Easy desktop app, hosted as a
+ library inside `easy-app/`.
+
+## Code layout (TBC)
+
+```
+server/
+├── Controllers/ HTTP endpoints, sliced by subdomain → service
+├── Services/ Business logic (presence, notifications, levels, ...)
+├── Hubs/ SignalR NotifyHub
+├── Data/ DbContext + EntityFramework entity classes
+├── Auth/ JWT issuance, Photon CustomAuth handler
+├── Middleware/ SubdomainRouter, request logging, error handling
+└── Program.cs Boot: migrations, seeds, canonical overrides, DI
+```
+
+## Building
+
+```bash
+dotnet build -c Release
+dotnet run # localhost:5000
+```
+
+## Provider switch
+
+The DbContext is provider-agnostic. `appsettings` picks one:
+
+```jsonc
+{
+ "ConnectionStrings": {
+ // Postgres for advanced mode
+ "Default": "Host=localhost;Database=dorknet;Username=dorknet;Password=..."
+ // OR SQLite for easy mode
+ // "Default": "Data Source=dorknet.db"
+ },
+ "Database": {
+ "Provider": "Postgres" // or "Sqlite"
+ }
+}
+```
+
+Easy app boots with `Provider=Sqlite` baked in; standalone defaults to
+`Provider=Postgres`.
+
+## See also
+
+- [docs/architecture.md](../docs/architecture.md) — request flow,
+ subdomain routing, Photon relationship
+- [docs/advanced-setup.md](../docs/advanced-setup.md) — production setup
+- [CONTRIBUTING.md](../CONTRIBUTING.md) — style + PR rules
diff --git a/tools/README.md b/tools/README.md
new file mode 100644
index 0000000..eebb7a6
--- /dev/null
+++ b/tools/README.md
@@ -0,0 +1,10 @@
+# tools/
+
+CLI helpers. Most users don't need to touch these — the desktop app
+wraps the same logic with a UI.
+
+| Script | Purpose |
+| -------------------- | ------------------------------------------------------ |
+| `patch-client.ps1` | Patch a Rec Room install to talk to your DorkNet server. The desktop app wraps this in a UI; CLI path is for advanced-mode hosts. |
+| `dump-il2cpp.ps1` | Run Cpp2IL against a Rec Room install to produce the decompiled dumps the project uses for reverse-engineering. Output stays on your machine — never committed. |
+| `build-easy-app.ps1` | Build the unified desktop app (`dorknet.exe`). Self-contained .NET single-file publish. |