diff --git a/appveyor.yml b/appveyor.yml
index d14df8f09f9ad8221b2fc3e55ea9dd6b0ddbae6d..7e14f365f96b31b824d19405bd135991c9febd27 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -21,12 +21,36 @@ environment:
  CCACHE_DIR: C:\Users\appveyor\.ccache
  # Disable UPX by default. The user can override this in their Appveyor project settings
  NOUPX: 1
+ ##############################
+ # DEPLOYER VARIABLES
+ # DPL_ENABLED=1 builds installers for branch names starting with `deployer`.
+ # DPL_TAG_ENABLED=1 will also build installers for release tags. DPL_ENABLED=1 must also be set.
+ # Set these in the Appveyor project settings
+ ##############################
+ DPL_ENABLED: 0
+ DPL_TAG_ENABLED: 0
+ DPL_INSTALLER_NAME: SRB2-v2123
+ # Asset handling is barebones vs. Travis Deployer. We operate on 7z only.
+ # Include the README files and the OpenGL batch in the main and patch archives.
+ # The x86/x64 archives contain the DLL binaries.
+ ASSET_ARCHIVE_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-assets.7z
+ ASSET_ARCHIVE_PATCH_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-patch-assets.7z
+ ASSET_ARCHIVE_X86_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-x86-assets.7z
+ ASSET_ARCHIVE_X64_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-x64-assets.7z
+ ASSET_FILES_OPTIONAL_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/music.dta
+ # This is overridden to 1 for release tag builds
+ ASSET_FILES_OPTIONAL_GET: 0
+ # For patches, also include the X86/X64 DLLs.
+ ASSET_PATCH_DLL_GET: 0
+ # Delete all asset downloads so they can be redownloaded
+ ASSET_CLEAN: 0
 
 cache:
 - nasm-2.12.01.zip
 - upx391w.zip
 - ccache.exe
 - C:\Users\appveyor\.ccache
+- assets\deployer\archives
 
 install:
 - if [%CONFIGURATION%] == [SDL64] ( set "X86_64=1" )
@@ -92,6 +116,10 @@ after_build:
 - appveyor PushArtifact %BUILD_ARCHIVE%
 - cmd: copy %BUILD_ARCHIVE% %BUILDSARCHIVE%
 - appveyor PushArtifact %BUILDSARCHIVE%
+##############################
+# DEPLOYER SCRIPT
+##############################
+- if [%DPL_ENABLED%] == [1] ( call "deployer\appveyor\deployer.bat" )
 
 test: off
 
diff --git a/deployer/appveyor/deployer.bat b/deployer/appveyor/deployer.bat
new file mode 100644
index 0000000000000000000000000000000000000000..c3a29bf848c8925d4253a26452452ddf0776179a
--- /dev/null
+++ b/deployer/appveyor/deployer.bat
@@ -0,0 +1,193 @@
+@setlocal enableextensions enabledelayedexpansion
+
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+: Appveyor Deployer
+: See appveyor.yml for default variables
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+: Evaluate whether we should be deploying
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+if not [%DPL_ENABLED%] == [1] (
+    echo Deployer is not enabled...
+    exit /b
+)
+
+: Don't do DD installs because fmodex DLL handling is not implemented.
+if [%CONFIGURATION%] == [DD] (
+    echo Deployer does not support DD builds...
+    exit /b
+)
+
+if [%CONFIGURATION%] == [DD64] (
+    echo Deployer does not support DD builds...
+    exit /b
+)
+
+: Substring match from https://stackoverflow.com/questions/7005951/batch-file-find-if-substring-is-in-string-not-in-a-file
+: The below line says "if deployer is NOT in string"
+: Note that APPVEYOR_REPO_BRANCH for pull request builds is the BASE branch that PR is merging INTO
+if x%APPVEYOR_REPO_BRANCH:deployer=%==x%APPVEYOR_REPO_BRANCH% (
+    if not [%APPVEYOR_REPO_TAG%] == [true] (
+        echo Deployer is enabled but we are not in a release tag or a 'deployer' branch...
+        exit /b
+    ) else (
+        if not [%DPL_TAG_ENABLED%] == [1] (
+            echo Deployer is not enabled for release tags...
+            exit /b
+        )
+    )
+)
+
+: Release tags always get optional assets (music.dta)
+if [%APPVEYOR_REPO_TAG%] == [true] (
+    set "ASSET_FILES_OPTIONAL_GET=1"
+)
+
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+: Get asset archives
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+if [%ASSET_CLEAN%] == [1] (
+    echo Cleaning asset archives...
+    rmdir /s /q "assets\deployer\archives"
+)
+
+if not exist "assets\deployer\archives" mkdir "assets\deployer\archives"
+
+goto EXTRACT_ARCHIVES
+
+::::::::::::::::::::::::::::::::
+: ARCHIVE_NAME_PARTS
+: Call this like a function. %archivepath% is the path to extract parts from.
+::::::::::::::::::::::::::::::::
+
+for %%a in (%archivepath%) do (
+    set "file=%%~fa"
+    set "filepath=%%~dpa"
+    set "filename=%%~nxa"
+)
+
+set "localarchivepath=assets\deployer\archives\%filename%"
+
+goto EOF
+
+::::::::::::::::::::::::::::::::
+: EXTRACT_ARCHIVES
+::::::::::::::::::::::::::::::::
+
+set "archivepath=%ASSET_ARCHIVE_PATH%"
+call :ARCHIVE_NAME_PARTS
+set "ASSET_ARCHIVE_PATH_LOCAL=%localarchivepath%"
+if not exist "%localarchivepath%" appveyor DownloadFile "%ASSET_ARCHIVE_PATH%" -FileName "%localarchivepath%"
+
+set "archivepath=%ASSET_ARCHIVE_PATCH_PATH%"
+call :ARCHIVE_NAME_PARTS
+set "ASSET_ARCHIVE_PATCH_PATH_LOCAL=%localarchivepath%"
+if not exist "%localarchivepath%" appveyor DownloadFile "%ASSET_ARCHIVE_PATCH_PATH%" -FileName "%localarchivepath%"
+
+if not [%X86_64%] == [1] (
+    set "archivepath=%ASSET_ARCHIVE_X86_PATH%"
+    call :ARCHIVE_NAME_PARTS
+    set "ASSET_ARCHIVE_X86_PATH_LOCAL=!localarchivepath!"
+    if not exist "!localarchivepath!" appveyor DownloadFile "%ASSET_ARCHIVE_X86_PATH%" -FileName "!localarchivepath!"
+)
+
+if [%X86_64%] == [1] (
+    set "archivepath=%ASSET_ARCHIVE_X64_PATH%"
+    call :ARCHIVE_NAME_PARTS
+    set "ASSET_ARCHIVE_X64_PATH_LOCAL=!localarchivepath!"
+    if not exist "!localarchivepath!" appveyor DownloadFile "%ASSET_ARCHIVE_X64_PATH%" -FileName "!localarchivepath!"
+)
+
+if [%ASSET_FILES_OPTIONAL_GET%] == [1] (
+    set "archivepath=%ASSET_FILES_OPTIONAL_PATH%"
+    call :ARCHIVE_NAME_PARTS
+    set "ASSET_FILES_OPTIONAL_PATH_LOCAL=!localarchivepath!"
+    if not exist "!localarchivepath!" appveyor DownloadFile "%ASSET_FILES_OPTIONAL_PATH%" -FileName "!localarchivepath!"
+)
+
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+: Build the installers
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+mkdir "assets\deployer\installer"
+mkdir "assets\deployer\patch"
+
+7z x -y "%ASSET_ARCHIVE_PATH_LOCAL%" -o"assets\deployer\installer" >null
+7z x -y "%ASSET_ARCHIVE_PATCH_PATH_LOCAL%" -o"assets\deployer\patch" >null
+
+: Copy optional files to full installer (music.dta)
+if [%ASSET_FILES_OPTIONAL_GET%] == [1] (
+    xcopy /I /Y "%ASSET_FILES_OPTIONAL_PATH_LOCAL%" "assets\deployer\installer"
+)
+
+: Copy EXE -- BUILD_PATH is from appveyor.yml
+robocopy /S /ns /nc /nfl /ndl /np /njh /njs "%BUILD_PATH%" "assets\deployer\installer" /XF "*.debug" ".gitignore"
+robocopy /S /ns /nc /nfl /ndl /np /njh /njs "%BUILD_PATH%" "assets\deployer\patch" /XF "*.debug" ".gitignore"
+
+: Are we building DD? (we were supposed to exit earlier!)
+if [%CONFIGURATION%] == [DD] ( set "DPL_INSTALLER_NAME=%DPL_INSTALLER_NAME%-DD" )
+if [%CONFIGURATION%] == [DD64] ( set "DPL_INSTALLER_NAME=%DPL_INSTALLER_NAME%-DD" )
+
+: If we are not a release tag, suffix the filename
+if not [%APPVEYOR_REPO_TAG%] == [true] (
+    set "INSTALLER_SUFFIX=-%APPVEYOR_REPO_BRANCH%-%GITSHORT%-%CONFIGURATION%"
+) else (
+    set "INSTALLER_SUFFIX="
+)
+
+if not [%X86_64%] == [1] ( goto X86_INSTALL )
+
+::::::::::::::::::::::::::::::::
+: X64_INSTALL
+::::::::::::::::::::::::::::::::
+
+: Extract DLL binaries
+7z x -y "%ASSET_ARCHIVE_X64_PATH_LOCAL%" -o"assets\deployer\installer" >null
+if [%ASSET_PATCH_DLL_GET%] == [1] (
+    7z x -y "!ASSET_ARCHIVE_X64_PATH_LOCAL!" -o"assets\deployer\patch" >null
+)
+
+: Build the installer
+7z a -sfx7z.sfx "%DPL_INSTALLER_NAME%-x64-Installer%INSTALLER_SUFFIX%.exe" .\assets\deployer\installer\*
+
+: Build the patch
+7z a "%DPL_INSTALLER_NAME%-x64-Patch%INSTALLER_SUFFIX%.zip" .\assets\deployer\patch\*
+
+: Upload artifacts
+appveyor PushArtifact "%DPL_INSTALLER_NAME%-x64-Installer%INSTALLER_SUFFIX%.exe"
+appveyor PushArtifact "%DPL_INSTALLER_NAME%-x64-Patch%INSTALLER_SUFFIX%.zip"
+
+: We only do x86 OR x64, one at a time, so exit now.
+goto EOF
+
+::::::::::::::::::::::::::::::::
+: X86_INSTALL
+::::::::::::::::::::::::::::::::
+
+: Extract DLL binaries
+7z x -y "%ASSET_ARCHIVE_X86_PATH_LOCAL%" -o"assets\deployer\installer" >null
+if [%ASSET_PATCH_DLL_GET%] == [1] (
+    7z x -y "!ASSET_ARCHIVE_X86_PATH_LOCAL!" -o"assets\deployer\patch" >null
+)
+
+: Build the installer
+7z a -sfx7z.sfx "%DPL_INSTALLER_NAME%-Installer%INSTALLER_SUFFIX%.exe" .\assets\deployer\installer\*
+
+: Build the patch
+7z a "%DPL_INSTALLER_NAME%-Patch%INSTALLER_SUFFIX%.zip" .\assets\deployer\patch\*
+
+: Upload artifacts
+appveyor PushArtifact "%DPL_INSTALLER_NAME%-Installer%INSTALLER_SUFFIX%.exe"
+appveyor PushArtifact "%DPL_INSTALLER_NAME%-Patch%INSTALLER_SUFFIX%.zip"
+
+: We only do x86 OR x64, one at a time, so exit now
+goto EOF
+
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+: EOF
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+endlocal