Avitesh Murmu
commited on
Commit
·
78f3aba
1
Parent(s):
76f644e
Fix FastAPI warning, debug structure, and enable CDN cache fallback
Browse files- .htaccess +52 -0
- Dockerfile +4 -5
- colab_reVCDOS.ipynb +286 -0
- docker-compose.yml +41 -0
- index.php +134 -0
- pixi.lock +0 -0
- pixi.toml +22 -0
- server.py +8 -2
.htaccess
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
RewriteEngine On
|
| 2 |
+
RewriteBase /reVCDOS/
|
| 3 |
+
|
| 4 |
+
# 1. Security Headers (Required)
|
| 5 |
+
<IfModule mod_headers.c>
|
| 6 |
+
Header set Cross-Origin-Opener-Policy "same-origin"
|
| 7 |
+
Header set Cross-Origin-Embedder-Policy "require-corp"
|
| 8 |
+
</IfModule>
|
| 9 |
+
|
| 10 |
+
# 2. DISABLE Apache Compression for .br files (The Fix)
|
| 11 |
+
<IfModule mod_deflate.c>
|
| 12 |
+
# Tell Apache NOT to gzip these files
|
| 13 |
+
SetEnvIfNoCase Request_URI "\.br$" no-gzip dont-vary
|
| 14 |
+
</IfModule>
|
| 15 |
+
|
| 16 |
+
# 3. Explicitly Handle .br Files
|
| 17 |
+
<FilesMatch "\.br$">
|
| 18 |
+
# Disable any default filters that might mess with the binary stream
|
| 19 |
+
RemoveOutputFilter .br
|
| 20 |
+
RemoveType .br
|
| 21 |
+
|
| 22 |
+
# Tell the browser: "This file is encoded with Brotli"
|
| 23 |
+
<IfModule mod_headers.c>
|
| 24 |
+
Header set Content-Encoding "br"
|
| 25 |
+
# Prevent caching issues during testing
|
| 26 |
+
Header set Cache-Control "no-cache, no-store, must-revalidate"
|
| 27 |
+
</IfModule>
|
| 28 |
+
</FilesMatch>
|
| 29 |
+
|
| 30 |
+
# 4. Correct MIME Types
|
| 31 |
+
AddType application/javascript .js
|
| 32 |
+
AddType application/wasm .wasm
|
| 33 |
+
AddType text/css .css
|
| 34 |
+
|
| 35 |
+
# Force MIME types for the specific compressed files
|
| 36 |
+
<FilesMatch "\.data\.br$">
|
| 37 |
+
ForceType application/octet-stream
|
| 38 |
+
</FilesMatch>
|
| 39 |
+
|
| 40 |
+
<FilesMatch "\.wasm\.br$">
|
| 41 |
+
ForceType application/wasm
|
| 42 |
+
</FilesMatch>
|
| 43 |
+
|
| 44 |
+
<FilesMatch "\.js\.br$">
|
| 45 |
+
ForceType application/javascript
|
| 46 |
+
</FilesMatch>
|
| 47 |
+
|
| 48 |
+
# 5. Routing
|
| 49 |
+
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
| 50 |
+
RewriteCond %{REQUEST_FILENAME} -d
|
| 51 |
+
RewriteRule ^ - [L]
|
| 52 |
+
RewriteRule ^.*$ index.php [L,QSA]
|
Dockerfile
CHANGED
|
@@ -21,13 +21,12 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|
| 21 |
# Copy everything
|
| 22 |
COPY --chown=user . .
|
| 23 |
|
| 24 |
-
# UNZIP
|
| 25 |
RUN if [ -f "vcsky.zip" ]; then \
|
| 26 |
-
# We MUST use -d vcsky to create the directory server.py expects
|
| 27 |
mkdir -p vcsky && \
|
| 28 |
unzip -o vcsky.zip -d vcsky && \
|
| 29 |
rm vcsky.zip && \
|
| 30 |
-
# Lowercase
|
| 31 |
find vcsky -depth -exec sh -c ' \
|
| 32 |
base=$(basename "$1"); \
|
| 33 |
dir=$(dirname "$1"); \
|
|
@@ -35,8 +34,8 @@ RUN if [ -f "vcsky.zip" ]; then \
|
|
| 35 |
if [ "$base" != "$new" ]; then \
|
| 36 |
mv "$1" "$dir/$new"; \
|
| 37 |
fi' -- {} +; \
|
| 38 |
-
echo "---
|
| 39 |
-
|
| 40 |
fi
|
| 41 |
|
| 42 |
EXPOSE 7860
|
|
|
|
| 21 |
# Copy everything
|
| 22 |
COPY --chown=user . .
|
| 23 |
|
| 24 |
+
# UNZIP and DEBUG structure
|
| 25 |
RUN if [ -f "vcsky.zip" ]; then \
|
|
|
|
| 26 |
mkdir -p vcsky && \
|
| 27 |
unzip -o vcsky.zip -d vcsky && \
|
| 28 |
rm vcsky.zip && \
|
| 29 |
+
# Lowercase everything
|
| 30 |
find vcsky -depth -exec sh -c ' \
|
| 31 |
base=$(basename "$1"); \
|
| 32 |
dir=$(dirname "$1"); \
|
|
|
|
| 34 |
if [ "$base" != "$new" ]; then \
|
| 35 |
mv "$1" "$dir/$new"; \
|
| 36 |
fi' -- {} +; \
|
| 37 |
+
echo "--- FINAL DIRECTORY TREE ---" && \
|
| 38 |
+
ls -R vcsky | head -n 100; \
|
| 39 |
fi
|
| 40 |
|
| 41 |
EXPOSE 7860
|
colab_reVCDOS.ipynb
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# 🎮 reVCDOS Server\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"**Powered by:** [GitHub (Lolendor/reVCDOS)](https://github.com/Lolendor/reVCDOS) [Forgejo (Lolendor/reVCDOS)](https://v13.next.forgejo.org/Lolendor/reVCDOS)\n",
|
| 10 |
+
"\n",
|
| 11 |
+
"Run the cell below to start the server. You will get a public URL and password."
|
| 12 |
+
]
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"cell_type": "code",
|
| 16 |
+
"execution_count": null,
|
| 17 |
+
"metadata": {},
|
| 18 |
+
"outputs": [],
|
| 19 |
+
"source": [
|
| 20 |
+
"#@title ▶️ Start Server { display-mode: \"form\" }\n",
|
| 21 |
+
"import subprocess\n",
|
| 22 |
+
"import time\n",
|
| 23 |
+
"import urllib.request\n",
|
| 24 |
+
"import re\n",
|
| 25 |
+
"import sys\n",
|
| 26 |
+
"from IPython.display import clear_output, display, HTML\n",
|
| 27 |
+
"\n",
|
| 28 |
+
"PORT = 8000\n",
|
| 29 |
+
"\n",
|
| 30 |
+
"# Clone repo\n",
|
| 31 |
+
"print(\"📥 Cloning repository...\")\n",
|
| 32 |
+
"subprocess.run([\"git\", \"clone\", \"-q\", \"https://github.com/Lolendor/reVCDOS.git\"], \n",
|
| 33 |
+
" capture_output=True)\n",
|
| 34 |
+
"\n",
|
| 35 |
+
"import os\n",
|
| 36 |
+
"os.chdir(\"reVCDOS\")\n",
|
| 37 |
+
"\n",
|
| 38 |
+
"# Install dependencies\n",
|
| 39 |
+
"print(\"📦 Installing dependencies...\")\n",
|
| 40 |
+
"subprocess.run([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"-r\", \"requirements.txt\"],\n",
|
| 41 |
+
" capture_output=True)\n",
|
| 42 |
+
"\n",
|
| 43 |
+
"# Install localtunnel\n",
|
| 44 |
+
"print(\"🌐 Installing localtunnel...\")\n",
|
| 45 |
+
"subprocess.run([\"npm\", \"install\", \"-g\", \"localtunnel\"], capture_output=True)\n",
|
| 46 |
+
"\n",
|
| 47 |
+
"# Get tunnel password from localtunnel's own endpoint\n",
|
| 48 |
+
"password = urllib.request.urlopen('https://loca.lt/mytunnelpassword').read().decode('utf8').strip()\n",
|
| 49 |
+
"\n",
|
| 50 |
+
"# Start server\n",
|
| 51 |
+
"print(\"🚀 Starting server...\")\n",
|
| 52 |
+
"server = subprocess.Popen(\n",
|
| 53 |
+
" [sys.executable, \"server.py\", \"--packed\", \"https://folder.morgen.monster/revcdos.bin\"],\n",
|
| 54 |
+
" stdout=subprocess.DEVNULL,\n",
|
| 55 |
+
" stderr=subprocess.DEVNULL\n",
|
| 56 |
+
")\n",
|
| 57 |
+
"time.sleep(3)\n",
|
| 58 |
+
"\n",
|
| 59 |
+
"# Start localtunnel and capture URL\n",
|
| 60 |
+
"print(\"🔗 Creating tunnel...\")\n",
|
| 61 |
+
"tunnel = subprocess.Popen(\n",
|
| 62 |
+
" [\"lt\", \"--port\", str(PORT)],\n",
|
| 63 |
+
" stdout=subprocess.PIPE,\n",
|
| 64 |
+
" stderr=subprocess.STDOUT,\n",
|
| 65 |
+
" text=True\n",
|
| 66 |
+
")\n",
|
| 67 |
+
"\n",
|
| 68 |
+
"# Wait for URL\n",
|
| 69 |
+
"tunnel_url = None\n",
|
| 70 |
+
"for _ in range(30):\n",
|
| 71 |
+
" line = tunnel.stdout.readline()\n",
|
| 72 |
+
" if line:\n",
|
| 73 |
+
" match = re.search(r'https://[a-zA-Z0-9-]+\\.loca\\.lt', line)\n",
|
| 74 |
+
" if match:\n",
|
| 75 |
+
" tunnel_url = match.group(0)\n",
|
| 76 |
+
" break\n",
|
| 77 |
+
" time.sleep(0.5)\n",
|
| 78 |
+
"\n",
|
| 79 |
+
"# Clear all output and show only what matters\n",
|
| 80 |
+
"clear_output(wait=True)\n",
|
| 81 |
+
"\n",
|
| 82 |
+
"if tunnel_url:\n",
|
| 83 |
+
" # Display interactive button that copies password and opens URL\n",
|
| 84 |
+
" html = f'''\n",
|
| 85 |
+
" <div style=\"font-family: 'Google Sans', Roboto, Arial, sans-serif; padding: 24px; background-color: #202124; border: 1px solid #3c4043; border-radius: 12px; text-align: center; max-width: 600px; margin: 10px auto; box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);\">\n",
|
| 86 |
+
" <div style=\"color: #e8eaed; font-size: 22px; font-weight: 500; margin-bottom: 8px;\">Server is ready</div>\n",
|
| 87 |
+
" <div style=\"color: #9aa0a6; font-size: 14px; margin-bottom: 24px;\">The tunnel is active. Configure options and launch the game.</div>\n",
|
| 88 |
+
" \n",
|
| 89 |
+
" <div style=\"margin-bottom: 24px; display: flex; justify-content: center; gap: 24px; align-items: flex-start; flex-wrap: wrap;\">\n",
|
| 90 |
+
" <div style=\"text-align: left;\">\n",
|
| 91 |
+
" <label style=\"color: #9aa0a6; font-size: 12px; display: block; margin-bottom: 6px; font-weight: 500;\">Language</label>\n",
|
| 92 |
+
" <div style=\"position: relative;\">\n",
|
| 93 |
+
" <select id=\"lang\" style=\"\n",
|
| 94 |
+
" appearance: none;\n",
|
| 95 |
+
" background: #202124;\n",
|
| 96 |
+
" color: #e8eaed;\n",
|
| 97 |
+
" border: 1px solid #5f6368;\n",
|
| 98 |
+
" border-radius: 4px;\n",
|
| 99 |
+
" padding: 8px 32px 8px 12px;\n",
|
| 100 |
+
" font-size: 14px;\n",
|
| 101 |
+
" outline: none;\n",
|
| 102 |
+
" cursor: pointer;\n",
|
| 103 |
+
" font-family: inherit;\n",
|
| 104 |
+
" \" onmouseover=\"this.style.borderColor='#dadce0'\" onmouseout=\"this.style.borderColor='#5f6368'\">\n",
|
| 105 |
+
" <option value=\"en\">English</option>\n",
|
| 106 |
+
" <option value=\"ru\">Russian</option>\n",
|
| 107 |
+
" </select>\n",
|
| 108 |
+
" <div style=\"position: absolute; right: 10px; top: 50%; transform: translateY(-50%); pointer-events: none; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 5px solid #9aa0a6;\"></div>\n",
|
| 109 |
+
" </div>\n",
|
| 110 |
+
" </div>\n",
|
| 111 |
+
" <div style=\"text-align: left;\">\n",
|
| 112 |
+
" <label style=\"color: #9aa0a6; font-size: 12px; display: block; margin-bottom: 6px; font-weight: 500;\">Max FPS</label>\n",
|
| 113 |
+
" <div style=\"position: relative;\">\n",
|
| 114 |
+
" <select id=\"max_fps\" style=\"\n",
|
| 115 |
+
" appearance: none;\n",
|
| 116 |
+
" background: #202124;\n",
|
| 117 |
+
" color: #e8eaed;\n",
|
| 118 |
+
" border: 1px solid #5f6368;\n",
|
| 119 |
+
" border-radius: 4px;\n",
|
| 120 |
+
" padding: 8px 32px 8px 12px;\n",
|
| 121 |
+
" font-size: 14px;\n",
|
| 122 |
+
" outline: none;\n",
|
| 123 |
+
" cursor: pointer;\n",
|
| 124 |
+
" font-family: inherit;\n",
|
| 125 |
+
" \" onmouseover=\"this.style.borderColor='#dadce0'\" onmouseout=\"this.style.borderColor='#5f6368'\" onchange=\"document.getElementById('custom_fps_container').style.display = this.value === 'custom' ? 'block' : 'none'\">\n",
|
| 126 |
+
" <option value=\"\">Unlocked</option>\n",
|
| 127 |
+
" <option value=\"30\">30 FPS</option>\n",
|
| 128 |
+
" <option value=\"60\">60 FPS</option>\n",
|
| 129 |
+
" <option value=\"120\">120 FPS</option>\n",
|
| 130 |
+
" <option value=\"240\">240 FPS</option>\n",
|
| 131 |
+
" <option value=\"custom\">Custom</option>\n",
|
| 132 |
+
" </select>\n",
|
| 133 |
+
" <div style=\"position: absolute; right: 10px; top: 50%; transform: translateY(-50%); pointer-events: none; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 5px solid #9aa0a6;\"></div>\n",
|
| 134 |
+
" </div>\n",
|
| 135 |
+
" <div id=\"custom_fps_container\" style=\"display: none; margin-top: 8px;\">\n",
|
| 136 |
+
" <input type=\"number\" id=\"custom_fps\" min=\"1\" max=\"240\" placeholder=\"1-240\" style=\"\n",
|
| 137 |
+
" background: #202124;\n",
|
| 138 |
+
" color: #e8eaed;\n",
|
| 139 |
+
" border: 1px solid #5f6368;\n",
|
| 140 |
+
" border-radius: 4px;\n",
|
| 141 |
+
" padding: 8px 12px;\n",
|
| 142 |
+
" font-size: 14px;\n",
|
| 143 |
+
" outline: none;\n",
|
| 144 |
+
" width: 80px;\n",
|
| 145 |
+
" font-family: inherit;\n",
|
| 146 |
+
" \" onmouseover=\"this.style.borderColor='#dadce0'\" onmouseout=\"this.style.borderColor='#5f6368'\">\n",
|
| 147 |
+
" </div>\n",
|
| 148 |
+
" </div>\n",
|
| 149 |
+
" <div style=\"text-align: left;\">\n",
|
| 150 |
+
" <label style=\"color: #9aa0a6; font-size: 12px; display: block; margin-bottom: 6px; font-weight: 500;\">Options</label>\n",
|
| 151 |
+
" <label style=\"display: flex; align-items: center; cursor: pointer; color: #e8eaed; font-size: 14px;\">\n",
|
| 152 |
+
" <input type=\"checkbox\" id=\"cheats\" style=\"\n",
|
| 153 |
+
" appearance: none;\n",
|
| 154 |
+
" width: 18px;\n",
|
| 155 |
+
" height: 18px;\n",
|
| 156 |
+
" border: 2px solid #9aa0a6;\n",
|
| 157 |
+
" border-radius: 2px;\n",
|
| 158 |
+
" margin-right: 8px;\n",
|
| 159 |
+
" position: relative;\n",
|
| 160 |
+
" cursor: pointer;\n",
|
| 161 |
+
" outline: none;\n",
|
| 162 |
+
" \" onchange=\"this.style.backgroundColor = this.checked ? '#8ab4f8' : 'transparent'; this.style.borderColor = this.checked ? '#8ab4f8' : '#9aa0a6';\">\n",
|
| 163 |
+
" Enable Cheats Menu (F3)\n",
|
| 164 |
+
" </label>\n",
|
| 165 |
+
" <label style=\"display: flex; align-items: center; cursor: pointer; color: #e8eaed; font-size: 14px; margin-top: 8px;\">\n",
|
| 166 |
+
" <input type=\"checkbox\" id=\"req_orig\" style=\"\n",
|
| 167 |
+
" appearance: none;\n",
|
| 168 |
+
" width: 18px;\n",
|
| 169 |
+
" height: 18px;\n",
|
| 170 |
+
" border: 2px solid #9aa0a6;\n",
|
| 171 |
+
" border-radius: 2px;\n",
|
| 172 |
+
" margin-right: 8px;\n",
|
| 173 |
+
" position: relative;\n",
|
| 174 |
+
" cursor: pointer;\n",
|
| 175 |
+
" outline: none;\n",
|
| 176 |
+
" \" onchange=\"this.style.backgroundColor = this.checked ? '#8ab4f8' : 'transparent'; this.style.borderColor = this.checked ? '#8ab4f8' : '#9aa0a6';\">\n",
|
| 177 |
+
" Request Original Game Files\n",
|
| 178 |
+
" </label>\n",
|
| 179 |
+
" <label style=\"display: flex; align-items: center; cursor: pointer; color: #e8eaed; font-size: 14px; margin-top: 8px;\">\n",
|
| 180 |
+
" <input type=\"checkbox\" id=\"fullscreen\" checked style=\"\n",
|
| 181 |
+
" appearance: none;\n",
|
| 182 |
+
" width: 18px;\n",
|
| 183 |
+
" height: 18px;\n",
|
| 184 |
+
" border: 2px solid #9aa0a6;\n",
|
| 185 |
+
" border-radius: 2px;\n",
|
| 186 |
+
" margin-right: 8px;\n",
|
| 187 |
+
" position: relative;\n",
|
| 188 |
+
" cursor: pointer;\n",
|
| 189 |
+
" outline: none;\n",
|
| 190 |
+
" background-color: #8ab4f8;\n",
|
| 191 |
+
" border-color: #8ab4f8;\n",
|
| 192 |
+
" \" onchange=\"this.style.backgroundColor = this.checked ? '#8ab4f8' : 'transparent'; this.style.borderColor = this.checked ? '#8ab4f8' : '#9aa0a6';\">\n",
|
| 193 |
+
" Auto Fullscreen\n",
|
| 194 |
+
" </label>\n",
|
| 195 |
+
" </div>\n",
|
| 196 |
+
" </div>\n",
|
| 197 |
+
"\n",
|
| 198 |
+
" <button onclick=\"copyAndOpen()\" style=\"\n",
|
| 199 |
+
" background-color: #8ab4f8;\n",
|
| 200 |
+
" color: #202124;\n",
|
| 201 |
+
" border: none;\n",
|
| 202 |
+
" padding: 12px 32px;\n",
|
| 203 |
+
" font-size: 14px;\n",
|
| 204 |
+
" font-weight: 500;\n",
|
| 205 |
+
" border-radius: 24px;\n",
|
| 206 |
+
" cursor: pointer;\n",
|
| 207 |
+
" transition: background-color 0.2s, box-shadow 0.2s;\n",
|
| 208 |
+
" outline: none;\n",
|
| 209 |
+
" \" onmouseover=\"this.style.backgroundColor='#aecbfa';this.style.boxShadow='0 1px 3px 0 rgba(60,64,67,0.3), 0 4px 8px 3px rgba(60,64,67,0.15)'\" \n",
|
| 210 |
+
" onmouseout=\"this.style.backgroundColor='#8ab4f8';this.style.boxShadow='none'\">\n",
|
| 211 |
+
" Launch Game\n",
|
| 212 |
+
" </button>\n",
|
| 213 |
+
" \n",
|
| 214 |
+
" <div id=\"status\" style=\"color: #8ab4f8; margin-top: 16px; font-size: 13px; height: 20px;\"></div>\n",
|
| 215 |
+
" \n",
|
| 216 |
+
" <div style=\"margin-top: 24px; padding: 16px; background-color: #292a2d; border-radius: 8px; text-align: left; border: 1px solid #3c4043;\">\n",
|
| 217 |
+
" <div style=\"margin-bottom: 8px;\">\n",
|
| 218 |
+
" <span style=\"color: #9aa0a6; font-size: 12px; display: block; margin-bottom: 2px;\">Tunnel URL</span>\n",
|
| 219 |
+
" <span style=\"color: #8ab4f8; font-size: 14px; word-break: break-all;\">{tunnel_url}</span>\n",
|
| 220 |
+
" </div>\n",
|
| 221 |
+
" <div>\n",
|
| 222 |
+
" <span style=\"color: #9aa0a6; font-size: 12px; display: block; margin-bottom: 2px;\">Tunnel Password</span>\n",
|
| 223 |
+
" <span style=\"color: #e8eaed; font-size: 14px; font-family: monospace;\">{password}</span>\n",
|
| 224 |
+
" </div>\n",
|
| 225 |
+
" </div>\n",
|
| 226 |
+
" </div>\n",
|
| 227 |
+
" <script>\n",
|
| 228 |
+
" function copyAndOpen() {{\n",
|
| 229 |
+
" const lang = document.getElementById('lang').value;\n",
|
| 230 |
+
" const cheats = document.getElementById('cheats').checked ? '&cheats=1' : '';\n",
|
| 231 |
+
" const reqOrig = document.getElementById('req_orig').checked ? '&request_original_game=1' : '';\n",
|
| 232 |
+
" const fullscreen = document.getElementById('fullscreen').checked ? '' : '&fullscreen=0';\n",
|
| 233 |
+
" const maxFpsSelect = document.getElementById('max_fps').value;\n",
|
| 234 |
+
" let maxFps = '';\n",
|
| 235 |
+
" if (maxFpsSelect === 'custom') {{\n",
|
| 236 |
+
" const customVal = document.getElementById('custom_fps').value;\n",
|
| 237 |
+
" if (customVal && parseInt(customVal) >= 1 && parseInt(customVal) <= 240) {{\n",
|
| 238 |
+
" maxFps = '&max_fps=' + customVal;\n",
|
| 239 |
+
" }}\n",
|
| 240 |
+
" }} else if (maxFpsSelect) {{\n",
|
| 241 |
+
" maxFps = '&max_fps=' + maxFpsSelect;\n",
|
| 242 |
+
" }}\n",
|
| 243 |
+
" const finalUrl = `{tunnel_url}/?lang=${{lang}}${{cheats}}${{reqOrig}}${{fullscreen}}${{maxFps}}`;\n",
|
| 244 |
+
" \n",
|
| 245 |
+
" navigator.clipboard.writeText(\"{password}\").then(function() {{\n",
|
| 246 |
+
" document.getElementById(\"status\").innerHTML = \"✓ Password copied! Opening game...\";\n",
|
| 247 |
+
" setTimeout(function() {{\n",
|
| 248 |
+
" window.open(finalUrl, \"_blank\");\n",
|
| 249 |
+
" }}, 500);\n",
|
| 250 |
+
" }}).catch(function() {{\n",
|
| 251 |
+
" document.getElementById(\"status\").innerHTML = \"Password: {password} (copy manually)\";\n",
|
| 252 |
+
" window.open(finalUrl, \"_blank\");\n",
|
| 253 |
+
" }});\n",
|
| 254 |
+
" }}\n",
|
| 255 |
+
" </script>\n",
|
| 256 |
+
" '''\n",
|
| 257 |
+
" display(HTML(html))\n",
|
| 258 |
+
"else:\n",
|
| 259 |
+
" print(\"❌ Failed to get tunnel URL\")\n",
|
| 260 |
+
"\n",
|
| 261 |
+
"# Keep running\n",
|
| 262 |
+
"try:\n",
|
| 263 |
+
" while tunnel.poll() is None:\n",
|
| 264 |
+
" time.sleep(10)\n",
|
| 265 |
+
"except KeyboardInterrupt:\n",
|
| 266 |
+
" tunnel.terminate()\n",
|
| 267 |
+
" server.terminate()"
|
| 268 |
+
]
|
| 269 |
+
}
|
| 270 |
+
],
|
| 271 |
+
"metadata": {
|
| 272 |
+
"colab": {
|
| 273 |
+
"name": "reVCDOS Server",
|
| 274 |
+
"provenance": []
|
| 275 |
+
},
|
| 276 |
+
"kernelspec": {
|
| 277 |
+
"display_name": "Python 3",
|
| 278 |
+
"name": "python3"
|
| 279 |
+
},
|
| 280 |
+
"language_info": {
|
| 281 |
+
"name": "python"
|
| 282 |
+
}
|
| 283 |
+
},
|
| 284 |
+
"nbformat": 4,
|
| 285 |
+
"nbformat_minor": 0
|
| 286 |
+
}
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
revcdos:
|
| 3 |
+
hostname: reVCDOS
|
| 4 |
+
build:
|
| 5 |
+
context: .
|
| 6 |
+
dockerfile: docker/Dockerfile
|
| 7 |
+
ports:
|
| 8 |
+
- "${OUT_HOST-0.0.0.0}:${OUT_PORT-8000}:${IN_PORT-8000}"
|
| 9 |
+
volumes:
|
| 10 |
+
- .:/app
|
| 11 |
+
networks:
|
| 12 |
+
- default
|
| 13 |
+
environment:
|
| 14 |
+
- AUTH_LOGIN=${AUTH_LOGIN:-}
|
| 15 |
+
- AUTH_PASSWORD=${AUTH_PASSWORD:-}
|
| 16 |
+
- CUSTOM_SAVES=${CUSTOM_SAVES:-}
|
| 17 |
+
- IN_PORT=${IN_PORT:-8000}
|
| 18 |
+
- VCSKY_LOCAL=${VCSKY_LOCAL:-}
|
| 19 |
+
- VCBR_LOCAL=${VCBR_LOCAL:-}
|
| 20 |
+
- VCSKY_URL=${VCSKY_URL:-}
|
| 21 |
+
- VCBR_URL=${VCBR_URL:-}
|
| 22 |
+
- VCSKY_CACHE=${VCSKY_CACHE:-}
|
| 23 |
+
- VCBR_CACHE=${VCBR_CACHE:-}
|
| 24 |
+
- PACKED=${PACKED:-}
|
| 25 |
+
- UNPACKED=${UNPACKED:-}
|
| 26 |
+
- PACK=${PACK:-}
|
| 27 |
+
command: >
|
| 28 |
+
sh -c "python server.py
|
| 29 |
+
--port $${IN_PORT:-8000}
|
| 30 |
+
$$([ -n \"$$AUTH_LOGIN\" ] && [ -n \"$$AUTH_PASSWORD\" ] && echo \"--login $$AUTH_LOGIN --password $$AUTH_PASSWORD\" || echo '')
|
| 31 |
+
$$([ \"$$CUSTOM_SAVES\" = '1' ] && echo '--custom_saves' || echo '')
|
| 32 |
+
$$(if [ \"$$VCSKY_LOCAL\" = '1' ]; then echo '--vcsky_local'; elif [ -n \"$$VCSKY_LOCAL\" ]; then echo \"--vcsky_local $$VCSKY_LOCAL\"; fi)
|
| 33 |
+
$$(if [ \"$$VCBR_LOCAL\" = '1' ]; then echo '--vcbr_local'; elif [ -n \"$$VCBR_LOCAL\" ]; then echo \"--vcbr_local $$VCBR_LOCAL\"; fi)
|
| 34 |
+
$$([ -n \"$$VCSKY_URL\" ] && echo \"--vcsky_url $$VCSKY_URL\" || echo '')
|
| 35 |
+
$$([ -n \"$$VCBR_URL\" ] && echo \"--vcbr_url $$VCBR_URL\" || echo '')
|
| 36 |
+
$$([ \"$$VCSKY_CACHE\" = '1' ] && echo '--vcsky_cache' || echo '')
|
| 37 |
+
$$([ \"$$VCBR_CACHE\" = '1' ] && echo '--vcbr_cache' || echo '')
|
| 38 |
+
$$([ -n \"$$PACKED\" ] && echo \"--packed $$PACKED\" || echo '')
|
| 39 |
+
$$([ -n \"$$UNPACKED\" ] && echo \"--unpacked $$UNPACKED\" || echo '')
|
| 40 |
+
$$([ -n \"$$PACK\" ] && echo \"--pack $$PACK\" || echo '')"
|
| 41 |
+
restart: unless-stopped
|
index.php
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
// ---------------- CONFIG ----------------
|
| 3 |
+
$VCSKY_BASE_URL = "https://cdn.dos.zone/vcsky/";
|
| 4 |
+
$BR_BASE_URL = "https://br.cdn.dos.zone/vcsky/";
|
| 5 |
+
$BASE_PATH = '/reVCDOS'; // public base URL
|
| 6 |
+
$DIST_PATH = __DIR__ . '/dist'; // local directory for SPA
|
| 7 |
+
|
| 8 |
+
// ---------------- HELPERS ----------------
|
| 9 |
+
|
| 10 |
+
// Build URL with query string
|
| 11 |
+
function build_url($base, $path) {
|
| 12 |
+
$query = $_SERVER['QUERY_STRING'];
|
| 13 |
+
$url = rtrim($base, '/') . '/' . ltrim($path, '/');
|
| 14 |
+
if ($query) {
|
| 15 |
+
$url .= '?' . $query;
|
| 16 |
+
}
|
| 17 |
+
return $url;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
// Proxy request using cURL
|
| 21 |
+
function proxy_request($url) {
|
| 22 |
+
$ch = curl_init($url);
|
| 23 |
+
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $_SERVER['REQUEST_METHOD']);
|
| 24 |
+
|
| 25 |
+
// Forward headers
|
| 26 |
+
$headers = [];
|
| 27 |
+
foreach (getallheaders() as $key => $value) {
|
| 28 |
+
if (!in_array(strtolower($key), ['host', 'content-length'])) {
|
| 29 |
+
$headers[] = "$key: $value";
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
$headers[] = "Cross-Origin-Opener-Policy: same-origin";
|
| 33 |
+
$headers[] = "Cross-Origin-Embedder-Policy: require-corp";
|
| 34 |
+
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
| 35 |
+
|
| 36 |
+
// Forward body for POST/PUT/PATCH
|
| 37 |
+
if (!in_array($_SERVER['REQUEST_METHOD'], ['GET', 'HEAD'])) {
|
| 38 |
+
curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents('php://input'));
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
curl_setopt($ch, CURLOPT_HEADER, true);
|
| 42 |
+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
| 43 |
+
|
| 44 |
+
$response = curl_exec($ch);
|
| 45 |
+
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
| 46 |
+
|
| 47 |
+
$header_text = substr($response, 0, $header_size);
|
| 48 |
+
$body = substr($response, $header_size);
|
| 49 |
+
|
| 50 |
+
foreach (explode("\r\n", $header_text) as $header) {
|
| 51 |
+
if ($header && stripos($header, 'Content-Length') === false) {
|
| 52 |
+
header($header, false);
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
echo $body;
|
| 57 |
+
curl_close($ch);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
// ---------------- ROUTING ----------------
|
| 61 |
+
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
| 62 |
+
|
| 63 |
+
// Proxy /vcsky/*
|
| 64 |
+
if (preg_match('#^' . $BASE_PATH . '/vcsky/(.*)#', $uri, $matches)) {
|
| 65 |
+
$url = build_url($VCSKY_BASE_URL, $matches[1]);
|
| 66 |
+
proxy_request($url);
|
| 67 |
+
exit;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// Proxy /vcbr/*
|
| 71 |
+
if (preg_match('#^' . $BASE_PATH . '/vcbr/(.*)#', $uri, $matches)) {
|
| 72 |
+
$url = build_url($BR_BASE_URL, $matches[1]);
|
| 73 |
+
proxy_request($url);
|
| 74 |
+
exit;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
// Serve static files under $BASE_PATH
|
| 79 |
+
if (strpos($uri, $BASE_PATH) === 0) {
|
| 80 |
+
$relativePath = substr($uri, strlen($BASE_PATH));
|
| 81 |
+
if ($relativePath === '' || $relativePath === '/') {
|
| 82 |
+
$relativePath = '/index.html';
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
$localFile = realpath($DIST_PATH . $relativePath);
|
| 86 |
+
|
| 87 |
+
// Ensure the file exists AND is inside $DIST_PATH
|
| 88 |
+
if ($localFile && is_file($localFile) && str_starts_with($localFile, realpath($DIST_PATH))) {
|
| 89 |
+
// Set MIME type manually for common extensions
|
| 90 |
+
$ext = pathinfo($localFile, PATHINFO_EXTENSION);
|
| 91 |
+
switch (strtolower($ext)) {
|
| 92 |
+
case 'js':
|
| 93 |
+
$mimeType = 'application/javascript';
|
| 94 |
+
break;
|
| 95 |
+
case 'css':
|
| 96 |
+
$mimeType = 'text/css';
|
| 97 |
+
break;
|
| 98 |
+
case 'html':
|
| 99 |
+
case 'htm':
|
| 100 |
+
$mimeType = 'text/html';
|
| 101 |
+
break;
|
| 102 |
+
case 'json':
|
| 103 |
+
$mimeType = 'application/json';
|
| 104 |
+
break;
|
| 105 |
+
case 'wasm':
|
| 106 |
+
$mimeType = 'application/wasm';
|
| 107 |
+
break;
|
| 108 |
+
default:
|
| 109 |
+
$mimeType = mime_content_type($localFile) ?: 'application/octet-stream';
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
header("Content-Type: $mimeType");
|
| 113 |
+
header("Cross-Origin-Opener-Policy: same-origin");
|
| 114 |
+
header("Cross-Origin-Embedder-Policy: require-corp");
|
| 115 |
+
readfile($localFile);
|
| 116 |
+
exit;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
// SPA fallback only for HTML requests
|
| 120 |
+
if (str_ends_with($uri, '/') || preg_match('/\.html$/', $uri)) {
|
| 121 |
+
$indexFile = $DIST_PATH . '/index.html';
|
| 122 |
+
if (is_file($indexFile)) {
|
| 123 |
+
header("Content-Type: text/html");
|
| 124 |
+
header("Cross-Origin-Opener-Policy: same-origin");
|
| 125 |
+
header("Cross-Origin-Embedder-Policy: require-corp");
|
| 126 |
+
readfile($indexFile);
|
| 127 |
+
exit;
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
// 404 fallback
|
| 133 |
+
http_response_code(404);
|
| 134 |
+
echo "Not Found";
|
pixi.lock
ADDED
|
Binary file (33 kB). View file
|
|
|
pixi.toml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[workspace]
|
| 2 |
+
authors = ["Th3W33kndXO <th3w33kndxo@gmail.com>"]
|
| 3 |
+
channels = ["conda-forge"]
|
| 4 |
+
name = "GTA VC Server"
|
| 5 |
+
platforms = ["win-64"]
|
| 6 |
+
version = "0.1.0"
|
| 7 |
+
|
| 8 |
+
[tasks]
|
| 9 |
+
local = "python server.py --custom_saves"
|
| 10 |
+
offline = "python server.py --vcsky_local --vcbr_local --custom_saves"
|
| 11 |
+
online = "python server.py --vcsky_cache --vcbr_cache --custom_saves"
|
| 12 |
+
start = "python server.py --vcsky_cache --vcbr_cache --custom_saves"
|
| 13 |
+
cheat = "python server.py --vcsky_cache --vcbr_cache --custom_saves --cheats --open"
|
| 14 |
+
|
| 15 |
+
[dependencies]
|
| 16 |
+
python = "3.12.*"
|
| 17 |
+
fastapi = ">=0.127.0,<0.128"
|
| 18 |
+
uvicorn = ">=0.40.0,<0.41"
|
| 19 |
+
httpx = ">=0.28.1,<0.29"
|
| 20 |
+
python-multipart = ">=0.0.21,<0.0.22"
|
| 21 |
+
brotli-python = ">=1.2.0,<2"
|
| 22 |
+
aiofiles = ">=25.1.0,<26"
|
server.py
CHANGED
|
@@ -8,6 +8,7 @@ from fastapi import FastAPI, Request, HTTPException
|
|
| 8 |
from fastapi.responses import Response
|
| 9 |
from fastapi.staticfiles import StaticFiles
|
| 10 |
import additions.saves as saves
|
|
|
|
| 11 |
from additions.auth import BasicAuthMiddleware
|
| 12 |
from additions.cache import proxy_and_cache, get_local_file
|
| 13 |
from additions.packed import init_packed_archive, get_packed_file, is_initialized as packed_is_initialized
|
|
@@ -274,9 +275,14 @@ async def setup_unpacked(source: str) -> tuple:
|
|
| 274 |
|
| 275 |
app = FastAPI()
|
| 276 |
|
| 277 |
-
@
|
| 278 |
-
async def
|
|
|
|
| 279 |
await init_server()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
|
| 281 |
if args.login and args.password:
|
| 282 |
app.add_middleware(BasicAuthMiddleware, username=args.login, password=args.password)
|
|
|
|
| 8 |
from fastapi.responses import Response
|
| 9 |
from fastapi.staticfiles import StaticFiles
|
| 10 |
import additions.saves as saves
|
| 11 |
+
from contextlib import asynccontextmanager
|
| 12 |
from additions.auth import BasicAuthMiddleware
|
| 13 |
from additions.cache import proxy_and_cache, get_local_file
|
| 14 |
from additions.packed import init_packed_archive, get_packed_file, is_initialized as packed_is_initialized
|
|
|
|
| 275 |
|
| 276 |
app = FastAPI()
|
| 277 |
|
| 278 |
+
@asynccontextmanager
|
| 279 |
+
async def lifespan(app: FastAPI):
|
| 280 |
+
# This runs on startup
|
| 281 |
await init_server()
|
| 282 |
+
yield
|
| 283 |
+
# This runs on shutdown (if needed)
|
| 284 |
+
|
| 285 |
+
app = FastAPI(lifespan=lifespan)
|
| 286 |
|
| 287 |
if args.login and args.password:
|
| 288 |
app.add_middleware(BasicAuthMiddleware, username=args.login, password=args.password)
|