diff --git a/.gitignore b/.gitignore
index 5ec172d2b..cf7b111ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,5 +27,8 @@ wiki/.vitepress/cache
# Visual Studio-created directory for... something?
/.vs
+# JetBrains Rider files
+/.idea
+
# Ignore config copies forks may keep to deploy a test instance
/wiki/.vitepress/config.*.ts
diff --git a/wiki/.assets/images/beginners-guide/BetaOptions_OculusBranch.jpg b/wiki/.assets/images/beginners-guide/BetaOptions_OculusBranch.jpg
index e88ce2859..6b6485505 100644
Binary files a/wiki/.assets/images/beginners-guide/BetaOptions_OculusBranch.jpg and b/wiki/.assets/images/beginners-guide/BetaOptions_OculusBranch.jpg differ
diff --git a/wiki/.assets/images/beginners-guide/BetaOptions_SteamBranch.jpg b/wiki/.assets/images/beginners-guide/BetaOptions_SteamBranch.jpg
index fe235b08a..2bec22f9f 100644
Binary files a/wiki/.assets/images/beginners-guide/BetaOptions_SteamBranch.jpg and b/wiki/.assets/images/beginners-guide/BetaOptions_SteamBranch.jpg differ
diff --git a/wiki/.assets/images/beginners-guide/mbf-android-modding-complete.png b/wiki/.assets/images/beginners-guide/mbf-android-modding-complete.png
index 3da940774..a48cbb100 100644
Binary files a/wiki/.assets/images/beginners-guide/mbf-android-modding-complete.png and b/wiki/.assets/images/beginners-guide/mbf-android-modding-complete.png differ
diff --git a/wiki/.assets/images/beginners-guide/mbf-android-preferences-changed.png b/wiki/.assets/images/beginners-guide/mbf-android-preferences-changed.png
index 952b3e202..043168e1f 100644
Binary files a/wiki/.assets/images/beginners-guide/mbf-android-preferences-changed.png and b/wiki/.assets/images/beginners-guide/mbf-android-preferences-changed.png differ
diff --git a/wiki/.assets/images/beginners-guide/mbf-android-quest-found.png b/wiki/.assets/images/beginners-guide/mbf-android-quest-found.png
index 4edcdf91f..fcb8562d5 100644
Binary files a/wiki/.assets/images/beginners-guide/mbf-android-quest-found.png and b/wiki/.assets/images/beginners-guide/mbf-android-quest-found.png differ
diff --git a/wiki/.assets/images/beginners-guide/mbf-android-ready-to-mod.png b/wiki/.assets/images/beginners-guide/mbf-android-ready-to-mod.png
index ea0b29535..c4f86bffc 100644
Binary files a/wiki/.assets/images/beginners-guide/mbf-android-ready-to-mod.png and b/wiki/.assets/images/beginners-guide/mbf-android-ready-to-mod.png differ
diff --git a/wiki/.assets/images/beginners-guide/mbf-android-usb-tray.png b/wiki/.assets/images/beginners-guide/mbf-android-usb-tray.png
index e2ef52509..1a347de49 100644
Binary files a/wiki/.assets/images/beginners-guide/mbf-android-usb-tray.png and b/wiki/.assets/images/beginners-guide/mbf-android-usb-tray.png differ
diff --git a/wiki/.assets/images/beginners-guide/mbfModBrowser.png b/wiki/.assets/images/beginners-guide/mbfModBrowser.png
index c518d97f2..ba3400c12 100644
Binary files a/wiki/.assets/images/beginners-guide/mbfModBrowser.png and b/wiki/.assets/images/beginners-guide/mbfModBrowser.png differ
diff --git a/wiki/.assets/images/beginners-guide/mbfReadyToMod.png b/wiki/.assets/images/beginners-guide/mbfReadyToMod.png
index 056c1e89d..dc301ca8a 100644
Binary files a/wiki/.assets/images/beginners-guide/mbfReadyToMod.png and b/wiki/.assets/images/beginners-guide/mbfReadyToMod.png differ
diff --git a/wiki/.assets/images/beginners-guide/mbfSelectDevice.png b/wiki/.assets/images/beginners-guide/mbfSelectDevice.png
index 68bca54d9..071af712e 100644
Binary files a/wiki/.assets/images/beginners-guide/mbfSelectDevice.png and b/wiki/.assets/images/beginners-guide/mbfSelectDevice.png differ
diff --git a/wiki/.assets/images/beginners-guide/sqConnected.png b/wiki/.assets/images/beginners-guide/sqConnected.png
index 28f5b977b..45b55cae5 100644
Binary files a/wiki/.assets/images/beginners-guide/sqConnected.png and b/wiki/.assets/images/beginners-guide/sqConnected.png differ
diff --git a/wiki/.assets/images/beginners-guide/squninstall.png b/wiki/.assets/images/beginners-guide/squninstall.png
index 07fa3ac18..49df6d9cd 100644
Binary files a/wiki/.assets/images/beginners-guide/squninstall.png and b/wiki/.assets/images/beginners-guide/squninstall.png differ
diff --git a/wiki/.assets/images/mapping/0-spacing.png b/wiki/.assets/images/mapping/0-spacing.png
index 0a470b191..daaedc6f8 100644
Binary files a/wiki/.assets/images/mapping/0-spacing.png and b/wiki/.assets/images/mapping/0-spacing.png differ
diff --git a/wiki/.assets/images/mapping/1-spacing.png b/wiki/.assets/images/mapping/1-spacing.png
index 7a9d5d17f..d9f42f5cc 100644
Binary files a/wiki/.assets/images/mapping/1-spacing.png and b/wiki/.assets/images/mapping/1-spacing.png differ
diff --git a/wiki/.assets/images/mapping/135_degree_cw.png b/wiki/.assets/images/mapping/135_degree_cw.png
index 7cfbae037..49c17c251 100644
Binary files a/wiki/.assets/images/mapping/135_degree_cw.png and b/wiki/.assets/images/mapping/135_degree_cw.png differ
diff --git a/wiki/.assets/images/mapping/180_degree_cw.png b/wiki/.assets/images/mapping/180_degree_cw.png
index 6796805fd..062df5a4d 100644
Binary files a/wiki/.assets/images/mapping/180_degree_cw.png and b/wiki/.assets/images/mapping/180_degree_cw.png differ
diff --git a/wiki/.assets/images/mapping/2-spacing.png b/wiki/.assets/images/mapping/2-spacing.png
index 723833d85..fdd87db32 100644
Binary files a/wiki/.assets/images/mapping/2-spacing.png and b/wiki/.assets/images/mapping/2-spacing.png differ
diff --git a/wiki/.assets/images/mapping/270_degree_cw.png b/wiki/.assets/images/mapping/270_degree_cw.png
index cec5bd4d4..e5a7d7275 100644
Binary files a/wiki/.assets/images/mapping/270_degree_cw.png and b/wiki/.assets/images/mapping/270_degree_cw.png differ
diff --git a/wiki/.assets/images/mapping/3-spacing.png b/wiki/.assets/images/mapping/3-spacing.png
index b55bb78c8..ffd653922 100644
Binary files a/wiki/.assets/images/mapping/3-spacing.png and b/wiki/.assets/images/mapping/3-spacing.png differ
diff --git a/wiki/.assets/images/mapping/45_degree_ccw.png b/wiki/.assets/images/mapping/45_degree_ccw.png
index f29c4b2b8..e295cae5d 100644
Binary files a/wiki/.assets/images/mapping/45_degree_ccw.png and b/wiki/.assets/images/mapping/45_degree_ccw.png differ
diff --git a/wiki/.assets/images/mapping/45_degree_cw.png b/wiki/.assets/images/mapping/45_degree_cw.png
index 9126b2d93..4c5517c0b 100644
Binary files a/wiki/.assets/images/mapping/45_degree_cw.png and b/wiki/.assets/images/mapping/45_degree_cw.png differ
diff --git a/wiki/.assets/images/mapping/45_degree_cw_2.png b/wiki/.assets/images/mapping/45_degree_cw_2.png
index ce6c0f63f..227214f24 100644
Binary files a/wiki/.assets/images/mapping/45_degree_cw_2.png and b/wiki/.assets/images/mapping/45_degree_cw_2.png differ
diff --git a/wiki/.assets/images/mapping/45_degree_cw_3.png b/wiki/.assets/images/mapping/45_degree_cw_3.png
index 53a584381..edfefccaa 100644
Binary files a/wiki/.assets/images/mapping/45_degree_cw_3.png and b/wiki/.assets/images/mapping/45_degree_cw_3.png differ
diff --git a/wiki/.assets/images/mapping/45_degree_cw_4.png b/wiki/.assets/images/mapping/45_degree_cw_4.png
index 61d8f0ec8..f469a45d7 100644
Binary files a/wiki/.assets/images/mapping/45_degree_cw_4.png and b/wiki/.assets/images/mapping/45_degree_cw_4.png differ
diff --git a/wiki/.assets/images/mapping/angle_enforcement.png b/wiki/.assets/images/mapping/angle_enforcement.png
index 2e451e35b..f493dc384 100644
Binary files a/wiki/.assets/images/mapping/angle_enforcement.png and b/wiki/.assets/images/mapping/angle_enforcement.png differ
diff --git a/wiki/.assets/images/mapping/apex.png b/wiki/.assets/images/mapping/apex.png
index 3c2af51f3..9ee3e0a6e 100644
Binary files a/wiki/.assets/images/mapping/apex.png and b/wiki/.assets/images/mapping/apex.png differ
diff --git a/wiki/.assets/images/mapping/beatbombreset.png b/wiki/.assets/images/mapping/beatbombreset.png
index fcfe68d6b..98acaaa6f 100644
Binary files a/wiki/.assets/images/mapping/beatbombreset.png and b/wiki/.assets/images/mapping/beatbombreset.png differ
diff --git a/wiki/.assets/images/mapping/beatbombreset_top.png b/wiki/.assets/images/mapping/beatbombreset_top.png
index 9c722ca17..9455ef2ec 100644
Binary files a/wiki/.assets/images/mapping/beatbombreset_top.png and b/wiki/.assets/images/mapping/beatbombreset_top.png differ
diff --git a/wiki/.assets/images/mapping/bomb-spiral-path.png b/wiki/.assets/images/mapping/bomb-spiral-path.png
index 119ba54f9..49a0d64d1 100644
Binary files a/wiki/.assets/images/mapping/bomb-spiral-path.png and b/wiki/.assets/images/mapping/bomb-spiral-path.png differ
diff --git a/wiki/.assets/images/mapping/bombhold.png b/wiki/.assets/images/mapping/bombhold.png
index 16234396e..dac9defa8 100644
Binary files a/wiki/.assets/images/mapping/bombhold.png and b/wiki/.assets/images/mapping/bombhold.png differ
diff --git a/wiki/.assets/images/mapping/bottom-reset.png b/wiki/.assets/images/mapping/bottom-reset.png
index c6686bc51..7de9ad88d 100644
Binary files a/wiki/.assets/images/mapping/bottom-reset.png and b/wiki/.assets/images/mapping/bottom-reset.png differ
diff --git a/wiki/.assets/images/mapping/burst.png b/wiki/.assets/images/mapping/burst.png
index a8169387d..ccc47f742 100644
Binary files a/wiki/.assets/images/mapping/burst.png and b/wiki/.assets/images/mapping/burst.png differ
diff --git a/wiki/.assets/images/mapping/burst_top.png b/wiki/.assets/images/mapping/burst_top.png
index fd6c72151..0dc3825e7 100644
Binary files a/wiki/.assets/images/mapping/burst_top.png and b/wiki/.assets/images/mapping/burst_top.png differ
diff --git a/wiki/.assets/images/mapping/chain_cm.png b/wiki/.assets/images/mapping/chain_cm.png
index 2dfe8e162..624cfec63 100644
Binary files a/wiki/.assets/images/mapping/chain_cm.png and b/wiki/.assets/images/mapping/chain_cm.png differ
diff --git a/wiki/.assets/images/mapping/circle.png b/wiki/.assets/images/mapping/circle.png
index 8aa59a50a..5928a14fb 100644
Binary files a/wiki/.assets/images/mapping/circle.png and b/wiki/.assets/images/mapping/circle.png differ
diff --git a/wiki/.assets/images/mapping/cosmetic-bomb-bad.png b/wiki/.assets/images/mapping/cosmetic-bomb-bad.png
index f38d4589d..5a2c0a1a2 100644
Binary files a/wiki/.assets/images/mapping/cosmetic-bomb-bad.png and b/wiki/.assets/images/mapping/cosmetic-bomb-bad.png differ
diff --git a/wiki/.assets/images/mapping/crouch-signposting.png b/wiki/.assets/images/mapping/crouch-signposting.png
index 7469994ae..12b815d10 100644
Binary files a/wiki/.assets/images/mapping/crouch-signposting.png and b/wiki/.assets/images/mapping/crouch-signposting.png differ
diff --git a/wiki/.assets/images/mapping/flick-alt.png b/wiki/.assets/images/mapping/flick-alt.png
index ebb001987..738b9e7ec 100644
Binary files a/wiki/.assets/images/mapping/flick-alt.png and b/wiki/.assets/images/mapping/flick-alt.png differ
diff --git a/wiki/.assets/images/mapping/flick-alt_top.png b/wiki/.assets/images/mapping/flick-alt_top.png
index f481e1cea..68411e97c 100644
Binary files a/wiki/.assets/images/mapping/flick-alt_top.png and b/wiki/.assets/images/mapping/flick-alt_top.png differ
diff --git a/wiki/.assets/images/mapping/gallop.png b/wiki/.assets/images/mapping/gallop.png
index 6f75b162b..2db0a42da 100644
Binary files a/wiki/.assets/images/mapping/gallop.png and b/wiki/.assets/images/mapping/gallop.png differ
diff --git a/wiki/.assets/images/mapping/gallop_top.png b/wiki/.assets/images/mapping/gallop_top.png
index 293cbccd5..b86531468 100644
Binary files a/wiki/.assets/images/mapping/gallop_top.png and b/wiki/.assets/images/mapping/gallop_top.png differ
diff --git a/wiki/.assets/images/mapping/good-crouch.png b/wiki/.assets/images/mapping/good-crouch.png
index ef7528514..f0cd670c0 100644
Binary files a/wiki/.assets/images/mapping/good-crouch.png and b/wiki/.assets/images/mapping/good-crouch.png differ
diff --git a/wiki/.assets/images/mapping/halfonehand.png b/wiki/.assets/images/mapping/halfonehand.png
index 99aef5e6c..b566b7e18 100644
Binary files a/wiki/.assets/images/mapping/halfonehand.png and b/wiki/.assets/images/mapping/halfonehand.png differ
diff --git a/wiki/.assets/images/mapping/halfonehand_top.png b/wiki/.assets/images/mapping/halfonehand_top.png
index 7f974af18..fef38787e 100644
Binary files a/wiki/.assets/images/mapping/halfonehand_top.png and b/wiki/.assets/images/mapping/halfonehand_top.png differ
diff --git a/wiki/.assets/images/mapping/halfspeedstream.png b/wiki/.assets/images/mapping/halfspeedstream.png
index 92564108c..bbb6a5a71 100644
Binary files a/wiki/.assets/images/mapping/halfspeedstream.png and b/wiki/.assets/images/mapping/halfspeedstream.png differ
diff --git a/wiki/.assets/images/mapping/halfspeedstream_top.png b/wiki/.assets/images/mapping/halfspeedstream_top.png
index 2884b82a6..9135f5802 100644
Binary files a/wiki/.assets/images/mapping/halfspeedstream_top.png and b/wiki/.assets/images/mapping/halfspeedstream_top.png differ
diff --git a/wiki/.assets/images/mapping/hard-dodge-wall.png b/wiki/.assets/images/mapping/hard-dodge-wall.png
index 43540b1a5..540fca2c1 100644
Binary files a/wiki/.assets/images/mapping/hard-dodge-wall.png and b/wiki/.assets/images/mapping/hard-dodge-wall.png differ
diff --git a/wiki/.assets/images/mapping/intended_rhythm.png b/wiki/.assets/images/mapping/intended_rhythm.png
index a0d91e818..a540bbc17 100644
Binary files a/wiki/.assets/images/mapping/intended_rhythm.png and b/wiki/.assets/images/mapping/intended_rhythm.png differ
diff --git a/wiki/.assets/images/mapping/inverted-swing.png b/wiki/.assets/images/mapping/inverted-swing.png
index 2e8c0140e..38192ac51 100644
Binary files a/wiki/.assets/images/mapping/inverted-swing.png and b/wiki/.assets/images/mapping/inverted-swing.png differ
diff --git a/wiki/.assets/images/mapping/lean_backhand.png b/wiki/.assets/images/mapping/lean_backhand.png
index af94b6514..5786a0a23 100644
Binary files a/wiki/.assets/images/mapping/lean_backhand.png and b/wiki/.assets/images/mapping/lean_backhand.png differ
diff --git a/wiki/.assets/images/mapping/lean_forehand.png b/wiki/.assets/images/mapping/lean_forehand.png
index 777ca53a5..9bc09ca51 100644
Binary files a/wiki/.assets/images/mapping/lean_forehand.png and b/wiki/.assets/images/mapping/lean_forehand.png differ
diff --git a/wiki/.assets/images/mapping/left-lean.png b/wiki/.assets/images/mapping/left-lean.png
index 82defaa01..ee6fd1d9b 100644
Binary files a/wiki/.assets/images/mapping/left-lean.png and b/wiki/.assets/images/mapping/left-lean.png differ
diff --git a/wiki/.assets/images/mapping/left_backhand_ccw.png b/wiki/.assets/images/mapping/left_backhand_ccw.png
index 071d30a49..67eefdf2b 100644
Binary files a/wiki/.assets/images/mapping/left_backhand_ccw.png and b/wiki/.assets/images/mapping/left_backhand_ccw.png differ
diff --git a/wiki/.assets/images/mapping/left_backhand_cw.png b/wiki/.assets/images/mapping/left_backhand_cw.png
index 61746c1db..bc981156e 100644
Binary files a/wiki/.assets/images/mapping/left_backhand_cw.png and b/wiki/.assets/images/mapping/left_backhand_cw.png differ
diff --git a/wiki/.assets/images/mapping/left_forehand_ccw.png b/wiki/.assets/images/mapping/left_forehand_ccw.png
index 8ca3b02e6..758b17b89 100644
Binary files a/wiki/.assets/images/mapping/left_forehand_ccw.png and b/wiki/.assets/images/mapping/left_forehand_ccw.png differ
diff --git a/wiki/.assets/images/mapping/left_forehand_cw.png b/wiki/.assets/images/mapping/left_forehand_cw.png
index 99aed0e74..a85569b31 100644
Binary files a/wiki/.assets/images/mapping/left_forehand_cw.png and b/wiki/.assets/images/mapping/left_forehand_cw.png differ
diff --git a/wiki/.assets/images/mapping/momentum-circle.png b/wiki/.assets/images/mapping/momentum-circle.png
index 9347caed1..cfd0c356e 100644
Binary files a/wiki/.assets/images/mapping/momentum-circle.png and b/wiki/.assets/images/mapping/momentum-circle.png differ
diff --git a/wiki/.assets/images/mapping/momentum-lol.png b/wiki/.assets/images/mapping/momentum-lol.png
index 2156609b7..7a0cd0857 100644
Binary files a/wiki/.assets/images/mapping/momentum-lol.png and b/wiki/.assets/images/mapping/momentum-lol.png differ
diff --git a/wiki/.assets/images/mapping/momentum-meh.png b/wiki/.assets/images/mapping/momentum-meh.png
index df0d1e33a..d9b383a8b 100644
Binary files a/wiki/.assets/images/mapping/momentum-meh.png and b/wiki/.assets/images/mapping/momentum-meh.png differ
diff --git a/wiki/.assets/images/mapping/momentum-nice.png b/wiki/.assets/images/mapping/momentum-nice.png
index 92f0c863e..a3bf626df 100644
Binary files a/wiki/.assets/images/mapping/momentum-nice.png and b/wiki/.assets/images/mapping/momentum-nice.png differ
diff --git a/wiki/.assets/images/mapping/momentum-no.png b/wiki/.assets/images/mapping/momentum-no.png
index e812e2594..8e786935a 100644
Binary files a/wiki/.assets/images/mapping/momentum-no.png and b/wiki/.assets/images/mapping/momentum-no.png differ
diff --git a/wiki/.assets/images/mapping/momentum-yes.png b/wiki/.assets/images/mapping/momentum-yes.png
index a0d05ef95..4fa82090a 100644
Binary files a/wiki/.assets/images/mapping/momentum-yes.png and b/wiki/.assets/images/mapping/momentum-yes.png differ
diff --git a/wiki/.assets/images/mapping/negative-curvature-135.png b/wiki/.assets/images/mapping/negative-curvature-135.png
index c306fe94d..8ee540113 100644
Binary files a/wiki/.assets/images/mapping/negative-curvature-135.png and b/wiki/.assets/images/mapping/negative-curvature-135.png differ
diff --git a/wiki/.assets/images/mapping/negative-curvature-45.png b/wiki/.assets/images/mapping/negative-curvature-45.png
index 3f159eb34..bb72815ff 100644
Binary files a/wiki/.assets/images/mapping/negative-curvature-45.png and b/wiki/.assets/images/mapping/negative-curvature-45.png differ
diff --git a/wiki/.assets/images/mapping/negative-curvature-90-ok.png b/wiki/.assets/images/mapping/negative-curvature-90-ok.png
index 57bdab02e..b3b7fd6b1 100644
Binary files a/wiki/.assets/images/mapping/negative-curvature-90-ok.png and b/wiki/.assets/images/mapping/negative-curvature-90-ok.png differ
diff --git a/wiki/.assets/images/mapping/negative-curvature-90.png b/wiki/.assets/images/mapping/negative-curvature-90.png
index 5abac4400..3943f3fcc 100644
Binary files a/wiki/.assets/images/mapping/negative-curvature-90.png and b/wiki/.assets/images/mapping/negative-curvature-90.png differ
diff --git a/wiki/.assets/images/mapping/negative-curvature-spam.png b/wiki/.assets/images/mapping/negative-curvature-spam.png
index 0bbf26b7f..f2e2ab93b 100644
Binary files a/wiki/.assets/images/mapping/negative-curvature-spam.png and b/wiki/.assets/images/mapping/negative-curvature-spam.png differ
diff --git a/wiki/.assets/images/mapping/no-lean.png b/wiki/.assets/images/mapping/no-lean.png
index 1f0b20a9d..52333410c 100644
Binary files a/wiki/.assets/images/mapping/no-lean.png and b/wiki/.assets/images/mapping/no-lean.png differ
diff --git a/wiki/.assets/images/mapping/no_rotation.png b/wiki/.assets/images/mapping/no_rotation.png
index 60c838e7f..e4c763f0c 100644
Binary files a/wiki/.assets/images/mapping/no_rotation.png and b/wiki/.assets/images/mapping/no_rotation.png differ
diff --git a/wiki/.assets/images/mapping/non-inverted-swing.png b/wiki/.assets/images/mapping/non-inverted-swing.png
index dbb416bc4..974ae7f0a 100644
Binary files a/wiki/.assets/images/mapping/non-inverted-swing.png and b/wiki/.assets/images/mapping/non-inverted-swing.png differ
diff --git a/wiki/.assets/images/mapping/onehandjumps.png b/wiki/.assets/images/mapping/onehandjumps.png
index d80ac2571..fa916f9a9 100644
Binary files a/wiki/.assets/images/mapping/onehandjumps.png and b/wiki/.assets/images/mapping/onehandjumps.png differ
diff --git a/wiki/.assets/images/mapping/onehandjumps_top.png b/wiki/.assets/images/mapping/onehandjumps_top.png
index 80ec65f83..8e32ca6bf 100644
Binary files a/wiki/.assets/images/mapping/onehandjumps_top.png and b/wiki/.assets/images/mapping/onehandjumps_top.png differ
diff --git a/wiki/.assets/images/mapping/positive-curvature-spam.png b/wiki/.assets/images/mapping/positive-curvature-spam.png
index b3a1cd7fa..0351d395a 100644
Binary files a/wiki/.assets/images/mapping/positive-curvature-spam.png and b/wiki/.assets/images/mapping/positive-curvature-spam.png differ
diff --git a/wiki/.assets/images/mapping/right-lean.png b/wiki/.assets/images/mapping/right-lean.png
index b4bae761a..49ac2ad54 100644
Binary files a/wiki/.assets/images/mapping/right-lean.png and b/wiki/.assets/images/mapping/right-lean.png differ
diff --git a/wiki/.assets/images/mapping/right_backhand_ccw.png b/wiki/.assets/images/mapping/right_backhand_ccw.png
index 1b6bac238..d27d05d94 100644
Binary files a/wiki/.assets/images/mapping/right_backhand_ccw.png and b/wiki/.assets/images/mapping/right_backhand_ccw.png differ
diff --git a/wiki/.assets/images/mapping/right_backhand_cw.png b/wiki/.assets/images/mapping/right_backhand_cw.png
index 910f687a3..1dc34629a 100644
Binary files a/wiki/.assets/images/mapping/right_backhand_cw.png and b/wiki/.assets/images/mapping/right_backhand_cw.png differ
diff --git a/wiki/.assets/images/mapping/right_forehand_ccw.png b/wiki/.assets/images/mapping/right_forehand_ccw.png
index 382b01d05..839c299b6 100644
Binary files a/wiki/.assets/images/mapping/right_forehand_ccw.png and b/wiki/.assets/images/mapping/right_forehand_ccw.png differ
diff --git a/wiki/.assets/images/mapping/right_forehand_cw.png b/wiki/.assets/images/mapping/right_forehand_cw.png
index dd024fbf4..cb1576cae 100644
Binary files a/wiki/.assets/images/mapping/right_forehand_cw.png and b/wiki/.assets/images/mapping/right_forehand_cw.png differ
diff --git a/wiki/.assets/images/mapping/scoop.png b/wiki/.assets/images/mapping/scoop.png
index 619dc7abe..3761a51df 100644
Binary files a/wiki/.assets/images/mapping/scoop.png and b/wiki/.assets/images/mapping/scoop.png differ
diff --git a/wiki/.assets/images/mapping/simple-lean-wall.png b/wiki/.assets/images/mapping/simple-lean-wall.png
index df38e9b5d..e34db87f5 100644
Binary files a/wiki/.assets/images/mapping/simple-lean-wall.png and b/wiki/.assets/images/mapping/simple-lean-wall.png differ
diff --git a/wiki/.assets/images/mapping/simple-sway-wall.png b/wiki/.assets/images/mapping/simple-sway-wall.png
index f6a1346de..3221ae9fb 100644
Binary files a/wiki/.assets/images/mapping/simple-sway-wall.png and b/wiki/.assets/images/mapping/simple-sway-wall.png differ
diff --git a/wiki/.assets/images/mapping/single.png b/wiki/.assets/images/mapping/single.png
index 0934754e9..bc5471c73 100644
Binary files a/wiki/.assets/images/mapping/single.png and b/wiki/.assets/images/mapping/single.png differ
diff --git a/wiki/.assets/images/mapping/sliders.png b/wiki/.assets/images/mapping/sliders.png
index c20c31fc1..a99dd9bb7 100644
Binary files a/wiki/.assets/images/mapping/sliders.png and b/wiki/.assets/images/mapping/sliders.png differ
diff --git a/wiki/.assets/images/mapping/stack.png b/wiki/.assets/images/mapping/stack.png
index ee0bd2167..fe012442d 100644
Binary files a/wiki/.assets/images/mapping/stack.png and b/wiki/.assets/images/mapping/stack.png differ
diff --git a/wiki/.assets/images/mapping/stream.png b/wiki/.assets/images/mapping/stream.png
index a1f6a8ec2..61df0f6a6 100644
Binary files a/wiki/.assets/images/mapping/stream.png and b/wiki/.assets/images/mapping/stream.png differ
diff --git a/wiki/.assets/images/mapping/stream_top.png b/wiki/.assets/images/mapping/stream_top.png
index 93a8a7766..c374ce6f6 100644
Binary files a/wiki/.assets/images/mapping/stream_top.png and b/wiki/.assets/images/mapping/stream_top.png differ
diff --git a/wiki/.assets/images/mapping/sway-lean-wall.png b/wiki/.assets/images/mapping/sway-lean-wall.png
index 9f735683f..9852b4445 100644
Binary files a/wiki/.assets/images/mapping/sway-lean-wall.png and b/wiki/.assets/images/mapping/sway-lean-wall.png differ
diff --git a/wiki/.assets/images/mapping/sway.png b/wiki/.assets/images/mapping/sway.png
index 983b498d5..99d871330 100644
Binary files a/wiki/.assets/images/mapping/sway.png and b/wiki/.assets/images/mapping/sway.png differ
diff --git a/wiki/.assets/images/mapping/tangle_problem.png b/wiki/.assets/images/mapping/tangle_problem.png
index d263dbaeb..ff5b148ea 100644
Binary files a/wiki/.assets/images/mapping/tangle_problem.png and b/wiki/.assets/images/mapping/tangle_problem.png differ
diff --git a/wiki/.assets/images/mapping/tangle_resolve.png b/wiki/.assets/images/mapping/tangle_resolve.png
index f89a01ecb..d75f37a42 100644
Binary files a/wiki/.assets/images/mapping/tangle_resolve.png and b/wiki/.assets/images/mapping/tangle_resolve.png differ
diff --git a/wiki/.assets/images/mapping/top-reset.png b/wiki/.assets/images/mapping/top-reset.png
index 7d836da8b..5ec106a50 100644
Binary files a/wiki/.assets/images/mapping/top-reset.png and b/wiki/.assets/images/mapping/top-reset.png differ
diff --git a/wiki/.assets/images/mapping/tower.png b/wiki/.assets/images/mapping/tower.png
index 9c9f88a47..4ed222e9b 100644
Binary files a/wiki/.assets/images/mapping/tower.png and b/wiki/.assets/images/mapping/tower.png differ
diff --git a/wiki/.assets/images/mapping/tripletjumps.png b/wiki/.assets/images/mapping/tripletjumps.png
index a581c793d..5bc7fb3f2 100644
Binary files a/wiki/.assets/images/mapping/tripletjumps.png and b/wiki/.assets/images/mapping/tripletjumps.png differ
diff --git a/wiki/.assets/images/mapping/tripletjumps_top.png b/wiki/.assets/images/mapping/tripletjumps_top.png
index 237d6140a..6196e04d8 100644
Binary files a/wiki/.assets/images/mapping/tripletjumps_top.png and b/wiki/.assets/images/mapping/tripletjumps_top.png differ
diff --git a/wiki/.assets/images/mapping/window.png b/wiki/.assets/images/mapping/window.png
index 2dd4999ca..eaef10098 100644
Binary files a/wiki/.assets/images/mapping/window.png and b/wiki/.assets/images/mapping/window.png differ
diff --git a/wiki/.assets/images/modding/pc-mod-bsml-buttons.jpg b/wiki/.assets/images/modding/pc-mod-bsml-buttons.jpg
new file mode 100644
index 000000000..e52d6bb32
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-bsml-buttons.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-bsml-embeddedresource.jpg b/wiki/.assets/images/modding/pc-mod-bsml-embeddedresource.jpg
new file mode 100644
index 000000000..63a7250f4
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-bsml-embeddedresource.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-bsml-file.jpg b/wiki/.assets/images/modding/pc-mod-bsml-file.jpg
new file mode 100644
index 000000000..2e1e97a2e
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-bsml-file.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-bsml-floating-screen.jpg b/wiki/.assets/images/modding/pc-mod-bsml-floating-screen.jpg
new file mode 100644
index 000000000..0336b3c7b
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-bsml-floating-screen.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-bsml-settings.jpg b/wiki/.assets/images/modding/pc-mod-bsml-settings.jpg
new file mode 100644
index 000000000..7d23bbc5f
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-bsml-settings.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-bsml-tabs.jpg b/wiki/.assets/images/modding/pc-mod-bsml-tabs.jpg
new file mode 100644
index 000000000..9a32afc74
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-bsml-tabs.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-build-rider.png b/wiki/.assets/images/modding/pc-mod-build-rider.png
new file mode 100644
index 000000000..7d3aa7646
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-build-rider.png differ
diff --git a/wiki/.assets/images/modding/pc-mod-console-testing.png b/wiki/.assets/images/modding/pc-mod-console-testing.png
new file mode 100644
index 000000000..71a0b61fb
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-console-testing.png differ
diff --git a/wiki/.assets/images/modding/pc-mod-directory-rider.png b/wiki/.assets/images/modding/pc-mod-directory-rider.png
new file mode 100644
index 000000000..dc0937062
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-directory-rider.png differ
diff --git a/wiki/.assets/images/modding/pc-mod-ilspy-analyze.jpg b/wiki/.assets/images/modding/pc-mod-ilspy-analyze.jpg
new file mode 100644
index 000000000..21245c26a
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-ilspy-analyze.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-ilspy-code.jpg b/wiki/.assets/images/modding/pc-mod-ilspy-code.jpg
new file mode 100644
index 000000000..ee5b4a645
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-ilspy-code.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-ilspy-list.jpg b/wiki/.assets/images/modding/pc-mod-ilspy-list.jpg
new file mode 100644
index 000000000..9d375f879
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-ilspy-list.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-ilspy-search.jpg b/wiki/.assets/images/modding/pc-mod-ilspy-search.jpg
new file mode 100644
index 000000000..5277d4efc
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-ilspy-search.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-references-rider.png b/wiki/.assets/images/modding/pc-mod-references-rider.png
new file mode 100644
index 000000000..2da997758
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-references-rider.png differ
diff --git a/wiki/.assets/images/modding/pc-mod-rider-decompiling.jpg b/wiki/.assets/images/modding/pc-mod-rider-decompiling.jpg
new file mode 100644
index 000000000..0829fa2c6
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-rider-decompiling.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-rider-plugin.png b/wiki/.assets/images/modding/pc-mod-rider-plugin.png
new file mode 100644
index 000000000..b67f28462
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-rider-plugin.png differ
diff --git a/wiki/.assets/images/modding/pc-mod-rue1.jpg b/wiki/.assets/images/modding/pc-mod-rue1.jpg
new file mode 100644
index 000000000..a82ca7496
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-rue1.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-rue2.jpg b/wiki/.assets/images/modding/pc-mod-rue2.jpg
new file mode 100644
index 000000000..6d63b04f0
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-rue2.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-template-rider.png b/wiki/.assets/images/modding/pc-mod-template-rider.png
new file mode 100644
index 000000000..06fc1d70e
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-template-rider.png differ
diff --git a/wiki/.assets/images/modding/pc-mod-tutorial-event-analyze.jpg b/wiki/.assets/images/modding/pc-mod-tutorial-event-analyze.jpg
new file mode 100644
index 000000000..c8e048ea8
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-tutorial-event-analyze.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-tutorial-menu.jpg b/wiki/.assets/images/modding/pc-mod-tutorial-menu.jpg
new file mode 100644
index 000000000..87f198779
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-tutorial-menu.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-tutorial-pool-analyze.jpg b/wiki/.assets/images/modding/pc-mod-tutorial-pool-analyze.jpg
new file mode 100644
index 000000000..94dc2a375
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-tutorial-pool-analyze.jpg differ
diff --git a/wiki/.assets/images/modding/pc-mod-tutorial-test.jpg b/wiki/.assets/images/modding/pc-mod-tutorial-test.jpg
new file mode 100644
index 000000000..9aa62d1a5
Binary files /dev/null and b/wiki/.assets/images/modding/pc-mod-tutorial-test.jpg differ
diff --git a/wiki/.assets/images/modding/testing-console.png b/wiki/.assets/images/modding/testing-console.png
deleted file mode 100644
index 708c54e1a..000000000
Binary files a/wiki/.assets/images/modding/testing-console.png and /dev/null differ
diff --git a/wiki/.assets/images/models/materials/AudioClipDrag.png b/wiki/.assets/images/models/materials/AudioClipDrag.png
index 23c160e99..3e49dd0b0 100644
Binary files a/wiki/.assets/images/models/materials/AudioClipDrag.png and b/wiki/.assets/images/models/materials/AudioClipDrag.png differ
diff --git a/wiki/.assets/images/models/materials/AudioLinkAvatarDrag.png b/wiki/.assets/images/models/materials/AudioLinkAvatarDrag.png
index d957f38c1..1dbd66c8f 100644
Binary files a/wiki/.assets/images/models/materials/AudioLinkAvatarDrag.png and b/wiki/.assets/images/models/materials/AudioLinkAvatarDrag.png differ
diff --git a/wiki/.assets/images/models/materials/CurveExample.png b/wiki/.assets/images/models/materials/CurveExample.png
index 1fc3378fa..b31e8813f 100644
Binary files a/wiki/.assets/images/models/materials/CurveExample.png and b/wiki/.assets/images/models/materials/CurveExample.png differ
diff --git a/wiki/.assets/images/models/materials/FireTrailShaderProperties.png b/wiki/.assets/images/models/materials/FireTrailShaderProperties.png
index f2e492724..44903fea1 100644
Binary files a/wiki/.assets/images/models/materials/FireTrailShaderProperties.png and b/wiki/.assets/images/models/materials/FireTrailShaderProperties.png differ
diff --git a/wiki/.assets/images/models/materials/GlassRefractionParameters.png b/wiki/.assets/images/models/materials/GlassRefractionParameters.png
index 8f9915cc0..bf4d0b4e8 100644
Binary files a/wiki/.assets/images/models/materials/GlassRefractionParameters.png and b/wiki/.assets/images/models/materials/GlassRefractionParameters.png differ
diff --git a/wiki/.assets/images/models/materials/HeightMapExample.png b/wiki/.assets/images/models/materials/HeightMapExample.png
index 12203eb43..549e9e1b1 100644
Binary files a/wiki/.assets/images/models/materials/HeightMapExample.png and b/wiki/.assets/images/models/materials/HeightMapExample.png differ
diff --git a/wiki/.assets/images/models/materials/HeightMapExampleHard.png b/wiki/.assets/images/models/materials/HeightMapExampleHard.png
index 79b1b5adb..62acddd07 100644
Binary files a/wiki/.assets/images/models/materials/HeightMapExampleHard.png and b/wiki/.assets/images/models/materials/HeightMapExampleHard.png differ
diff --git a/wiki/.assets/images/models/materials/MelodiUnlitExample.png b/wiki/.assets/images/models/materials/MelodiUnlitExample.png
index 56afb1e55..eca3d7e85 100644
Binary files a/wiki/.assets/images/models/materials/MelodiUnlitExample.png and b/wiki/.assets/images/models/materials/MelodiUnlitExample.png differ
diff --git a/wiki/.assets/images/models/materials/RenderQueueExample.png b/wiki/.assets/images/models/materials/RenderQueueExample.png
index 32f2c1b15..bece9188f 100644
Binary files a/wiki/.assets/images/models/materials/RenderQueueExample.png and b/wiki/.assets/images/models/materials/RenderQueueExample.png differ
diff --git a/wiki/.assets/images/models/materials/RenderTypesExample.png b/wiki/.assets/images/models/materials/RenderTypesExample.png
index abd8ec56a..73eb0e933 100644
Binary files a/wiki/.assets/images/models/materials/RenderTypesExample.png and b/wiki/.assets/images/models/materials/RenderTypesExample.png differ
diff --git a/wiki/.assets/images/models/materials/UberShaderProperties.png b/wiki/.assets/images/models/materials/UberShaderProperties.png
index 4e9e7f436..6bc6e6c5a 100644
Binary files a/wiki/.assets/images/models/materials/UberShaderProperties.png and b/wiki/.assets/images/models/materials/UberShaderProperties.png differ
diff --git a/wiki/.assets/images/models/materials/UnlitGlowProperties.png b/wiki/.assets/images/models/materials/UnlitGlowProperties.png
index 4543b906c..c801c20b2 100644
Binary files a/wiki/.assets/images/models/materials/UnlitGlowProperties.png and b/wiki/.assets/images/models/materials/UnlitGlowProperties.png differ
diff --git a/wiki/.assets/images/models/materials/YoutubeLinkExample.png b/wiki/.assets/images/models/materials/YoutubeLinkExample.png
index 56be26504..a4213592d 100644
Binary files a/wiki/.assets/images/models/materials/YoutubeLinkExample.png and b/wiki/.assets/images/models/materials/YoutubeLinkExample.png differ
diff --git a/wiki/.assets/images/models/platforms/AddEventManagerScript.png b/wiki/.assets/images/models/platforms/AddEventManagerScript.png
index e17d6b348..1161cb296 100644
Binary files a/wiki/.assets/images/models/platforms/AddEventManagerScript.png and b/wiki/.assets/images/models/platforms/AddEventManagerScript.png differ
diff --git a/wiki/.assets/images/models/platforms/AddGlowModelEvents.png b/wiki/.assets/images/models/platforms/AddGlowModelEvents.png
index 8364432f8..5c6660cab 100644
Binary files a/wiki/.assets/images/models/platforms/AddGlowModelEvents.png and b/wiki/.assets/images/models/platforms/AddGlowModelEvents.png differ
diff --git a/wiki/.assets/images/models/platforms/AddTextureNode.png b/wiki/.assets/images/models/platforms/AddTextureNode.png
index 33e52d821..1da9615a5 100644
Binary files a/wiki/.assets/images/models/platforms/AddTextureNode.png and b/wiki/.assets/images/models/platforms/AddTextureNode.png differ
diff --git a/wiki/.assets/images/models/platforms/AssignGlowModelsToEvents.png b/wiki/.assets/images/models/platforms/AssignGlowModelsToEvents.png
index dbab19557..6f8c50910 100644
Binary files a/wiki/.assets/images/models/platforms/AssignGlowModelsToEvents.png and b/wiki/.assets/images/models/platforms/AssignGlowModelsToEvents.png differ
diff --git a/wiki/.assets/images/models/platforms/AvailableEventScripts.png b/wiki/.assets/images/models/platforms/AvailableEventScripts.png
index 7f5e52704..d4b1ea487 100644
Binary files a/wiki/.assets/images/models/platforms/AvailableEventScripts.png and b/wiki/.assets/images/models/platforms/AvailableEventScripts.png differ
diff --git a/wiki/.assets/images/models/platforms/BakedLightingSettings.jpg b/wiki/.assets/images/models/platforms/BakedLightingSettings.jpg
index 22a602409..a744627d5 100644
Binary files a/wiki/.assets/images/models/platforms/BakedLightingSettings.jpg and b/wiki/.assets/images/models/platforms/BakedLightingSettings.jpg differ
diff --git a/wiki/.assets/images/models/platforms/DeleteDuplicateCubes.png b/wiki/.assets/images/models/platforms/DeleteDuplicateCubes.png
index 0c337e165..aeb2cfcfc 100644
Binary files a/wiki/.assets/images/models/platforms/DeleteDuplicateCubes.png and b/wiki/.assets/images/models/platforms/DeleteDuplicateCubes.png differ
diff --git a/wiki/.assets/images/models/platforms/DragDropPrefab.png b/wiki/.assets/images/models/platforms/DragDropPrefab.png
index c1b913a97..68e82bf9c 100644
Binary files a/wiki/.assets/images/models/platforms/DragDropPrefab.png and b/wiki/.assets/images/models/platforms/DragDropPrefab.png differ
diff --git a/wiki/.assets/images/models/platforms/DragDropPrefabResult.png b/wiki/.assets/images/models/platforms/DragDropPrefabResult.png
index 01fd22749..3ef71dfcb 100644
Binary files a/wiki/.assets/images/models/platforms/DragDropPrefabResult.png and b/wiki/.assets/images/models/platforms/DragDropPrefabResult.png differ
diff --git a/wiki/.assets/images/models/platforms/DuplicateExistingPrefab.png b/wiki/.assets/images/models/platforms/DuplicateExistingPrefab.png
index 74ba8bcea..7203bd25f 100644
Binary files a/wiki/.assets/images/models/platforms/DuplicateExistingPrefab.png and b/wiki/.assets/images/models/platforms/DuplicateExistingPrefab.png differ
diff --git a/wiki/.assets/images/models/platforms/DuplicateGlowObject.png b/wiki/.assets/images/models/platforms/DuplicateGlowObject.png
index 3bd86df80..eb318b5ac 100644
Binary files a/wiki/.assets/images/models/platforms/DuplicateGlowObject.png and b/wiki/.assets/images/models/platforms/DuplicateGlowObject.png differ
diff --git a/wiki/.assets/images/models/platforms/DuplicatedGlowModelsResult.png b/wiki/.assets/images/models/platforms/DuplicatedGlowModelsResult.png
index 05259a5ba..30ce392c7 100644
Binary files a/wiki/.assets/images/models/platforms/DuplicatedGlowModelsResult.png and b/wiki/.assets/images/models/platforms/DuplicatedGlowModelsResult.png differ
diff --git a/wiki/.assets/images/models/platforms/GlowEvents.png b/wiki/.assets/images/models/platforms/GlowEvents.png
index c0eb60f7a..fa0a83f63 100644
Binary files a/wiki/.assets/images/models/platforms/GlowEvents.png and b/wiki/.assets/images/models/platforms/GlowEvents.png differ
diff --git a/wiki/.assets/images/models/platforms/GlowModels.png b/wiki/.assets/images/models/platforms/GlowModels.png
index 2dc1ba294..44057343b 100644
Binary files a/wiki/.assets/images/models/platforms/GlowModels.png and b/wiki/.assets/images/models/platforms/GlowModels.png differ
diff --git a/wiki/.assets/images/models/platforms/SelectObjectToBakeLighting.jpg b/wiki/.assets/images/models/platforms/SelectObjectToBakeLighting.jpg
index 4d4834ba1..12b0b5e6e 100644
Binary files a/wiki/.assets/images/models/platforms/SelectObjectToBakeLighting.jpg and b/wiki/.assets/images/models/platforms/SelectObjectToBakeLighting.jpg differ
diff --git a/wiki/.assets/images/models/platforms/SetActiveObject.png b/wiki/.assets/images/models/platforms/SetActiveObject.png
index 776699f33..359f6f566 100644
Binary files a/wiki/.assets/images/models/platforms/SetActiveObject.png and b/wiki/.assets/images/models/platforms/SetActiveObject.png differ
diff --git a/wiki/.assets/images/models/platforms/ShaderPerformanceExample.jpg b/wiki/.assets/images/models/platforms/ShaderPerformanceExample.jpg
index 940f90780..a753fd877 100644
Binary files a/wiki/.assets/images/models/platforms/ShaderPerformanceExample.jpg and b/wiki/.assets/images/models/platforms/ShaderPerformanceExample.jpg differ
diff --git a/wiki/.assets/images/models/platforms/TrackLanePrefabDrag.png b/wiki/.assets/images/models/platforms/TrackLanePrefabDrag.png
index 79222b883..c94012ad2 100644
Binary files a/wiki/.assets/images/models/platforms/TrackLanePrefabDrag.png and b/wiki/.assets/images/models/platforms/TrackLanePrefabDrag.png differ
diff --git a/wiki/.assets/images/models/platforms/TrackRingOrigin.png b/wiki/.assets/images/models/platforms/TrackRingOrigin.png
index e9c420f6c..4cb6becd0 100644
Binary files a/wiki/.assets/images/models/platforms/TrackRingOrigin.png and b/wiki/.assets/images/models/platforms/TrackRingOrigin.png differ
diff --git a/wiki/.assets/images/models/platforms/TrackRingOutOfView.png b/wiki/.assets/images/models/platforms/TrackRingOutOfView.png
index 9cb8bb215..c356567ec 100644
Binary files a/wiki/.assets/images/models/platforms/TrackRingOutOfView.png and b/wiki/.assets/images/models/platforms/TrackRingOutOfView.png differ
diff --git a/wiki/.assets/images/models/platforms/TrackRings.png b/wiki/.assets/images/models/platforms/TrackRings.png
index 9be11e1f8..07d31a409 100644
Binary files a/wiki/.assets/images/models/platforms/TrackRings.png and b/wiki/.assets/images/models/platforms/TrackRings.png differ
diff --git a/wiki/.assets/images/models/platforms/TrackRingsComponent.png b/wiki/.assets/images/models/platforms/TrackRingsComponent.png
index 20547a231..957212b6b 100644
Binary files a/wiki/.assets/images/models/platforms/TrackRingsComponent.png and b/wiki/.assets/images/models/platforms/TrackRingsComponent.png differ
diff --git a/wiki/.assets/images/models/platforms/cmbHumanoidPlacement.png b/wiki/.assets/images/models/platforms/cmbHumanoidPlacement.png
index 401238a4d..935fbbb13 100644
Binary files a/wiki/.assets/images/models/platforms/cmbHumanoidPlacement.png and b/wiki/.assets/images/models/platforms/cmbHumanoidPlacement.png differ
diff --git a/wiki/.assets/images/models/platforms/prefabTogameObject.png b/wiki/.assets/images/models/platforms/prefabTogameObject.png
index 6291df8c4..b249c07ac 100644
Binary files a/wiki/.assets/images/models/platforms/prefabTogameObject.png and b/wiki/.assets/images/models/platforms/prefabTogameObject.png differ
diff --git a/wiki/.assets/images/support/BlankMenuEnvironment.png b/wiki/.assets/images/support/BlankMenuEnvironment.png
index 1428376d5..8913a7b4e 100644
Binary files a/wiki/.assets/images/support/BlankMenuEnvironment.png and b/wiki/.assets/images/support/BlankMenuEnvironment.png differ
diff --git a/wiki/.assets/images/support/GrayMenuEnvironment.png b/wiki/.assets/images/support/GrayMenuEnvironment.png
index f3b5eba13..baed2cd72 100644
Binary files a/wiki/.assets/images/support/GrayMenuEnvironment.png and b/wiki/.assets/images/support/GrayMenuEnvironment.png differ
diff --git a/wiki/.assets/images/support/UnityCrashPopup.png b/wiki/.assets/images/support/UnityCrashPopup.png
index 14bb5b57b..e82e18443 100644
Binary files a/wiki/.assets/images/support/UnityCrashPopup.png and b/wiki/.assets/images/support/UnityCrashPopup.png differ
diff --git a/wiki/.vitepress/config/en.ts b/wiki/.vitepress/config/en.ts
index e14c30661..a67e65ce1 100644
--- a/wiki/.vitepress/config/en.ts
+++ b/wiki/.vitepress/config/en.ts
@@ -111,14 +111,26 @@ export const en = defineConfig({
name: 'Modding',
path: '/modding/',
routes: [
- ['PC', '/modding/pc-mod-dev-intro'],
+ [
+ 'PC',
+ '/modding/pc/setup',
+ [
+ ['Setup Guide', '/modding/pc/setup'],
+ ['RUE', '/modding/pc/rue'],
+ ['Decompiling', '/modding/pc/decompiling'],
+ ['Harmony Patching', '/modding/pc/harmony-patching'],
+ ['Creating UI', '/modding/pc/bsml'],
+ ['Zenject and SiraUtil', '/modding/pc/zenject'],
+ ['Full Guide', '/modding/pc/full-mod-guide'],
+ ],
+ ],
[
'Quest',
- '/modding/quest-mod-dev-intro',
+ '/modding/quest/intro',
[
- ['Config', '/modding/quest-mod-dev-config'],
- ['Custom Types', '/modding/quest-mod-dev-custom-types'],
- ['UI', '/modding/quest-mod-dev-ui'],
+ ['Config', '/modding/quest/config'],
+ ['Custom Types', '/modding/quest/custom-types'],
+ ['UI', '/modding/quest/ui'],
],
],
],
diff --git a/wiki/modding/index.md b/wiki/modding/index.md
index 329a63442..40e071357 100644
--- a/wiki/modding/index.md
+++ b/wiki/modding/index.md
@@ -12,50 +12,10 @@ Development for [PC](#pc-mod-development) and [Quest standalone](#quest-mod-deve
## PC Mod Development
-### Injecting Mods
+If you want to make mods for the PC version of the game, the following guide will cover a multitude of different
+processes involved in making mods from scratch, as well as some of the different APIs you have access to.
-Instead, most mods within the mod installer rely on
-[BSIPA (Beat Saber Illusion Plugin Architecture)](https://github.com/nike4613/BeatSaber-IPA-Reloaded/)
-to inject plugins into the game, as well as providing some useful tools for us modders.
-
-For those of you who prefer [BepInEx](https://github.com/BepInEx/BepInEx) over either of these options, Bepis has created
-a loader for BSIPA plugins, available [here](https://github.com/BepInEx/BepInEx.BSIPA.Loader). As for developing Beat Saber
-plugins with the BepInEx plugin API, a generic guide exists on their
-[documentation site](https://bepinex.github.io/bepinex_docs/v5.0/articles/dev_guide/plugin_tutorial/index.html),
-but other than that you're kinda on your own.
-
-### Project Setup
-
-If you are interested in creating a Beat Saber mod, but do not have a template or Visual Studio template set up,
-[follow the Intro guide to get your project all set up](./pc-mod-dev-intro.md).
-
-#### Ready to go?
-
-Check out the [links below](#other-links) for documentation relating to Unity and related tooling. If you have any questions,
-the best place to ask is in the `#pc-mod-dev` channel on the [BSMG Discord](https://discord.gg/beatsabermods)
-
-### Launch args
-
-Helpful launch arguments that will make modding / debugging easier.
-
-
-
-| Argument | Description |
-| -------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `--verbose` | Enables the output log window for IPA. This will show the debug console that mods use. |
-| `fpfc` | "First Person Flying Controller"
This allows you to use WASD and the mouse to navigate around the menu in game. This makes testing much easier, because you don't have to put on your headset! |
-| `-vrmode oculus` | If you are running Beat Saber through Steam, this allows you to play the game on an Oculus headset. |
-
-
-
-### Other Links
-
-- [BeatMods](https://beatmods.com)
-- [BeatMods Approval Guidelines](https://docs.google.com/document/d/15RBVesZdS-U94AvesJ2DJqcnAtgh9E2PZOcbjrQle5Y/edit?usp=sharing)
-- [Unity Scripting API](https://docs.unity3d.com/ScriptReference/index.html)
-- [dnSpy](https://github.com/0xd4d/dnSpy)
-- [Harmony](https://github.com/pardeike/Harmony)
-- [Beat Saber IPA](https://nike4613.github.io/BeatSaber-IPA-Reloaded/)
+Visit the [PC Mod Development](./pc/index.md) page to begin.
## Quest Mod Development
@@ -67,4 +27,4 @@ limited to:
- User Interfaces using `bsml`
- Custom types
-Visit the [Quest Mod Development Intro](./quest-mod-dev-intro.md) page for more information on getting started!
+Visit the [Quest Mod Development Intro](./quest/intro.md) page for more information on getting started!
diff --git a/wiki/modding/pc-mod-dev-intro.md b/wiki/modding/pc-mod-dev-intro.md
deleted file mode 100644
index ce7c51668..000000000
--- a/wiki/modding/pc-mod-dev-intro.md
+++ /dev/null
@@ -1,125 +0,0 @@
----
-prev: false
-next: false
-description: Learn how to create your own PC mods!
----
-
-# PC Mod Development Intro
-
-_Learn how to get started writing your own PC Mods._
-
-## Getting Started
-
-::: warning
-This guide is for making mods for the **PC** version of Beat Saber!
-
-If you want to develop mods for the **Quest Standalone** version of the game, visit the [Quest Mod Development Guide](./quest-mod-dev-intro.md)
-
-Make sure your game is modded before trying to make a mod.
-See instructions for [modding Beat Saber on PC.](/pc-modding.md)
-
-This guide assumes you have a basic to intermediate understanding of C# and Unity.
-You may have difficulty understanding what is covered here if you do not have this foundation.
-:::
-
-Beat Saber is made in Unity 2019.3 using C# with .NET framework 4.6
-You will need to download the latest version of [Visual Studio Community](https://visualstudio.microsoft.com/).
-
-## Setup Modding Tools
-
-We will be using the BeatSaberModdingTools extension in this tutorial,
-as it comes with modding templates and useful features.
-BeatSaberModdingTools is maintained by Zingabopp.
-If you find the tools to be useful, consider throwing some support their way.
-
-You can download it on their [GitHub](https://github.com/Zingabopp/BeatSaberTemplates/releases/latest).
-You will need to download `BeatSaberModdingTools.vsix`. (Expand the Assets dropdown if you cannot find it)
-
-Once downloaded, open the `.vsix` and it will install itself as a Visual Studio Plugin.
-If you have any issues, consult the project's [README](https://github.com/Zingabopp/BeatSaberModdingTools#readme) and [WIKI](https://github.com/Zingabopp/BeatSaberModdingTools/wiki).
-
-## Template setup
-
-First, create a new project using the template.
-We are going to use the `BSIPA4 Plugin (Core)` template, and we'll be calling our mod `BSPlugin1`.
-You should change the name to whatever you want to call your mod.
-
-
-
-
-You will then need to set your Beat Saber Directory in Visual Studio.
-Follow the instructions [on the template readme](https://github.com/Zingabopp/BeatSaberModdingTools#how-to-use),
-or see the screenshot below.
-
-
-
-At this point, **try and build the project**, and it should automatically find the
-references for you and the build should succeed.
-
-If your build does not succeed, check that you don't have any missing references.
-
-::: tip
-BeatSaberModdingTools will automatically handle references. If your references could not be found, [double-check the instructions](https://github.com/Zingabopp/BeatSaberModdingTools#how-to-use).
-
-If you need to manually add references, right click on `References` in the Project folder, then `Beat Saber Reference Manager...`.
-Select your references, then click "Apply".
-
-You can find more information about the reference manager [here](https://github.com/Zingabopp/BeatSaberModdingTools/wiki/Adding-References).
-:::
-
-## Inspecting the Code
-
-You should have 5 files open automatically with the template.
-
-| Filename | About |
-| ------------------------ | ------------------------------------------------------------------------------ |
-| `manifest.json` | Information about your mod for BSIPA. |
-| `Plugin.cs` | The main file that is loaded for your mod. |
-| `AssemblyInfo.cs` | File information about your mod. This is mostly managed by Modding Tools. |
-| `PluginConfig.cs` | A template for enabling config for your mod. This is commented out by default. |
-| `BSPlugin1Controller.cs` | A generic MonoBehaviour for your mod. |
-
-### Edit your mod's Manifest
-
-Fill out the `manifest.json` file with your information.
-The `name` and `id` keys are used to identify your mod.
-The ID should match the ID used when uploading your mod to BeatMods.
-
-::: warning
-Do **not** remove the dependency on BSIPA. As of BSIPA v4.1 this is required for your mod to load.
-:::
-
-## Compiling
-
-Build your plugin with `Build -> Build Solution` or CTRL + SHIFT + B
-Your compiled DLL should automatically be copied to the `Plugins` folder in your Beat Saber directory!
-This will be done for both debug and release builds.
-
-::: tip NOTE
-When you are ready to release your mod, select the `Release` option to make a Release build of your mod.
-
-Building in Release mode will generate a packaged `.zip` file ready to upload to BeatMods.
-:::
-
-## Testing your mod in-game
-
-To test if your mod is loaded in-game, you will need to launch Beat Saber with the BSIPA Console enabled.
-Add `--verbose` as a launch argument and run the game.
-For more information on launch arguments, see [here](./#launch-args).
-
-When you launch the game, you should see BSIPA load your mod in the console window.
-
-
-
-## Next Steps
-
-Here are some useful resources in continuing your modding career.
-
-- If you need help with developing mods, you can ask in `#pc-mod-dev` on the [BSMG Discord](https://discord.gg/beatsabermods).
-- If you want to decompile code, check out [dnSpy](https://github.com/dnSpy/dnSpy/releases)
-- See the BSIPA Documentation for more information about the [configuration system](https://bsmg.github.io/BeatSaber-IPA-Reloaded/tags/4.1.3/articles/start-dev.html#configuring-your-plugin).
-- If you need to patch the game's code for your mod, you should use [Harmony](https://github.com/pardeike/Harmony#readme).
- The `0Harmony.dll` is already installed for modded games.
-- For experienced developers, you may be interested in learning about Zenject, the Dependency Injection system used heavily
- by Beat Saber. [SiraUtil](https://github.com/Auros/SiraUtil#readme) is a library that allows you to easily hook
- into this system.
diff --git a/wiki/modding/pc/bsml.md b/wiki/modding/pc/bsml.md
new file mode 100644
index 000000000..e2c5ffde6
--- /dev/null
+++ b/wiki/modding/pc/bsml.md
@@ -0,0 +1,362 @@
+---
+prev: Harmony Patching
+next: Zenject and SiraUtil
+---
+
+# Creating Beat Saber UI
+
+[BeatSaberMarkupLanguage (BSML)](https://github.com/monkeymanboy/BeatSaberMarkupLanguage) is the most common way to
+create customized UI in Beat Saber. BSML is effectively a tag-based language that mimics the GameObject hierarchy
+of Unity. It parses tags into GameObjects, and attaches the relevant Unity and Beat Saber UI elements to them.
+
+The documentation for all BSML components can be found [here](https://monkeymanboy.github.io/BSML-Docs/).
+
+## Getting Set Up
+
+Of course, if you want to add BSML in your mod, make sure that you have it installed in your game, and your project
+is referencing BSML.
+
+### Creating the BSML file
+
+You can name the file anything you want, just make sure that its file extension is `.bsml`.
+
+
+
+BSML will require that the bsml file be embedded in the assembly. You can do this by right-clicking the file in the
+explorer, going to properties, and then changing the build action to `EmbeddedResource`.
+
+
+
+### Writing in BSML
+
+If you're using Rider, you may have to add a file association for `.bsml` files to get basic syntax highlighting. To
+do this, go to `File | Settings | Editor | File Types` and search for `XML`. Add a new file name pattern as `*.bsml`.
+This will make Rider accept `.bsml` files as XML files and do highlighting accordingly.
+
+To get autocompletion in a BSML file, you will need to provide a schema. A way to do this is to use the
+[background tag](https://monkeymanboy.github.io/BSML-Docs/Tags/BackgroundTag/) and add the schema to it:
+
+```xml
+
+
+
+```
+
+Rider may prompt you that the resource is not found. Simply right click on the URL, or press `Alt+Enter`, and select
+fetch external resource.
+
+Once set up, you should have basic autocompletion for tags if you start typing inside the `` tag.
+
+## Running Code In The Menu
+
+There are a couple different ways you can display your BSML in game, however, it is first important to note that
+you should not call any of the methods mentioned below outside of the main menu. You should make sure the game has finished
+loading the main menu before doing anything.
+
+- The recommended method, if you don't already use SiraUtil, is BSML's own `MainMenuAwaiter` class that has an
+ [event](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/) called `MainMenuInitializing`
+ that is invoked when the main menu loads
+- If you are using SiraUtil, it is recommended to bind a type with a `Location.Menu`, or on the `MainSettingsMenuViewControllersInstaller`
+- BS Utils also provides events in `BSEvents` and they are called `earlyMenuSceneLoadedFresh` and `lateMenuSceneLoadedFresh`
+- You can use the game's `GameScenesManager` and the `transitionDidFinishEvent`, then check if the output `ScenesTransitionSetupDataSO`
+ is a `MenuScenesTransitionSetupDataSO` using a
+ [type test expression](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/type-testing-and-cast)
+- If you want to get an event when the main menu loads yourself, you can use Unity's
+ [SceneManager](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/SceneManagement.SceneManager.html)
+ and check the name of the loaded scene manually, however this is a lot more effort than all of the other methods
+- You could also use a Harmony patch into a method that will run every time the menu reinitializes, but this is also unnecessarily
+ complicated
+
+## Adding Menus
+
+Once you have code running in the main menu, it's time to decide where you want to display your UI.
+
+### Mod Settings
+
+The mod settings menu is added by BSML and can be accessed from a custom button in the main menu settings. To register
+your own tab, check the `BSMLSettings` class. `TutorialMenu` is just a normal class.
+
+```c#
+private readonly TutorialMenu tutorialMenu = new TutorialMenu();
+
+public void AddSettingsMenu()
+{
+ BSMLSettings.Instance.AddSettingsMenu(
+ name: "Tutorial Mod",
+ resource: "TutorialMod.tutorial.bsml",
+ host: tutorialMenu);
+}
+```
+
+
+
+### Gameplay Setup
+
+The mods tab is added by BSML in the Gameplay Setup menu, which is found to the left of the song list, where
+you can normally find player settings and gameplay modifiers. To register a new tab, check the `GameplaySetup` class.
+`TutorialMenu` is just a normal class.
+
+```c#
+private readonly TutorialMenu tutorialMenu = new TutorialMenu();
+
+public void AddTab()
+{
+ GameplaySetup.Instance.AddTab(
+ name: "Tutorial Mod",
+ resource: "TutorialMod.tutorial.bsml",
+ host: tutorialMenu);
+}
+```
+
+
+
+### Custom Flow Coordinator
+
+BSML gives you a way to create a button in the left screen of the main menu. This button can do anything you want it
+to do, but most modders make it present their mod's UI. This is done by using a `FlowCoordinator`, and by adding one
+or more `ViewController` objects to it.
+
+BSML provides methods to create both flow coordinators and view controllers, which makes this process a lot cleaner.
+
+BSML has a few choices of view controller types you can inherit; we are going to use the `BSMLAutomaticViewController`
+because it has the option of hot reloading the menu when you make changes to the bsml file.
+
+```c#
+[ViewDefinition("TutorialMod.tutorial.bsml")]
+public class TutorialViewController : BSMLAutomaticViewController { }
+```
+
+The flow coordinator is responsible for managing view controllers. `FlowCoordinator` has many members that you can use
+or override, so it's worth checking out the code for it.
+
+```c#
+public class TutorialFlowCoordinator : FlowCoordinator
+{
+ private readonly TutorialViewController tutorialViewController = BeatSaberUI.CreateViewController();
+
+ // Called immediately when the flow coordinator is activated
+ protected override void DidActivate(bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling)
+ {
+ if (firstActivation)
+ {
+ // Sets the title text in the top bar
+ SetTitle("Tutorial Mod");
+ showBackButton = true;
+ }
+
+ if (addedToHierarchy)
+ {
+ ProvideInitialViewControllers(tutorialViewController);
+ }
+ }
+
+ protected override void BackButtonWasPressed(ViewController topViewController)
+ {
+ BeatSaberUI.MainFlowCoordinator.DismissFlowCoordinator(this);
+ }
+}
+```
+
+The code below is related to managing the menu button. We tell the `MainFlowCoordinator` to present our own
+flow coordinator. You can also have your own way to dismiss your flow coordinator but, in the example above,
+we are relying on the back button to do this.
+
+```c#
+private readonly TutorialFlowCoordinator tutorialFlowCoordinator;
+private readonly MenuButton menuButton;
+
+public MenuManager()
+{
+ tutorialFlowCoordinator = BeatSaberUI.CreateFlowCoordinator();
+ menuButton = new MenuButton("Tutorial Mod", ShowFlowCoordinator);
+}
+
+public void AddMenuButton()
+{
+ MenuButtons.Instance.RegisterButton(menuButton);
+}
+
+private void ShowFlowCoordinator()
+{
+ BeatSaberUI.MainFlowCoordinator.PresentFlowCoordinator(tutorialFlowCoordinator);
+}
+```
+
+
+
+### Floating Screen
+
+If you want to place your UI components anywhere, you can create a floating screen. This will allow you to have a view controller
+anywhere in the world. You can also create a handle for the floating screen which will allow the player to move the screen
+around.
+
+The example below creates just creates a small screen near the ground in front of the player's place.
+
+```c#
+private readonly TutorialViewController tutorialViewController = BeatSaberUI.CreateViewController();
+
+public void CreateFloatingScreen()
+{
+ var floatingScreen = FloatingScreen.CreateFloatingScreen(
+ screenSize: new Vector2(25f, 10f),
+ createHandle: false,
+ position: new Vector3(0f, 0.5f, 2f),
+ rotation: Quaternion.Euler(45f, 0f, 0f));
+
+ floatingScreen.SetRootViewController(tutorialViewController, ViewController.AnimationType.None);
+}
+```
+
+Since floating screens aren't part of the screen system, and because the menu persists during gameplay, you can have
+the floating screen active in the game scene. The below screenshot is of the floating screen from
+[SliceDetails](https://github.com/qqrz997/SliceDetails), which is activated when the game is paused.
+
+
+
+## Interacting With The Menu
+
+Now let's take a look at some of the ways you can make use of your UI. Again, to find out more about the components
+that we will talk about in the following sections, check the [BSML documentation](https://monkeymanboy.github.io/BSML-Docs/).
+
+### Buttons And Actions
+
+We are going to add a [button](https://monkeymanboy.github.io/BSML-Docs/Tags/ButtonTag/) to the menu:
+
+```xml
+
+```
+
+And add the corresponding method in the object host or view controller:
+
+```c#
+public void ButtonClicked() => Plugin.Log.Info("Button Clicked");
+```
+
+Now, `ButtonClicked()` will get called whenever our button is clicked.
+
+If you want to run a different method or a method with a different name to the one specified, you can use the
+[UIAction](https://monkeymanboy.github.io/BSML-Docs/Attributes/UIAction/) annotation and specify the name:
+
+```c#
+[UIAction("ButtonClicked")]
+public void SomeMethodName() { }
+```
+
+### UI Components
+
+BSML components must be part of and accessed from the provided host object or view controller. To access the instance of
+a BSML component, you must give one an `id`:
+
+```xml
+
+```
+
+And then add it in the object host by adding a [UIComponent](https://monkeymanboy.github.io/BSML-Docs/Attributes/UIComponent/)
+annotation:
+
+```c#
+[UIComponent("textComponent")]
+private readonly TextMeshProUGUI textComponent = null!; // assigned by BSML
+```
+
+If you want to have initialization logic for components in your UI, do not use Unity's `Awake()` or `Start()` or a constructor,
+instead use the post-parse event provided by BSML. This will be called after all of the UI has been created and all components
+on the object host have been assigned a value.
+
+```c#
+[UIAction("#post-parse")]
+public void PostParse()
+{
+ textComponent.text = "The text has changed.";
+}
+```
+
+### Settings And Values
+
+There are many different ways to get input values from BSML. Let's take a look at the
+[toggle](https://monkeymanboy.github.io/BSML-Docs/Tags/ToggleSettingTag/) and
+[slider](https://monkeymanboy.github.io/BSML-Docs/Tags/SliderSettingTag/) settings:
+
+```xml
+
+
+
+
+```
+
+We use `apply-on-change` to make the property get set when the input value changes, otherwise you would need to use
+[INotifyPropertyChanged](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged)
+when you want to apply the values, which can still be useful if you want to manually do it.
+
+```c#
+private bool toggleValue;
+private float sliderValue;
+
+public bool ToggleValue
+{
+ get => toggleValue;
+ set
+ {
+ toggleValue = value;
+ Plugin.Log.Info($"Toggle set to {value}");
+ }
+}
+
+public float SliderValue
+{
+ get => sliderValue;
+ set
+ {
+ sliderValue = value;
+ Plugin.Log.Info($"Slider set to {value}");
+ }
+}
+```
+
+If you want a property with a different name to the one specified, you can use the [UIValue](https://monkeymanboy.github.io/BSML-Docs/Attributes/UIValue/)
+annotation and specify the name:
+
+```c#
+[UIValue("ToggleValue")]
+public bool SomePropertyName { get; set; }
+```
+
+### Displaying Data
+
+As well as taking input in your UI, it's very common to need to display data. Let's add a [list](https://monkeymanboy.github.io/BSML-Docs/Tags/ListTag/):
+
+```xml
+
+```
+
+And set the data through a property:
+
+```c#
+private IList ListData =>
+[
+ new("A list cell", "and"),
+ new("Another list cell", "and"),
+ new("Another list cell", "that is all.")
+];
+```
+
+Or alternatively, you can grab the
+[CustomListTableData](https://monkeymanboy.github.io/BSML-Docs/TypeHandlers/CustomListTableData/)
+component from the list by adding an `id` and use that:
+
+```c#
+[UIComponent("List")]
+private readonly CustomListTableData list = null!; // assigned by BSML
+
+[UIAction("#post-parse")]
+public void PostParse()
+{
+ list.Data = [
+ new("A list cell", "and"),
+ new("Another list cell", "and"),
+ new("Another list cell", "that is all.")
+ ];
+ list.TableView.ReloadData();
+}
+```
diff --git a/wiki/modding/pc/decompiling.md b/wiki/modding/pc/decompiling.md
new file mode 100644
index 000000000..28bccd337
--- /dev/null
+++ b/wiki/modding/pc/decompiling.md
@@ -0,0 +1,72 @@
+---
+prev: Runtime Unity Editor
+next: Harmony Patching
+---
+
+# Decompiling
+
+When modding Beat Saber and patching the game to change certain behaviour, it's important to be able to read
+the game's code itself. There are some tools to help with this.
+
+## Tools
+
+Rider and Visual Studio do have built-in decompilers to let you see under the hood of types.
+
+
+
+This will only have limited usage and won't help you browse the types the game has to offer, or see how different
+parts of the game's code interact.
+
+### ILSpy
+
+[ILSpy](https://github.com/icsharpcode/ILSpy) is a lightweight decompiler for C# dlls which will allow you to freely
+browse the different types, variables, and methods that are contained within the game's own dlls. Grab the installer
+from the [releases](https://github.com/icsharpcode/ILSpy/releases) and install ILSpy.
+
+Once you have ILSpy opened, find the `Manage Assembly Lists` icon in the top bar and create a new list. You can name it
+after the Beat Saber version you are working on. Once created, double click it to open the list.
+
+
+
+To add binaries, click the `Open` icon in the top bar and navigate to your game folder. You are looking for
+`/Beat Saber_Data/Managed`, select everything in this folder and open them into ILSpy. This will also include the
+.NET framework and Unity assemblies, so that when you are looking at types from Beat Saber, all of the references will
+be resolved.
+
+### dnSpy
+
+[dnSpy](https://github.com/dnSpyEx/dnSpy) is a much more in-depth tool for developing .NET programs; it has a
+debugger, assembly editor, and more. It also has a decompiler built in to it for browsing decompiled C#, just like
+ILSpy.
+
+You can get dnSpy from the [releases](https://github.com/dnSpyEx/dnSpy/releases) on GitHub. Extract the zip archive and
+run the .exe to get started. Similarly to ILSpy, you create a new list by going to `File`, then `Open List...`, and
+adding a new list. You can name it after the Beat Saber version you are working on. Once created, double click it to
+open the list.
+
+Click the `Open` icon in the top bar or press `Ctrl+O` and navigate to `Beat Saber/Beat Saber_Data/Managed`,
+select everything in this folder and open them into your list. To start searching, click the `Search Assemblies` in the
+top bar.
+
+## Browsing the Code
+
+Beat Saber is a complex game with a lot of different assemblies, but it is pretty well organized and you can expect to
+find what you are looking for where it should be. Something that may help is to find an object in game using RUE,
+and by checking the MonoBehaviours attached to them, you can search for them in ILSpy.
+
+
+
+If you double click a type in the search window, or in the assembly list, you will see the decompiler's interpretation
+of that type and the corresponding C# code.
+
+
+
+An important trick to know is analyzing members of a type. By pressing `Ctrl+R` or right-clicking and `Analyze` on,
+for example, a public method, you will see the usages of that member. In the example below, the method
+`FlyingScoreEffect.InitAndPresent` is called by `FlyingScoreSpawner.SpawnFlyingScore`.
+
+
+
+This tool will be very important when writing [Harmony patches](./harmony-patching.md), which will be covered in the next
+section of this wiki. You will want to be able to know how different parts of the code interact so that you can work out
+where you should implement custom behaviour in your mod.
diff --git a/wiki/modding/pc/full-mod-guide.md b/wiki/modding/pc/full-mod-guide.md
new file mode 100644
index 000000000..072c5687a
--- /dev/null
+++ b/wiki/modding/pc/full-mod-guide.md
@@ -0,0 +1,627 @@
+---
+prev: Zenject and SiraUtil
+next: false
+---
+
+# Full Mod Guide
+
+This part of the wiki will be dedicated to showing the full process of making a Beat Saber mod.
+
+## The Mod
+
+The first step of creating a mod is understanding exactly what you want to achieve.
+
+In this tutorial, we will be creating a mod capable of changing the "MISS" effect and replacing it with text. The mod
+will have an in-game interface to allow you to change the text through a text input. The mod will be designed in a
+decoupled way, which will make it easier to add new features to the mod later if we wish.
+
+We can use [BSML](./bsml.md) for the UI, and we can use [SiraUtil](./zenject.md) to create our custom text effects
+while remaining loosely coupled to in-game functions.
+
+### Creating The Project
+
+The first thing we are going to do is set up the plugin template. Refer to the [setup guide](./setup.md) for more information.
+We will name the plugin `MissTextChanger` and add dependencies to `BSML` and `SiraUtil` in the metadata.
+
+This will start from a bare-bones BSIPA template, going step by step through the testing process of making a simple
+plugin to help people understand everything. If you're following along, you can also just use the full template, which
+has a basic SiraUtil and BSML setup already done.
+
+### Figuring Out The Game
+
+Before going any further, we need to get an understanding of how the game handles miss text normally. First, let's go in
+to [ILSpy](./decompiling.md), and search for "ScoreController". This class is responsible for basically everything related
+to giving the player score, so we can figure out how misses are handled from here.
+
+In the `Start()` method of the `ScoreController`, we can see the `noteWasMissedEvent` being assigned to which is a part
+of the `BeatmapObjectManager`. Let's analyze this event and see what the `add` method of the event is used by. We can now
+see the `MissedNoteEffectSpawner` which, as we can assume by its name, is exactly what we're looking for.
+
+
+
+Looking into the `MissedNoteEffectSpawner` we can see all it is doing is taking data from the missed note's `NoteController`
+and passing it to a `FlyingSpriteSpawner` to spawn the effect. The sprite spawner manages a
+[Zenject Pool](https://github.com/Mathijs-Bakker/Extenject/blob/master/Documentation/MemoryPools.md)
+of sprite effects.
+
+If we analyze the `FlyingSpriteEffect.Pool` we can figure out where it is bound by checking where it is used.
+
+
+
+Now, looking at the `EffectPoolsManualInstaller.ManualInstallBindings()` method we see a couple different memory pools here.
+One that is particularly interesting is the `FlyingTextEffect`, which if we analyze we can see the `FlyingTextSpawner`.
+
+This is surely something we can use to achieve customizable miss text, however, looking at and comparing the spawn
+methods for the sprite and text spawners, they are not exactly the same. The `x` of the `targetPos` vector is anchored
+in the sprite spawner by its sign, which is why we see miss effects only fly to two locations to the left and right of
+the track; there are only two possible values for sign.
+
+Because of this difference, if we wanted to maintain the same visuals, we cannot use the `FlyingTextSpawner` for our needs.
+We could use a harmony patch to change how the `SpawnFlyingSprite()` method works, but this may affect other mods that may
+want to use this.
+
+### The Solution
+
+Instead of using the game's methods for our needs, let's make a custom effect spawner, and a custom flying object
+effect. This should ensure that our mod doesn't conflict with other mods' features, but we're going to have to patch in to
+the `MissedNoteEffectSpawner` to replace the base-game's miss effect with our custom one.
+
+Let's start with the `MissTextEffect`, which will inherit `FlyingObjectEffect` like the other effects. For the text, we
+will want a `TextMeshPro`.
+
+```c#
+internal class MissTextEffect : FlyingObjectEffect
+{
+ // This is the pool from Zenject
+ public class Pool : MonoMemoryPool;
+
+ // We don't have something to use here yet, we will get one later
+ private AnimationCurve fadeAnimationCurve;
+
+ // This field is serialized so that it will be included on instantiation
+ [SerializeField]
+ public TextMeshPro? textMesh;
+
+ private Color color;
+
+ public void InitAndPresent(string text, float duration, Vector3 targetPos,
+ Quaternion rotation, Color color, float fontSize, bool shake)
+ {
+ if (textMesh == null) return;
+ this.color = color;
+ textMesh.text = text;
+ textMesh.fontSize = fontSize;
+ InitAndPresent(duration, targetPos, rotation, shake);
+ }
+
+ public override void ManualUpdate(float t)
+ {
+ if (textMesh != null)
+ textMesh.color = color with { a = fadeAnimationCurve.Evaluate(t) };
+ }
+}
+```
+
+We have some things to fill in on this object, but we will figure that out a bit later.
+
+Next let's look at the spawner. We will be making sure to match the logic of the sprite spawner so
+that the behaviour is the same.
+
+```c#
+internal class MissTextEffectSpawner : MonoBehaviour,
+ IFlyingObjectEffectDidFinishEvent
+{
+ // There is a lot of data here that needs filling
+ private float duration;
+ private float xSpread;
+ private float targetYPos;
+ private float targetZPos;
+ private Color color;
+ private float fontSize;
+ private MissTextEffect.Pool missTextEffectPool;
+
+ public void SpawnText(
+ Vector3 pos, Quaternion rotation, Quaternion inverseRotation)
+ {
+ var text = "CUSTOM MISS";
+ var targetPos = rotation * new Vector3(
+ Mathf.Sign((inverseRotation * pos).x) * xSpread,
+ targetYPos,
+ targetZPos);
+
+ var missTextEffect = missTextEffectPool.Spawn();
+ missTextEffect.didFinishEvent.Add(this);
+ missTextEffect.transform.localPosition = pos;
+ missTextEffect.InitAndPresent(
+ text, duration, targetPos, rotation, color, fontSize, false);
+ }
+
+ public void HandleFlyingObjectEffectDidFinish(
+ FlyingObjectEffect flyingObjectEffect)
+ {
+ flyingObjectEffect.didFinishEvent.Remove(this);
+ missTextEffectPool.Despawn((MissTextEffect)flyingObjectEffect);
+ }
+}
+```
+
+All we need to do is register these components in an installer. Let's create a `PlayerInstaller` and
+add our bindings.
+
+- Bind the `MissTextEffectSpawner` as a component on a single new game object
+- Bind the memory pool for the `MissTextEffect` similar to the other score effects in the
+ `EffectPoolsManualInstaller`
+
+```c#
+internal class PlayerInstaller : Installer
+{
+ public override void InstallBindings()
+ {
+ Container.Bind()
+ .FromNewComponentOnNewGameObject()
+ .AsSingle();
+ Container.BindMemoryPool()
+ .WithInitialSize(20)
+ .FromComponentInNewPrefab(GetMissTextEffectPrefab());
+ }
+
+ private static MissTextEffect GetMissTextEffectPrefab()
+ {
+ var prefabObject = new GameObject("MissTextEffect");
+ var textEffect = prefabObject.AddComponent();
+
+ var textObject = new GameObject("Text") { layer = 5 };
+ textObject.transform.SetParent(prefabObject.transform, false);
+
+ textEffect.textMesh = textObject.AddComponent();
+ textEffect.textMesh.alignment = TextAlignmentOptions.Capline;
+ textEffect.textMesh.fontStyle = FontStyles.Bold | FontStyles.Italic;
+
+ return textEffect;
+ }
+}
+```
+
+Creating the `MissTextEffect` prefab here doesn't make much sense and should realistically move to its
+own class but for now this is fine to demonstrate what we're doing.
+
+Remember to add the zenjector to the `Plugin` init too.
+
+```c#
+[Plugin(RuntimeOptions.SingleStartInit), NoEnableDisable]
+internal class Plugin
+{
+ [Init]
+ public Plugin(Logger log, Config config,
+ PluginMetadata metadata, Zenjector zenjector)
+ {
+ log.Info($"{metadata.Name} {metadata.HVersion} initialized.");
+
+ zenjector.UseLogger(log);
+ zenjector.Install(Location.Player);
+ }
+}
+```
+
+Now that we have the main components of the mod outlined, we need to set their fields. There are two ways
+we can do this. We can do it manually by loading up a map in-game, opening [Runtime Unity Editor](rue.md),
+and looking for the miss effect spawner to see the values. This may work, but we should figure out how to automate
+it in case the values aren't constant.
+
+As seen before, we found the prefab for the `FlyingSpriteEffect` in the `EffectPoolsManualInstaller`. This isn't
+actually an installer, instead it's a part of the _much_ larger `GameplayCoreInstaller`.
+
+If we were to patch in to the `GameplayCoreInstaller`, we can access the prefabs for the `FlyingTextEffect` and
+the instance of the `FlyingSpriteSpawner` to get the fields we need for our custom components.
+
+Since we're using SiraUtil for this mod, let's make an [affinity patch](./zenject.md#affinity-patching)
+into the `InstallBindings()` method. We can take the fields from the prefabs and bind their values with an ID,
+so that we can inject them into our own components.
+
+```c#
+internal class GameCoreInstallerHook : IAffinity
+{
+ [AffinityPrefix]
+ [AffinityPatch(typeof(GameplayCoreInstaller), "InstallBindings")]
+ private void InstallBindingsPostfix(GameplayCoreInstaller __instance)
+ {
+ var container = __instance.Container;
+ var flyingSpriteSpawner = __instance._missedNoteEffectSpawnerPrefab._missedNoteFlyingSpriteSpawner;
+ var flyingTextEffect = __instance._effectPoolsManualInstaller._flyingTextEffectPrefab;
+
+ float duration = flyingSpriteSpawner._duration;
+ float spread = flyingSpriteSpawner._xSpread;
+ float targetYPos = flyingSpriteSpawner._targetYPos;
+ float targetZPos = flyingSpriteSpawner._targetZPos;
+ var color = Color.white;
+ const float fontSize = 4.5f; // Miss text is a sprite; estimate the font size
+ var fadeAnimationCurve = flyingTextEffect._fadeAnimationCurve;
+ var moveAnimationCurve = flyingTextEffect._moveAnimationCurve;
+
+ container.BindInstance(duration).WithId("missEffectDuration").AsCached();
+ container.BindInstance(spread).WithId("missEffectSpread").AsCached();
+ container.BindInstance(targetYPos).WithId("missEffectTargetYPos").AsCached();
+ container.BindInstance(targetZPos).WithId("missEffectTargetZPos").AsCached();
+ container.BindInstance(color).WithId("missEffectColor").AsCached();
+ container.BindInstance(fontSize).WithId("missEffectFontSize").AsCached();
+ container.BindInstance(fadeAnimationCurve).WithId("textEffectFadeAnimationCurve").AsCached();
+ container.BindInstance(moveAnimationCurve).WithId("textEffectMoveAnimationCurve").AsCached();
+ }
+}
+```
+
+Make sure not to forget to bind this patch. Since we're patching the installer itself, binding it alongside
+the installer we are patching won't work because the `InstallBindings` will be called before our patch is applied.
+Instead let's make an `AppInstaller`, because that will be applied when the game initializes.
+
+```c#
+internal class AppInstaller : Installer
+{
+ public override void InstallBindings()
+ {
+ Container.BindInterfacesTo().AsSingle();
+ }
+}
+```
+
+Remember to add this to the `Plugin` init too.
+
+```c#
+zenjector.Install(Location.App);
+```
+
+And now we add [inject methods](./zenject.md#methods) to our components, starting with the `MissTextEffect`. Note that
+the `_moveAnimationCurve` is part of the base class. We need this so that the movement animation matches the base game's
+movement.
+
+```c#
+[Inject]
+public void Init(
+ [Inject(Id = "textEffectFadeAnimationCurve")] AnimationCurve fadeAnimationCurve,
+ [Inject(Id = "textEffectMoveAnimationCurve")] AnimationCurve moveAnimationCurve)
+{
+ this.fadeAnimationCurve = fadeAnimationCurve;
+ _moveAnimationCurve = moveAnimationCurve;
+}
+```
+
+And for `MissTextEffectSpawner` there are quite a few properties. Also, remember to inject the `Pool`.
+
+```c#
+[Inject]
+public void Init(
+ [Inject(Id = "missEffectDuration")] float duration,
+ [Inject(Id = "missEffectSpread")] float xSpread,
+ [Inject(Id = "missEffectTargetYPos")] float targetYPos,
+ [Inject(Id = "missEffectTargetZPos")] float targetZPos,
+ [Inject(Id = "missEffectColor")] Color color,
+ [Inject(Id = "missEffectFontSize")] float fontSize,
+ MissTextEffect.Pool missTextEffectPool)
+{
+ this.duration = duration;
+ this.xSpread = xSpread;
+ this.targetYPos = targetYPos;
+ this.targetZPos = targetZPos;
+ this.color = color;
+ this.fontSize = fontSize;
+ this.missTextEffectPool = missTextEffectPool;
+}
+```
+
+Now we're all set up to implement our custom text effect. We just need to figure out how to spawn them. Ultimately,
+the goal is to replace the game's "MISS" sprite effect with our own, so let's go back to the `MissedNoteEffectSpawner`
+and patch it to replace the `FlyingSpriteSpawner` with our spawner by using a patch.
+
+By using an affinity patch we can inject the `MissTextEffectSpawner` and use it within the patch with ease.
+
+```c#
+internal class OnMissEffectPatch : IAffinity
+{
+ private readonly MissTextEffectSpawner missTextEffectSpawner;
+
+ public OnMissEffectPatch(MissTextEffectSpawner missTextEffectSpawner)
+ {
+ this.missTextEffectSpawner = missTextEffectSpawner;
+ }
+
+ [AffinityPrefix]
+ [AffinityPatch(typeof(MissedNoteEffectSpawner), nameof(MissedNoteEffectSpawner.HandleNoteWasMissed))]
+ private bool HandleNoteWasMissedPrefix(MissedNoteEffectSpawner __instance, NoteController noteController)
+ {
+ if (noteController.hidden
+ || noteController.noteData.time + 0.5f < __instance._audioTimeSyncController.songTime
+ || noteController.noteData.colorType == ColorType.None)
+ {
+ // Do nothing
+ return false;
+ }
+
+ var position = noteController.inverseWorldRotation * noteController.noteTransform.position;
+ position.z = __instance._spawnPosZ;
+
+ // Spawn our miss text effect
+ missTextEffectSpawner.SpawnText(
+ noteController.worldRotation * position,
+ noteController.worldRotation,
+ noteController.inverseWorldRotation);
+
+ // Cancel the original implementation
+ return false;
+ }
+}
+```
+
+Apart from being syntactically different to the original method we can see from the decompiler, the logic is the same.
+We can bind this in the `PlayerInstaller` because this method runs during gameplay, and that's where our effect spawner
+is bound too.
+
+```c#
+Container.BindInterfacesTo().AsSingle();
+```
+
+### Testing
+
+At this point we should be able to see this in action. Open the game with [FPFC](./index.md#launch-args) and open any map.
+Using No Fail will help.
+
+
+
+## Adding Settings
+
+There are many ways to add interactive menus in to the game, which you can see in the [UI section of this wiki](./bsml.md#adding-menus).
+
+For this guide we will be using a [custom flow coordinator](./bsml.md#custom-flow-coordinator) which will provide plenty
+of space to add more features to the UI in the future if we need to.
+
+Before creating the UI, let's decide what features we need it to have.
+
+- We want a setting to toggle the mod off and on - this is for the player's convenience and most mods should have one
+- We need a way to input text to change the miss text, we can use the [ModalKeyboard](https://monkeymanboy.github.io/BSML-Docs/Tags/ModalKeyboardTag/)
+ for this
+- As well as the input, we should also have some [Text](https://monkeymanboy.github.io/BSML-Docs/Tags/TextTag/) to show
+ the current miss text
+- And finally, we need a way to open the modal keyboard. A simple [Button](https://monkeymanboy.github.io/BSML-Docs/Tags/ButtonTag/)
+ can do this
+
+### Creating A Config
+
+To make settings that will save between sessions, we can utilize BSIPA's config. Let's create a config class, and
+add it to the plugin init. Instead of making a static config, we should pass it as a param of the `AppInstaller`, then bind
+it there so we can inject it anywhere.
+
+```c#
+[assembly: InternalsVisibleTo(GeneratedStore.AssemblyVisibilityTarget)]
+namespace MissTextChanger;
+
+internal class PluginConfig
+{
+ public virtual bool Enabled { get; set; } = true;
+ public virtual string MissText { get; set; } = "MISS";
+}
+```
+
+Then add it to the `Plugin` init:
+
+```c#
+var pluginConfig = config.Generated();
+
+zenjector.Install(Location.App, pluginConfig);
+```
+
+And in the installer:
+
+```c#
+internal class AppInstaller : Installer
+{
+ public AppInstaller(PluginConfig pluginConfig)
+ {
+ this.pluginConfig = pluginConfig;
+ }
+
+ public override void InstallBindings()
+ {
+ Container.BindInstance(pluginConfig).AsSingle();
+ /* ... */
+```
+
+### Implementing The Settings
+
+Before we mess around with the UI, let's make sure we can make these new features work. First, inject the config
+into the `PlayerInstaller` so we can use it to stop our bindings from being made:
+
+```c#
+internal class PlayerInstaller : Installer
+{
+ private readonly PluginConfig pluginConfig;
+
+ public PlayerInstaller(PluginConfig pluginConfig)
+ {
+ this.pluginConfig = pluginConfig;
+ }
+
+ public override void InstallBindings()
+ {
+ if (!pluginConfig.Enabled) return;
+ /* ... */
+```
+
+You can go to the config `.json` file in the `UserData` folder and tweak the settings manually to test that this
+is working. Next, let's add the config to the `MissTextEffectSpawner` and use the text property in the
+`SpawnText()` method.
+
+```c#
+public void SpawnText(
+ Vector3 pos, Quaternion rotation, Quaternion inverseRotation)
+{
+ /* ... */
+ var text = pluginConfig.MissText;
+ missTextEffect.InitAndPresent(text, duration, targetPos, rotation,
+ color, fontSize, false);
+ /* ... */
+}
+```
+
+That was simple thanks to zenject. Now let's move on to setting up the UI.
+
+### Adding The UI
+
+Now we will set up the flow coordinator so that we can start playing around with the BSML immediately.
+Let's start at the end of the dependency tree with the view controller.
+
+```xml
+
+
+
+
+
+
+```
+
+And the host for our view:
+
+```c#
+[HotReload(RelativePathToLayout = @".\settingsView.bsml")]
+[ViewDefinition("MissTextChanger.Menu.settingsView.bsml")]
+internal class SettingsViewController : BSMLAutomaticViewController
+{
+ [Inject] private readonly PluginConfig pluginConfig = null!;
+
+ [UIComponent("MissText")] private readonly TextMeshProUGUI missText = null!;
+
+ [UIAction("#post-parse")]
+ private void PostParse()
+ {
+ SetMissTextPreview(pluginConfig.MissText);
+ }
+
+ private bool Enabled
+ {
+ get => pluginConfig.Enabled;
+ set => pluginConfig.Enabled = value;
+ }
+
+ private string KeyboardInput
+ {
+ get => pluginConfig.MissText;
+ set
+ {
+ pluginConfig.MissText = value;
+ SetMissTextPreview(value);
+ }
+ }
+
+ private void SetMissTextPreview(string v) =>
+ missText.text = string.IsNullOrEmpty(v) ? "No miss text" : v;
+}
+```
+
+By using the `HotReload` attribute we can get live updates to the view controller when we change the bsml file,
+without having to re-build the mod.
+
+Now for the `FlowCoordinator`, which is responsible for managing our view controller.
+
+```c#
+internal class MissTextChangerFlowCoordinator : FlowCoordinator
+{
+ [Inject] private readonly SettingsViewController settingsViewController = null!;
+
+ public event Action? DidFinish;
+
+ protected override void DidActivate(bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling)
+ {
+ if (firstActivation)
+ {
+ showBackButton = true;
+ SetTitle("MissTextChanger");
+ }
+
+ if (addedToHierarchy)
+ {
+ ProvideInitialViewControllers(settingsViewController);
+ }
+ }
+
+ protected override void BackButtonWasPressed(ViewController topViewController)
+ {
+ DidFinish?.Invoke();
+ }
+}
+```
+
+We're using an action here to signal when we are done so that we don't need an extra dependency to handle returning to the
+main flow coordinator. This event will be used by our button manager.
+
+```c#
+internal class MenuButtonManager : IInitializable, IDisposable
+{
+ private readonly MainFlowCoordinator mainFlowCoordinator;
+ private readonly MissTextChangerFlowCoordinator missTextChangerFlowCoordinator;
+ private readonly MenuButtons menuButtons;
+ private readonly MenuButton menuButton;
+
+ public MenuButtonManager(
+ MainFlowCoordinator mainFlowCoordinator,
+ MissTextChangerFlowCoordinator missTextChangerFlowCoordinator,
+ MenuButtons menuButtons)
+ {
+ this.mainFlowCoordinator = mainFlowCoordinator;
+ this.missTextChangerFlowCoordinator = missTextChangerFlowCoordinator;
+ this.menuButtons = menuButtons;
+ menuButton = new("MissTextChanger", PresentFlowCoordinator);
+ }
+
+ public void Initialize()
+ {
+ menuButtons.RegisterButton(menuButton);
+ missTextChangerFlowCoordinator.DidFinish += DismissFlowCoordinator;
+ }
+
+ public void Dispose()
+ {
+ missTextChangerFlowCoordinator.DidFinish -= DismissFlowCoordinator;
+ }
+
+ private void PresentFlowCoordinator() =>
+ mainFlowCoordinator.PresentFlowCoordinator(missTextChangerFlowCoordinator);
+
+ private void DismissFlowCoordinator() =>
+ mainFlowCoordinator.DismissFlowCoordinator(missTextChangerFlowCoordinator);
+}
+```
+
+That's everything we need to create our UI. Now we just need a `MenuInstaller` to create the bindings.
+
+```c#
+internal class MenuInstaller : Installer
+{
+ public override void InstallBindings()
+ {
+ Container.BindInterfacesTo().AsSingle();
+ Container.Bind().FromNewComponentOnNewGameObject().AsSingle();
+ Container.Bind().FromNewComponentAsViewController().AsSingle();
+ }
+}
+```
+
+And of course, remember to add this to the `Plugin` init.
+
+```c#
+zenjector.Install(Location.Menu);
+```
+
+
+
+## Closing Remarks
+
+We have now covered every step of creating a new Beat Saber mod.
+
+This example mod has been designed in a way which allows easy changes and extension to its features. When designing a mod,
+it's important to figure out what you want to do so that development doesn't reach a halt.
+
+If you want to learn more we highly recommend checking the source code for other mods to learn more about different APIs
+and how Beat Saber works. You can find that most mods are open source, and you can find that source by visiting
+[BeatMods](https://beatmods.com/) and going to the more info section for any given mod.
+
+You can view all of the source code used in this guide [here](https://github.com/qqrz997/TutorialPCMod).
diff --git a/wiki/modding/pc/harmony-patching.md b/wiki/modding/pc/harmony-patching.md
new file mode 100644
index 000000000..1dbed6511
--- /dev/null
+++ b/wiki/modding/pc/harmony-patching.md
@@ -0,0 +1,234 @@
+---
+prev: Decompiling
+next: Creating UI
+---
+
+# Harmony Patching
+
+A common method of altering the behavior of the game is through the Harmony API, and every modder should know how
+to use it.
+
+## Summary
+
+Harmony patching is a way of hooking the games methods and pointing them to different implementations.
+By writing harmony patches, you are essentially adding code to methods, changing parts of them, or entirely rewriting
+them.
+
+Harmony patches are quite powerful and are used in a great amount of different mods. There is a lot of detail about
+patching and you should read the [documentation](https://harmony.pardeike.net/articles/patching.html) if you ever need
+to do something specific.
+
+## Harmony Setup
+
+There are different methods of setting up your patches as stated
+[here](https://harmony.pardeike.net/articles/basics.html#patching-using-annotations)
+in the documentation. We are simply going to patch all methods marked with the `HarmonyPatch` attributes using
+`PatchAll()`:
+
+```c#
+internal class Plugin
+{
+ private Harmony harmony;
+ private Assembly executingAssembly = Assembly.GetExecutingAssembly();
+
+ [Init]
+ public Plugin(PluginMetadata pluginMetadata)
+ {
+ harmony = new Harmony(pluginMetadata.Id);
+ }
+
+ [OnStart]
+ public void OnApplicationStart() => harmony.PatchAll(executingAssembly);
+
+ [OnExit]
+ public void OnApplicationQuit() => harmony.UnpatchSelf();
+}
+```
+
+Now, any classes and methods with the `HarmonyPatch` attribute will be registered as a patch. Once again, if you want
+more control, there are different methods to do this as stated in the
+[documentation](https://harmony.pardeike.net/articles/basics.html#manual-patching).
+
+## Examples
+
+To make understanding harmony patches easier, we will provide some simple examples of the different things you can do.
+
+### Postfix
+
+The simplest method of patching involves adding code at the end of a method - a postfix.
+
+- This is very commonly seen and can be used as events to execute code when the game does certain events
+- It can be used to reliably get references to objects without having to use expensive methods like
+ [`Resources.FindObjectsOfTypeAll`](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Resources.FindObjectsOfTypeAll.html).
+- They can also change the return result of methods as mentioned in the
+ [documentation](https://harmony.pardeike.net/articles/patching-postfix.html).
+
+The following patch patches the `Init()` method in any `NoteController`. The patch gets a reference to the instance of
+the object by injecting the `__instance` variable in the patch params.
+
+Since `NoteController` is a type that has many inheritors, we can get what type of note controller it is. If you run
+this in a map that also has bombs and chains, you will see their types get listed in the logs too.
+
+```c#
+[HarmonyPatch(typeof(NoteController), "Init")]
+public class ExamplePatch
+{
+ public static void Postfix(NoteController __instance)
+ {
+ Plugin.Log.Info($"A {__instance.GetType().Name} has been initialized.");
+ }
+}
+```
+
+### Prefix
+
+Another common patch, this is very similar to a postfix except it runs before the original method.
+
+- This allows you to decide dynamically decide whether the original implementation should run or not
+- Like with a postfix, you can also decide the result yourself
+- You can create a state variable that can be passed to a postfix of the same method
+
+The following example patches the `RefreshScore()` method in the `FlyingScoreEffect`, which is the MonoBehaviour
+attached to the text that displays your score when you cut a note. We get a reference to the instance, and also
+the original method params: `score` and `maxPossibleCutScore`.
+
+The patch is pretty self explanatory, but when you score the max possible score for a note - which is 115 for
+a normal note - the text will be replaced with `Hello World!`, and the original method will be ignored. If the score
+does not reach the max possible score, then the original method will be called instead.
+
+```c#
+[HarmonyPatch(typeof(FlyingScoreEffect), "RefreshScore")]
+public class ExamplePatch
+{
+ public static bool Prefix(FlyingScoreEffect __instance, int score, int maxPossibleCutScore)
+ {
+ if (score >= maxPossibleCutScore)
+ {
+ __instance._text.text = "Hello World!";
+ __instance._colorAMultiplier = 1f;
+
+ // Cancel the original method
+ return false;
+ }
+
+ // Run the original implementation
+ return true;
+ }
+}
+```
+
+### Transpiler
+
+The last commonly used patch we will mention is the transpiler. These are used to modify the
+[CIL](https://en.wikipedia.org/wiki/Common_Intermediate_Language) code of the game directly. With these, you can
+make changes in the middle of methods.
+
+This type of patch is much more complicated, and we won't provide an example here (for now), but we can recommend
+checking out transpilers from other mods. As always, if you want to learn more about transpilers, check the
+[documentation](https://harmony.pardeike.net/articles/patching-transpiler.html).
+
+## Accessing Private Code
+
+When making mods you often will need to alter `private` fields, or call `private` methods. Thankfully, in C# there are
+some methods that allow us to do this.
+
+### Publicizing Assemblies
+
+The easiest and recommended way to access `private` members is by utilizing the `BepInEx.AssemblyPublicizer.MSBuild`
+NuGet package. To add this to your project, do one of the following:
+
+- Navigate to your project dependencies in the assembly explorer, right click it, and select `Manage NuGet Packages`.
+ Then, search for "BepInEx.AssemblyPublicizer.MSBuild", right click it, and select install;
+- or navigate to the top bar and look for `Tools | NuGet | Manage NuGet Packages for Solution` and search for it there.
+
+Alternatively, you can add it manually in the `.csproj` project file manually by adding a `PackageReference`:
+
+```xml
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+```
+
+Once installed, all you have to do is add the `Publicize` property to an assembly reference like this:
+
+```xml
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\Main.dll
+ false
+
+```
+
+Now, anything that was `private` or `protected` will be seen as `public` by the compiler, allowing you to bypass this
+restriction.
+
+The only restriction you will run in to now is with `readonly` fields and auto-computed properties (see below).
+If you want to set the value of these, you will have to use [reflection](#reflection).
+
+```c#
+public readonly float _field;
+public float Property { get; }
+
+```
+
+::: danger IMPORTANT
+**Do not use the assembly publicizer to publicize other mods**. This can cause some problems with the mod loader. Instead,
+use [reflection](#reflection) or make a request to the mod's maintainer to add a change if you need it.
+:::
+
+### Reflection
+
+Reflection is a special feature of C# that lets you read code at runtime by making types into objects which you can access
+members from. There is a lot you can do with reflection, and something that it is commonly used for is checking if certain
+parts of another mod's code are running without actually having to reference that mod's assembly.
+
+If you want to read more about reflection you can check
+[Microsoft's docs](https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/reflection-and-attributes/).
+
+IPA provides some utilities to use reflection to get and set values of members, and invoke methods, even if they are private.
+
+```c#
+using IPA.Utilities;
+```
+
+Now we can use the `ReflectionUtil` class, which provides a couple extension methods to pretty easily access private members
+of an object.
+
+```c#
+public class SomeClass
+{
+ private float someValue = 0.25f;
+
+ public float SomeValue => someValue;
+}
+```
+
+Let's say we had a reference to an object of type `SomeClass`, we can access and set the private field by using `SetField`,
+this works even when the field is `readonly`.
+
+```c#
+var someClass = new SomeClass();
+someClass.SetField("someValue", 0.5f);
+```
+
+::: tip NOTE
+If you are just reading the values of members, accessing methods, or setting the values of **non-readonly** fields and properties,
+you should use the [Assembly Publicizer](#publicizing-assemblies), because it is easier to read, is faster, and creates less
+garbage.
+:::
+
+If you must set a field often or repetitively with reflection, you should use the `FieldAccessor` to reduce the
+performance cost. You create the accessor by providing the type of the object the field is on, and the backing type of
+the field, as well as the name of the field itself.
+
+```c#
+private static FieldAccessor.Accessor SomeValueAccessor { get; } =
+ FieldAccessor.GetAccessor("someValue");
+
+private SomeClass someClass = new();
+
+public void SomeMethod()
+{
+ SomeValueAccessor(ref someClass) = 1f;
+}
+```
diff --git a/wiki/modding/pc/index.md b/wiki/modding/pc/index.md
new file mode 100644
index 000000000..a4a2f8ca9
--- /dev/null
+++ b/wiki/modding/pc/index.md
@@ -0,0 +1,74 @@
+---
+prev: false
+next: false
+description: Learn how to create your own PC mods!
+---
+
+# Making PC Mods
+
+Currently, all mods are made using the
+[BSIPA (Beat Saber Illusion Plugin Architecture)](https://github.com/nike4613/BeatSaber-IPA-Reloaded/)
+to inject plugins into the game. It makes the process of executing code in game much easier, and provides many useful
+tools, some of which will be covered in this section of the wiki.
+
+## List of contents
+
+- [Getting a setup ready for creating PC mods](#getting-started)
+- [Useful launch arguments](#launch-args)
+- [Using Runtime Unity Editor](./rue.md)
+- [Inspecting the game code with a decompiler](./decompiling.md)
+- [Harmony patching](./harmony-patching.md)
+- [Creating user interfaces with BeatSaberMarkupLanguage](./bsml.md)
+- [The essentials of Zenject through SiraUtil](./zenject.md)
+- [Writing a functioning mod step-by-step](./full-mod-guide.md)
+- [Other links](#other-links)
+
+## Getting Started
+
+If you are interested in creating a Beat Saber mod, but do not have a template or Visual Studio template set up,
+follow the [setup guide](./setup.md) to get your project all set up.
+
+If you have any questions at any point, the best place to ask is in the `#pc-mod-dev` channel on the
+[BSMG Discord](https://discord.gg/beatsabermods), another modder may be able to help you solve your problem.
+
+## Launch args
+
+Listed in the table below are numerous helpful launch arguments that will make modding / debugging easier.
+
+If you are using Steam, you can enter these by right-clicking the game in Steam, then `Properties...`, then `General`.
+
+If you are using BSManager, you can enter these by opening the `Advanced launch` option on the game launch section.
+BSManager also already provides FPFC and Debug modes, which correspond to `fpfc` and `--verbose` respectively.
+
+
+
+| Argument | Description |
+| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `--verbose` | Enables the output log window for IPA. This will show the debug console that mods use. This is a must-have for all modders. |
+| `--debug` | Enables 'debug' level logs to show up in the log output window. These would otherwise normally only show up in log files. |
+| `--trace` | Enables 'trace' level logs to show up in the log output window. These are typically reserved for overly-detailed logs. |
+| `fpfc` | The "First Person Flying Controller" is a base-game feature that allows you to use WASD and mouse to control the camera without VR. This makes for easy testing. |
+| `--auto_play` | A base-game feature since version 1.37.1, it enables a basic auto player. This is useful for testing gameplay without playing yourself. |
+| `-vrmode oculus` | Only works on versions 1.29.1 and older. Allows you to play without SteamVR when playing the game from Steam. |
+
+
+
+## Other Links
+
+Notable links mentioned in the PC modding wiki:
+
+- [BSIPA Documentation](https://nike4613.github.io/BeatSaber-IPA-Reloaded/articles/start-dev.html)
+- [JetBrains Rider](https://www.jetbrains.com/rider/)
+- [BSMT For JetBrains Rider](https://github.com/Fernthedev/BSMT-Rider/)
+- [BSMT For Visual Studio](https://github.com/Zingabopp/BeatSaberTemplates/)
+- [C# Documentation](https://learn.microsoft.com/en-us/dotnet/csharp/)
+- [Unity Scripting API](https://docs.unity3d.com/ScriptReference/index.html)
+- [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor)
+- [ILSpy](https://github.com/icsharpcode/ILSpy)
+- [dnSpy](https://github.com/dnSpyEx/dnSpy)
+- [Harmony](https://github.com/pardeike/Harmony)
+- [Harmony Documentation](https://harmony.pardeike.net/articles/patching.html)
+- [Zenject](https://github.com/Mathijs-Bakker/Extenject)
+- [BSMG Discord](https://discord.gg/beatsabermods)
+- [BeatMods](https://beatmods.com)
+- [BeatMods Approval Guidelines](https://docs.google.com/document/d/15RBVesZdS-U94AvesJ2DJqcnAtgh9E2PZOcbjrQle5Y/edit?usp=sharing)
diff --git a/wiki/modding/pc/rue.md b/wiki/modding/pc/rue.md
new file mode 100644
index 000000000..44759952a
--- /dev/null
+++ b/wiki/modding/pc/rue.md
@@ -0,0 +1,35 @@
+---
+prev: Setup Guide
+next: Decompiling
+---
+
+# Object Inspectors
+
+An essential tool for modding is a game object inspector.
+
+## Runtime Unity Editor
+
+[Runtime Unity Editor (RUE)](https://github.com/ManlyMarco/RuntimeUnityEditor) is a tool that we can use to look at different
+components in-game while playing. It will allow us to find objects by name, components attached to GameObjects, and
+tweak properties of these while the game is running.
+
+It's important to get used to using RUE because figuring out the game through code will take ten times as much trial-and-error.
+In order to get RUE, currently, you can download it from a pinned message you will find in the `#pc-mod-dev` channel in the
+[BSMG discord](https://discord.gg/beatsabermods).
+
+You will have to [manually install](../../pc-modding.md#manual-installation) RUE by dragging the Libs and Plugins within
+the zip into your game. Once installed, you can open the game in FPFC mode, then press `G` to open RUE.
+
+You can configure the keybinding in `/UserData/Runtime Unity Editor (BSIPA).json` which is recommended because `G` is also
+the default keybinding for SiraUtil's FPFC toggle feature.
+
+
+
+
+
+## UnityExplorer
+
+An alternative to Runtime Unity Editor is [UnityExplorer](https://github.com/yukieiji/UnityExplorer),
+which is also regularly used for Beat Saber modding. You can find all details on how to install UnityExplorer
+[here](https://github.com/yukieiji/UnityExplorer?tab=readme-ov-file#standalone), but, because we use BSIPA, you will
+have to either build it yourself or search around in BSMG for someone who has already done this.
diff --git a/wiki/modding/pc/setup.md b/wiki/modding/pc/setup.md
new file mode 100644
index 000000000..83b5f9ab3
--- /dev/null
+++ b/wiki/modding/pc/setup.md
@@ -0,0 +1,183 @@
+---
+prev: false
+next: Runtime Unity Editor
+---
+
+# PC Mod Development Intro
+
+_Learn how to get started writing your own PC Mods._
+
+## Getting Started
+
+::: warning
+This guide is for making mods for the **PC** version of Beat Saber!
+
+If you want to develop mods for the **Quest Standalone** version of the game, visit
+the [Quest Mod Development Guide](../quest/intro.md)
+
+Make sure your game is modded before trying to make a mod.
+See instructions for [modding Beat Saber on PC.](../../pc-modding.md)
+
+This guide assumes you have a basic to intermediate understanding of C# and Unity.
+You may have difficulty understanding what is covered here if you do not have this foundation.
+:::
+
+Beat Saber is made in Unity 2022.3 using C# with .NET framework 4.7.2. To make writing and building mods as simple as
+possible you will need to download an IDE that supports Unity.
+
+This guide
+will be focused on [JetBrains Rider](https://www.jetbrains.com/rider/), however you can also
+use [Microsoft Visual Studio Community](https://visualstudio.microsoft.com/). Both of these are good options, however,
+the guide for Rider users is more up-to-date.
+
+We will now cover setting up Rider for modding. For Visual Studio users, refer to the
+[Visual Studio Setup](./vs-setup.md) page.
+
+## Modding Tools Setup
+
+We will be using the BeatSaberModdingTools (BSMT) extension in this tutorial, as it comes with modding templates and
+useful features, like saving your Beat Saber directory.
+
+Firstly, download and install [Rider](https://www.jetbrains.com/rider/) from their website. Rider is free for
+non-commercial use.
+
+The Rider extension can be downloaded from [GitHub](https://github.com/Fernthedev/BSMT-Rider/releases/latest). Download
+the BSMT Rider zip.
+
+Once you have installed Rider open it and, after signing in, you will be greeted by the welcome window. In the bottom
+left, click `Configure`, click `Settings`, then look for `Plugins`.
+
+Next to `Marketplace` and `Installed` there will be a settings icon, click this, and click
+`Install Plugin from Disk...`. From here, find the BSMT Rider zip you downloaded and select it, this will install the
+plugin in Rider.
+
+
+
+## Template setup
+
+BSMT comes with some working plugin templates to get you started as quickly as possible.
+
+Create a new solution and, if you installed BSMT correctly, you should be able to select a plugin template from the
+`Custom Templates` list. We are going to use the bare template in this example and, later on, we will be building
+[a functional mod completely from scratch](./full-mod-guide.md).
+
+
+
+Choose a name for your mod and the location you want to save it. Do not save the solution in your Beat Saber
+installation folder lest you lose it.
+
+Once you're done, click `Create` and the mod template will open. Next, you will receive a popup asking you to set your
+Beat Saber Directory.
+
+
+
+Select your Beat Saber game's installation, you can also use a BSManager instance here too. If you select
+`Store this beat saber folder in config`, BSMT will remember this directory whenever you reopen a project.
+
+At this point, **try and build the project**, and it should automatically find the
+references for you and the build should succeed if you set a valid Beat Saber installation directory. You can do this
+with the build hotkey or the button on the top bar.
+
+
+
+If you get any immediate errors, you may want to double-check the Beat Saber directory you provided. You can change it
+by navigating to the `Tools` section at the top of the Rider window, and locating the `BSMT Project Tools` option. If
+you still get errors, you can try restarting Rider.
+
+Once again, if you have any issues you can't resolve, you can always
+ask questions in the `#pc-mod-dev` channel in the BSMG discord.
+
+If you need to manually add Beat Saber assembly or other mod references, right click on `Dependencies` in the Project
+folder, then `Add Beat Saber assembly references`. This will let you search for Beat Saber assemblies, and it will add
+them to the `.csproj` for you.
+
+
+
+## Inspecting the Code
+
+Open the explorer on the right side of Rider and you should see all the project files.
+
+| Filename | About |
+| ------------------------ | ------------------------------------------------------------------------------- |
+| `PluginName.csproj` | This is the C# project that contains build information. |
+| `PluginName.csproj.user` | This is where the Beat Saber directory is saved. BSMT will manage this for you. |
+| `Plugin.cs` | The main file that is loaded for your mod. This is the entry point for BSIPA. |
+| `Directory.Build.props` | Contains metadata for your plugin like the version, links, dependencies etc. |
+
+## Edit your mod's manifest
+
+### Defining Metadata
+
+Open `Directory.Build.props` and fill in your mod's information in the Plugin Metadata `PropertyGroup`:
+
+- The `PluginId` and `PluginName` keys are used to identify your mod. Mods that will be uploaded to BeatMods typically
+ should have these be exactly the same and have no spaces.
+- The `Authors` is where you use your name.
+- The `Version` is the version of your mod. This follows [Semantic Versioning](https://semver.org).
+- The `GameVersion` is the exact version of the game you are making the mod for. It's recommended to make mods for the latest
+ version of the game with mod support.
+- In the `Description` provide a short sentence or two about what your mod is/does.
+
+There are also some optional properties you can add:
+
+- The `ProjectSource` is a URL to the source code of your mod. Most mods have their source code open on GitHub, for
+ instance.
+- The `ProjectHome` can be a URL to a website where your mod is downloaded from or hosted.
+- You can also specify a `Donate` URL, which if you want to, you can set up a way for people to support your modding
+ work.
+- The `PluginIcon` is a path to a `.png` file that can be pulled from your plugin.
+
+### Defining Dependencies
+
+Underneath the plugin metadata is an `ItemGroup` that declares which other mods are required for your mod to work.
+
+::: warning
+Do not remove the dependency on BSIPA. This is required by BSIPA itself.
+:::
+
+The template in this case only needs BSIPA to work. Add additional `DependsOn` members for each dependency.
+
+Some example mod libraries that are commonly used could be BeatSaberMarkupLanguage, which is used to generate custom
+menus in Beat Saber, or SiraUtil, which is used to interface with the game's Zenject system to easily access certain
+game objects and build robust large plugins. These will be briefly covered with some examples later on this wiki.
+
+### Additional Properties
+
+- If your mod breaks in the presence of another mod due to conflicting behavior, you should add it as a `ConflictsWith`
+ member, which will make your plugin not load if the specified conflicting mod is installed.
+- If your mod interacts with other mods but does not need them in order to function, consider adding `LoadAfter` to
+ ensure your mod doesn't try to interact with them before they are loaded by BSIPA.
+- Similarly, you can add `LoadBefore` members to make your mod load before the specified mod.
+- If you want to move `Plugin.cs` to somewhere else in the project, use `PluginHint` to specify where it is so that
+ BSIPA can find it.
+- You can add numerous `RequiredFile` properties to specify external files required by the mod, typically used for libraries.
+
+Once you've set all of this, BSMT will automatically generate an embedded `manifest.json` in your mod during build,
+which is required by BSIPA and can be used to pull information about the mod.
+
+This data can also be pulled from BSIPA to be used within your mod, and by other mods.
+
+## Compiling
+
+After running the build, your compiled DLL should automatically be copied to the `Plugins` folder in your Beat Saber
+directory, which will be done for both debug and release builds.
+
+When you are ready to release your mod, find the dropdown next to the build icon, and select the `Release` option to
+make a Release build of your mod. Building in `Release` mode will generate a packaged `.zip` file ready to distribute.
+This zip file should appear in `\bin\Release\net472\zip\` but you can always look at the build output tab to find the
+zip destination directory.
+
+## Testing your mod in-game
+
+To test if your mod is loaded in-game, you will need to launch Beat Saber with the BSIPA Console enabled. For more
+information on launch arguments, see [here](./index.md#launch-args).
+
+When you launch the game, you should see BSIPA load your mod in the console window.
+
+
+
+If you got this far, congratulations! You are now set up to create mods for Beat Saber.
+
+From here, you should consider checking out the other pages of this wiki to learn about some of the libraries modders
+use to add functionality to their mods, as well as learning to use some essential tools. If it helps, you can follow
+the [full mod guide](./full-mod-guide.md) too, which will cover designing a mod from scratch.
diff --git a/wiki/modding/pc/vs-setup.md b/wiki/modding/pc/vs-setup.md
new file mode 100644
index 000000000..a55f26239
--- /dev/null
+++ b/wiki/modding/pc/vs-setup.md
@@ -0,0 +1,76 @@
+---
+prev: false
+next: false
+---
+
+# Modding Tools Setup for Visual Studio
+
+We will be using the BeatSaberModdingTools (BSMT) extension in this tutorial,
+as it comes with modding templates and useful features, like saving your Beat Saber directory.
+
+The Visual Studio extension can be downloaded
+from [GitHub](https://github.com/Zingabopp/BeatSaberTemplates/releases/latest). You will need to download
+`BeatSaberModdingTools.vsix`. (Expand the Assets dropdown if you cannot find it)
+
+Once downloaded, open the `.vsix` and it will install itself as a Visual Studio Plugin.
+If you have any issues, consult the project's [README](https://github.com/Zingabopp/BeatSaberModdingTools#readme)
+and [WIKI](https://github.com/Zingabopp/BeatSaberModdingTools/wiki).
+
+## Template setup
+
+BSMT comes with some working plugin templates to get you started as quickly as possible.
+
+First, create a new project and find a template. We are going to use the `BSIPA4 Plugin (Core)` template, and we'll be
+calling our mod `BSPlugin1`.
+You should change the name to whatever you want to call your mod.
+
+
+
+
+You will then need to set your Beat Saber Directory in Visual Studio.
+Follow the instructions [on the template readme](https://github.com/Zingabopp/BeatSaberModdingTools#how-to-use),
+or see the screenshot below.
+
+
+
+At this point, **try and build the project**, and it should automatically find the
+references for you and the build should succeed.
+
+If your build does not succeed, check that you don't have any missing references.
+
+::: tip
+BeatSaberModdingTools will automatically handle references. If your references could not be
+found, [double-check the instructions](https://github.com/Zingabopp/BeatSaberModdingTools#beat-saber-modding-tools).
+
+If you need to manually add references, right click on `References` in the Project folder, then
+`Beat Saber Reference Manager...`.
+Select your references, then click "Apply".
+
+You can find more information about the reference
+manager [here](https://github.com/Zingabopp/BeatSaberModdingTools/wiki/Adding-References).
+:::
+
+## Inspecting the Code
+
+You should have 5 files open automatically with the template.
+
+| Filename | About |
+| ------------------------ | ------------------------------------------------------------------------------ |
+| `manifest.json` | Information about your mod for BSIPA. |
+| `Plugin.cs` | The main file that is loaded for your mod. |
+| `AssemblyInfo.cs` | File information about your mod. This is mostly managed by Modding Tools. |
+| `PluginConfig.cs` | A template for enabling config for your mod. This is commented out by default. |
+| `BSPlugin1Controller.cs` | A generic MonoBehaviour for your mod. |
+
+### Edit your mod's Manifest
+
+Fill out the `manifest.json` file with your information.
+The `name` and `id` keys are used to identify your mod.
+The ID should match the ID used when uploading your mod to BeatMods.
+
+::: warning
+Do **not** remove the dependency on BSIPA. As of BSIPA v4.1 this is required for your mod to load.
+:::
+
+After you're done with the setup, you can return to the main
+[PC mod dev intro page](./setup.md#compiling) to find out how to run your mod in game!
diff --git a/wiki/modding/pc/zenject.md b/wiki/modding/pc/zenject.md
new file mode 100644
index 000000000..fbde721d4
--- /dev/null
+++ b/wiki/modding/pc/zenject.md
@@ -0,0 +1,517 @@
+---
+prev: Creating UI
+next: Step-by-step Mod Tutorial
+---
+
+# Zenject Introduction
+
+Zenject is what is called a Dependency Injection (DI) Framework, and Beat Saber's code uses it extensively. You can read
+more about DI on [Microsoft's docs](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection) and
+on [Wikipedia](https://en.wikipedia.org/wiki/Dependency_injection).
+
+## What Is Dependency Injection
+
+Trying to explain dependency injection usually makes it sound a lot more complex than it is. In short, it's when you delegate
+the responsibility certain functionality in your code to "dependencies" and "injecting" them into objects upon their creation.
+
+That is all DI is, but let's look at a simple C# example:
+
+```c#
+public interface IService
+{
+ public int GetNumber();
+}
+
+internal class ServiceImplementation : IService
+{
+ public int GetNumber()
+ {
+ // Implement this method
+ }
+
+ // Some other private behaviour
+}
+```
+
+Now, we have an interface that provides the result of `GetNumber()`. Let's say we needed this behaviour in another object:
+
+```c#
+internal class SomeObject
+{
+ private readonly IService service;
+ private readonly List numbers = [];
+
+ public SomeObject(IService service)
+ {
+ this.service = service;
+ }
+
+ void Update()
+ {
+ if (numbers.Count > 5)
+ {
+ numbers.Clear();
+ }
+
+ int number = _service.GetNumber();
+ numbers.Add(number);
+ }
+}
+```
+
+As we can see, when we create `SomeObject` we have to provide an instance of `IService` because it depends on the service.
+The field `_numbers` is not a dependency in this case; it is just data that belongs to `SomeObject`.
+
+This is essentially all you need to know to understand dependency injection, but the
+[Zenject README](https://github.com/Mathijs-Bakker/Extenject?tab=readme-ov-file#what-is-dependency-injection)
+goes a bit more in-depth about the what and why of DI.
+
+By using dependency injection, you are able to more easily define the behaviour that each feature needs. If you need to
+make changes in the future, your code will have enough abstractness that you should not have to go into every part of
+the code to make everything work together.
+
+## What Is Zenject
+
+Now that you have some idea of what DI looks like, all Zenject does is makes the process of maintaining DI easy. Zenject
+has a lot of different features but it would be pointless to cover them all here, but you can always check the
+[GitHub README](https://github.com/Mathijs-Bakker/Extenject) to learn more about all of its features.
+
+Zenject lets create us objects by declaring their "contract binding" in what they call an `Installer`. We can give keys
+to dependencies, we can provide specific methods to create objects, we can declare multiple implementations of the same
+interface, and more.
+
+## Using Zenject In Mods
+
+In order to easily access the game's implementation of Zenject, we use a library called [SiraUtil](https://github.com/Auros/SiraUtil).
+This is used in a wide variety of mods and it allows us to take full advantage of dependency injection without much
+extra effort.
+
+Before doing anything, add an assembly reference to `SiraUtil`, `Zenject`, and `Zenject-usage`. Make sure you add
+`SiraUtil` as a dependency in your plugin metadata.
+
+### Implementing Zenject
+
+First, add a `Zenjector` param to your plugin class `[Init]` method:
+
+```c#
+[Init]
+public Plugin(Zenjector zenjector)
+{
+
+}
+```
+
+The `Zenjector` will allow you to access the game's `Installer`s and let you make your own bindings with them.
+
+Let's now look at the class we will be using to test Zenject:
+
+```C#
+internal class Test : IInitializable
+{
+ private readonly SiraLog log;
+
+ public Test(SiraLog log) { this.log = log; }
+
+ public void Initialize() => log.Info("Initializable test");
+}
+```
+
+We pass a `SiraLog` instance to this object in the constructor. This is a service provided by SiraUtil, and acts
+as an instance-based logger.
+
+This class implements [IInitializable](https://github.com/Mathijs-Bakker/Extenject?tab=readme-ov-file#iinitializable),
+which is an interface provided by Zenject. The `Initialize()` method gets called after all objects have been created,
+and on Unity's [Start](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.Start.html) event.
+This is ideally where initialization logic for your object would go.
+In this test case, all it does is log a message when created.
+
+Let's make a binding to test this behaviour - we provide an installer and use the callback with the `DiContainer`
+to make a binding:
+
+```c#
+public Plugin(Zenjector zenjector)
+{
+ zenjector.Install(container =>
+ {
+ container.Bind().To().AsSingle();
+ });
+}
+```
+
+What we are doing here is binding `Test` with its `IInitializable` interface on the DiContainer for the
+`StandardGameplayInstaller`. The `AsSingle` method ensures only one instance of `Test` can be bound.
+
+If you build this now and play any map in solo, you will see the "Initializable test" message appear in the console
+when the scene transition ends.
+
+However, the `SiraLog` that we used doesn't have a base logger to use, so the
+source appears as `???`. In order to fix this, we can just provide the `Zenjector` with IPA's logger:
+
+```c#
+zenjector.UseLogger(logger);
+```
+
+### Cleaning Up
+
+It's recommended to organize your bindings in your own installers. Create an installer, and override the
+`InstallBindings()` method:
+
+```c#
+internal class TutorialInstaller : Installer
+{
+ public override void InstallBindings()
+ {
+ Container.BindInterfacesTo().AsSingle();
+ }
+}
+```
+
+We have also made use of the [BindInterfacesTo](https://github.com/Mathijs-Bakker/Extenject?tab=readme-ov-file#bindinterfacesto-and-bindinterfacesandselfto)
+method here, which is just a shortcut so you don't have to remember what interfaces your type implements. It is good to know
+the full expression in case you want to make it clear that you are implementing an interface that will be used as a
+dependency throughout your code.
+
+Now, we just specify the installer to the `Zenjector` with either a base installer to install upon, or by using the
+`location` enum argument to specify a common location:
+
+```c#
+public Plugin(Zenjector zenjector, IPALogger logger)
+{
+ zenjector.UseLogger(logger);
+ zenjector.Install(Location.StandardPlayer);
+}
+```
+
+By doing this we have made the `Plugin` class just responsible for defining the contexts in which the plugin operates
+in, whilst the installers declare the interface of the code.
+
+## Types Of Injection
+
+So far we've only covered injecting dependencies through a constructor, however, there are multiple ways to achieve this
+goal with Zenject.
+
+### Constructors
+
+As covered before, constructor injection is the main form of injection. They force the dependencies to only be resolved
+at object creation, the dependencies are immediately apparent, and they guarantee no circular dependencies which
+encourages better design.
+
+```c#
+internal class SomeObject
+{
+ private readonly IService service;
+
+ public SomeObject(IService service) { this.service = service; }
+}
+
+internal record SomeOtherObject(IService Service);
+```
+
+Unfortunately, MonoBehaviours cannot have constructors, so you are left with method and field injection for those.
+
+### Methods
+
+The `Inject` attribute can be used on methods, and with it we can treat methods just like constructors by supplying the
+dependencies in the params for the method.
+
+```c#
+internal class SomeBehavior : MonoBehaviour
+{
+ private IService service = null!;
+
+ [Inject]
+ public void Init(IService service) { this.service = service; }
+}
+```
+
+As you can see, this example is using a `MonoBehaviour`. Since MonoBehaviours cannot have constructors, this is the
+preferred way to do injection on them. It looks a lot like a constructor which makes the intention of this code
+slightly more clear. That being said, you can use field injection on MonoBehaviours too.
+
+A problem with this approach is that you can't make the field readonly. This can make the code's intent less clear,
+as a field that isn't readonly implies it might be open to changing; you usually aren't going to be changing the
+value of dependencies.
+
+### Fields And Properties
+
+Field and property injections occur directly after the constructor finishes. This is achieved by adding `[Inject]` to any
+field or property.
+
+```c#
+internal class SomeBehavior : MonoBehaviour
+{
+ [Inject]
+ private readonly IService service = null! // assigned by Zenject
+}
+```
+
+Since Zenject uses [reflection](https://learn.microsoft.com/en-us/dotnet/fundamentals/reflection/reflection)
+to set these fields, you can make them private and readonly. This is great for demonstrating the intention of the code,
+but field injection can look a bit cryptic for others looking at the code.
+
+## Common DiContainer Methods
+
+There are dozens of methods to create a binding as seen in the documentation, so let's highlight a few ways of creating
+bindings that you will be mostly using.
+
+
+
+| Name | Description |
+| ---------------------------------- | --------------------------------------------------------------------------------------------------- |
+| `Bind` | Registers the type `T` for injection for itself and other types |
+| `BindInstance` | Registers the type of the provided existing object instance |
+| `BindInterfacesTo` | Registers the interfaces for the type `T` for injection |
+| `BindInterfacesAndSelfTo` | A combination of `Bind` and `BindInterfacesTo` |
+| `AsCached` | The same instance of the object will be reused |
+| `AsSingle` | The same as `AsCached` but ensures only one binding can be made for the result type |
+| `AsTransient` | Instances of the result type will not be reused; a new one will be created each time it's requested |
+| `FromNewComponentOnNewGameObject` | Create an empty `GameObject` and add a new component of the result type on it |
+| `FromNewComponentAsViewController` | Provided by SiraUtil; creates a new view controller - result type must inherit `ViewController` |
+
+
+
+## Zenject With UI
+
+Once you have your SiraUtil setup, you can easily declare all menu-related code in a installer in the menu.
+
+```c#
+zenjector.Install(Location.Menu);
+```
+
+### Binding View Controllers
+
+SiraUtil provides a way to create view controllers easily using `FromNewComponentAsViewController`. You
+can also bind a flow coordinator, but since it is a `MonoBehaviour`, you should use `FromNewComponentOnNewGameObject`,
+or any compatible construction method.
+
+```c#
+internal class MenuInstaller : Installer
+{
+ public override void InstallBindings()
+ {
+ Container.Bind().FromNewComponentAsViewController().AsSingle();
+ Container.Bind().FromNewComponentOnNewGameObject().AsSingle();
+ Container.BindInterfacesTo().AsSingle();
+ }
+}
+```
+
+Now, we would be able to inject our view controllers into the flow coordinator, and we can also inject the
+`MainFlowCoordinator` to make use of it for the menu button.
+
+Additionally, as seen before with the `SiraLog`, we can use bindings made by other mods. Another case is the `MenuButtons`
+class from BSML:
+
+```c#
+internal class MenuButtonManager : IInitializable
+{
+ private readonly MenuButtons menuButtons;
+ private readonly MainFlowCoordinator mainFlowCoordinator;
+ private readonly TutorialFlowCoordinator tutorialFlowCoordinator;
+ private readonly MenuButton menuButton;
+
+ public MenuButtonManager(MenuButtons menuButtons, MainFlowCoordinator mainFlowCoordinator, TutorialFlowCoordinator tutorialFlowCoordinator)
+ {
+ this.menuButtons = menuButtons;
+ this.mainFlowCoordinator = mainFlowCoordinator;
+ this.tutorialFlowCoordinator = tutorialFlowCoordinator;
+ menuButton = new("Tutorial Mod", ShowFlowCoordinator);
+ }
+
+ public void Initialize()
+ {
+ menuButtons.RegisterButton(menuButton);
+ }
+
+ private void ShowFlowCoordinator()
+ {
+ mainFlowCoordinator.PresentFlowCoordinator(tutorialFlowCoordinator);
+ }
+}
+```
+
+This seems more complex than it would be without Zenject, however, Zenject will call `Initialize` for
+us on the first frame of the menu scene being loaded. Most importantly, this class is only responsible
+for doing one thing: managing the menu button.
+
+### Registering Custom Tags
+
+If you have some custom UI tags that you want to use, it's recommended to bind them using Zenject. You
+would bind them like this in a menu installer:
+
+```c#
+Container.Bind().To().AsSingle();
+Container.Bind().To().AsSingle();
+```
+
+## Affinity Patching
+
+SiraUtil provides a way to make non-static [Harmony patches](./harmony-patching.md) using the "Affinity
+API". Being able to make patch methods not static lets you make use of dependency injection for your
+patches.
+
+The syntax is mostly the same, however, Affinity is a lot more limited than Harmony. For the attributes, you must specify
+a `AffinityPatch` attribute on every patch method, and you need to specify a patch type using either `AffinityPostfix`,
+`AffinityPrefix`, or `AffinityTranspiler`. Do note - if you don't provide a patch type attribute then affinity will default
+to a postfix.
+
+### How To Affinity
+
+Below is an example of an affinity patch taken from the SiraUtil documentation. It injects the `PauseController` and causes
+the game to pause every 10 misses and cancels the miss by using a [prefix](./harmony-patching.md#prefix).
+
+```c#
+internal class PauseOnXMisses : IAffinity
+{
+ private readonly PauseController pauseController;
+
+ public PauseOnXMisses(PauseController pauseController)
+ {
+ this.pauseController = pauseController;
+ }
+
+ private int misses = 0;
+
+ [AffinityPrefix]
+ [AffinityPatch(typeof(ScoreController), nameof(ScoreController.HandleNoteWasMissed))]
+ private bool HandleNoteWasMissedPrefix(NoteController noteController)
+ {
+ if (noteController.colorType == ColorType.None && misses++ < 10)
+ {
+ return true;
+ }
+
+ pauseController.Pause();
+ misses = 0;
+ return false;
+ }
+}
+```
+
+As you can see, you just need to add the `IAffinity` interface to the patch class, then you need to bind it in a gameplay
+related installer so that you have access to the `PauseController`.
+
+```c#
+Container.BindInterfacesTo().AsSingle();
+```
+
+### Affinity's Limitations
+
+Affinity is maintained separately from Harmony, so it doesn't have nearly as many features as Harmony does.
+
+The main problem is the timing of the patch. Your patch will only be effective after the object graph is constructed,
+so you can't patch `Awake` methods or constructors, for instance.
+
+Secondly, your patches will be unapplied automatically when the DiContainer it was bound to is disposed, but this should
+be fine in almost all cases.
+
+## Custom Sabers
+
+SiraUtil provides a unified way to replace the vanilla saber model, such that mods do not fight over which saber model
+gets shown.
+
+### Registering A Saber Model
+
+Create a class which inherits from a `SaberModelController`, create the saber model registration, and bind it in a game
+installer. You will have to provide a priority too so SiraUtil can decide which registration to use when there are
+multiple.
+
+```c#
+internal class CustomSaberModelController : SaberModelController { }
+```
+
+```c#
+var registration = SaberModelRegistration.Create(0);
+Container.BindInstance(registration).AsSingle();
+```
+
+### Additional Interfaces
+
+`IColorable` will provide a property which receives a color when one is set by SiraUtil. This is primarily used by
+Chroma to set the color of sabers to the color of Chroma-colored notes.
+
+```c#
+internal class CustomSaberModelController : SaberModelController, IColorable
+{
+ public Color Color { get; set; } // Add behaviour on the setter
+}
+```
+
+`IPreSaberModelInit` and `IPostSaberModelInit` provide methods which will be called before and after the `Init()`
+method of the `SaberModelController` and also provide a reference to the original `Saber` and saber parent `Transform`.
+
+The return type of `PreInit()` is `bool`, and it works just like Harmony prefixes; you should return `true` if you
+want the original `Init` to run, otherwise return `false`.
+
+```c#
+internal class CustomSaberModelController
+ : SaberModelController, IPreSaberModelInit, IPostSaberModelInit
+{
+ public bool PreInit(Transform parent, Saber saber) => true;
+ public void PostInit(Transform parent, Saber saber) { }
+}
+```
+
+## Object Redecorating
+
+Similarly to registering saber models, SiraUtil provides a way to modify the prefabs for various GameObjects before
+they are bound in their installers.
+
+As well as a priority, you can decide if it should be chained, which is useful if your redecoration doesn't causes
+conflicts. SiraUtil will start at the registration with the highest priority, and if it has chaining, it will continue
+to the next highest priority registration until it encounters a registration that doesn't have chaining.
+
+The following example simply takes the `GameObject` of the `BombController` provided by the param of the `BombNoteRegistration`,
+and adds a `CustomBombBehaviour` to it.
+
+```c#
+var bombNoteRegistration = new BombNoteRegistration(
+ redecorateCall: bomb =>
+ {
+ bomb.gameObject.AddComponent();
+ return bombNoteController;
+ },
+ priority: int.MaxValue,
+ chain: true);
+
+Container.RegisterRedecorator(bombNoteRegistration);
+```
+
+Below is a collection of all possible redecorators provided by SiraUtil as of v3.1.14.
+
+### Notes
+
+| Name | Backing Prefab Type |
+| --------------------------------- | ---------------------------------------------- |
+| `BasicNoteRegistration` | `GameNoteController` |
+| `ProModeNoteRegistration` | `GameNoteController` |
+| `BurstSliderHeadNoteRegistration` | `GameNoteController` |
+| `BombNoteRegistration` | `BombNoteRegistration` |
+| `BurstSliderNoteRegistration` | `BurstSliderGameNoteController` |
+| `LongSliderNoteRegistration` | `SliderController` |
+| `MediumSliderNoteRegistration` | `SliderController` |
+| `ShortSliderNoteRegistration` | `SliderController` |
+| `ConnectedPlayerNoteRegistration` | `MultiplayerConnectedPlayerGameNoteController` |
+
+### Debris
+
+| Name | Backing Prefab Type |
+| ----------------------------------------- | ------------------- |
+| `NormalNoteDebrisHDRegistration` | `NoteDebris` |
+| `NormalNoteDebrisLWRegistration` | `NoteDebris` |
+| `BurstSliderHeadNoteDebrisHDRegistration` | `NoteDebris` |
+| `BurstSliderHeadNoteDebrisLWRegistration` | `NoteDebris` |
+| `BurstSliderElementNoteHDRegistration` | `NoteDebris` |
+| `BurstSliderElementNoteLWRegistration` | `NoteDebris` |
+
+### Multiplayer
+
+| Name | Backing Prefab Type |
+| ----------------------------------- | -------------------------------------- |
+| `LocalActivePlayerRegistration` | `MultiplayerLocalActivePlayerFacade` |
+| `LocalActivePlayerDuelRegistration` | `MultiplayerLocalActivePlayerFacade` |
+| `ConnectedPlayerRegistration` | `MultiplayerConnectedPlayerFacade` |
+| `ConnectedPlayerDuelRegistration` | `MultiplayerConnectedPlayerFacade` |
+| `LobbyAvatarPlaceRegistration` | `MultiplayerLobbyAvatarPlace` |
+| `LobbyAvatarRegistration` | `MultiplayerLobbyAvatarController` |
+| `LocalInactivePlayerRegistration` | `MultiplayerLocalInactivePlayerFacade` |
diff --git a/wiki/modding/quest-mod-dev-config.md b/wiki/modding/quest/config.md
similarity index 100%
rename from wiki/modding/quest-mod-dev-config.md
rename to wiki/modding/quest/config.md
diff --git a/wiki/modding/quest-mod-dev-custom-types.md b/wiki/modding/quest/custom-types.md
similarity index 100%
rename from wiki/modding/quest-mod-dev-custom-types.md
rename to wiki/modding/quest/custom-types.md
diff --git a/wiki/modding/quest-mod-dev-intro.md b/wiki/modding/quest/intro.md
similarity index 98%
rename from wiki/modding/quest-mod-dev-intro.md
rename to wiki/modding/quest/intro.md
index 0b9de1de0..7a6f31390 100644
--- a/wiki/modding/quest-mod-dev-intro.md
+++ b/wiki/modding/quest/intro.md
@@ -13,7 +13,7 @@ _Learn how to get started writing your own Quest Mods._
::: warning
This guide is for making mods for the **Quest Standalone** version of Beat Saber!
-If you use Oculus Link or similar, you want to visit the [PC Mod Development Guide](./pc-mod-dev-intro.md) as that uses
+If you use Oculus Link or similar, you want to visit the [PC Mod Development Guide](../pc/index.md) as that uses
the PC version of the game.
:::
@@ -409,7 +409,7 @@ Update the following in your `mod.template.json`:
Most mods require a configuration to allow users to change the functionality of the mod.
-Visit the [Quest Mod Configuration](./quest-mod-dev-config.md) page to learn the basics of using `config-utils` to create
+Visit the [Quest Mod Configuration](./config.md) page to learn the basics of using `config-utils` to create
a configuration for your mod.
## Custom Types
@@ -418,12 +418,12 @@ a configuration for your mod.
classes such as `MonoBehaviour` and much more. `custom-types` also allows you to create and use [coroutines](https://docs.unity3d.com/Manual/Coroutines.html)
and [delegates](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/).
-Custom Types are complex and requires knowledge of basic C#. Visit the [Quest Custom Types](./quest-mod-dev-custom-types.md)
+Custom Types are complex and requires knowledge of basic C#. Visit the [Quest Custom Types](./custom-types.md)
page to learn more about integrating this into your mod.
## User Interface
-A user interface (UI) is used by many mods to show configuration options. Visit the [Quest User Interface](./quest-mod-dev-ui.md)
+A user interface (UI) is used by many mods to show configuration options. Visit the [Quest User Interface](./ui.md)
page to see how to use `bsml` to create a settings screen for your mod.
## Credits
diff --git a/wiki/modding/quest-mod-dev-ui.md b/wiki/modding/quest/ui.md
similarity index 100%
rename from wiki/modding/quest-mod-dev-ui.md
rename to wiki/modding/quest/ui.md