Avitesh Murmu commited on
Commit
78f3aba
·
1 Parent(s): 76f644e

Fix FastAPI warning, debug structure, and enable CDN cache fallback

Browse files
Files changed (8) hide show
  1. .htaccess +52 -0
  2. Dockerfile +4 -5
  3. colab_reVCDOS.ipynb +286 -0
  4. docker-compose.yml +41 -0
  5. index.php +134 -0
  6. pixi.lock +0 -0
  7. pixi.toml +22 -0
  8. 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 into 'vcsky' folder and FIX CASE SENSITIVITY
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 fix: Rename files/folders inside vcsky
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 "--- VERIFYING FILE STRUCTURE ---" && \
39
- find vcsky -maxdepth 3; \
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
- @app.on_event("startup")
278
- async def startup_event():
 
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)