mirror of
https://github.com/Lolendor/reVCDOS.git
synced 2026-10-10 03:43:12 +08:00
19edc9df14
Add support for serving assets from packed binary archives with brotli compression.
New features:
- `packer_brotli.py`: Pack vcsky/vcbr folders into single .bin archive with brotli compression (quality 11)
- `downloader_brotli.py`: Stream unpack archives during download
- `--packed` flag: Serve files directly from archive (supports local files and URLs)
- `--unpacked` flag: Extract archive to `unpacked/{hash}/` and serve from there
- `--vcsky_local` / `--vcbr_local`: Now accept optional custom paths
- MD5 hash detection: Pass existing folder hash directly to `--unpacked`
- `--pack` for packing
Archive format:
- ULEB128 length encoding for compact size
- Folder/file deduplication (identical content stored once)
- Parallel compression with ProcessPoolExecutor
and much more, like ?configurable=1
and auto language detection fix, ...
287 lines
16 KiB
Plaintext
287 lines
16 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# 🎮 reVCDOS Server\n",
|
|
"\n",
|
|
"**Powered by:** [GitHub (Lolendor/reVCDOS)](https://github.com/Lolendor/reVCDOS) [Forgejo (Lolendor/reVCDOS)](https://v13.next.forgejo.org/Lolendor/reVCDOS)\n",
|
|
"\n",
|
|
"Run the cell below to start the server. You will get a public URL and password."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"#@title ▶️ Start Server { display-mode: \"form\" }\n",
|
|
"import subprocess\n",
|
|
"import time\n",
|
|
"import urllib.request\n",
|
|
"import re\n",
|
|
"import sys\n",
|
|
"from IPython.display import clear_output, display, HTML\n",
|
|
"\n",
|
|
"PORT = 8000\n",
|
|
"\n",
|
|
"# Clone repo\n",
|
|
"print(\"📥 Cloning repository...\")\n",
|
|
"subprocess.run([\"git\", \"clone\", \"-q\", \"https://github.com/Lolendor/reVCDOS.git\"], \n",
|
|
" capture_output=True)\n",
|
|
"\n",
|
|
"import os\n",
|
|
"os.chdir(\"reVCDOS\")\n",
|
|
"\n",
|
|
"# Install dependencies\n",
|
|
"print(\"📦 Installing dependencies...\")\n",
|
|
"subprocess.run([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"-r\", \"requirements.txt\"],\n",
|
|
" capture_output=True)\n",
|
|
"\n",
|
|
"# Install localtunnel\n",
|
|
"print(\"🌐 Installing localtunnel...\")\n",
|
|
"subprocess.run([\"npm\", \"install\", \"-g\", \"localtunnel\"], capture_output=True)\n",
|
|
"\n",
|
|
"# Get tunnel password from localtunnel's own endpoint\n",
|
|
"password = urllib.request.urlopen('https://loca.lt/mytunnelpassword').read().decode('utf8').strip()\n",
|
|
"\n",
|
|
"# Start server\n",
|
|
"print(\"🚀 Starting server...\")\n",
|
|
"server = subprocess.Popen(\n",
|
|
" [sys.executable, \"server.py\", \"--packed\", \"https://folder.morgen.monster/revcdos.bin\"],\n",
|
|
" stdout=subprocess.DEVNULL,\n",
|
|
" stderr=subprocess.DEVNULL\n",
|
|
")\n",
|
|
"time.sleep(3)\n",
|
|
"\n",
|
|
"# Start localtunnel and capture URL\n",
|
|
"print(\"🔗 Creating tunnel...\")\n",
|
|
"tunnel = subprocess.Popen(\n",
|
|
" [\"lt\", \"--port\", str(PORT)],\n",
|
|
" stdout=subprocess.PIPE,\n",
|
|
" stderr=subprocess.STDOUT,\n",
|
|
" text=True\n",
|
|
")\n",
|
|
"\n",
|
|
"# Wait for URL\n",
|
|
"tunnel_url = None\n",
|
|
"for _ in range(30):\n",
|
|
" line = tunnel.stdout.readline()\n",
|
|
" if line:\n",
|
|
" match = re.search(r'https://[a-zA-Z0-9-]+\\.loca\\.lt', line)\n",
|
|
" if match:\n",
|
|
" tunnel_url = match.group(0)\n",
|
|
" break\n",
|
|
" time.sleep(0.5)\n",
|
|
"\n",
|
|
"# Clear all output and show only what matters\n",
|
|
"clear_output(wait=True)\n",
|
|
"\n",
|
|
"if tunnel_url:\n",
|
|
" # Display interactive button that copies password and opens URL\n",
|
|
" html = f'''\n",
|
|
" <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",
|
|
" <div style=\"color: #e8eaed; font-size: 22px; font-weight: 500; margin-bottom: 8px;\">Server is ready</div>\n",
|
|
" <div style=\"color: #9aa0a6; font-size: 14px; margin-bottom: 24px;\">The tunnel is active. Configure options and launch the game.</div>\n",
|
|
" \n",
|
|
" <div style=\"margin-bottom: 24px; display: flex; justify-content: center; gap: 24px; align-items: flex-start; flex-wrap: wrap;\">\n",
|
|
" <div style=\"text-align: left;\">\n",
|
|
" <label style=\"color: #9aa0a6; font-size: 12px; display: block; margin-bottom: 6px; font-weight: 500;\">Language</label>\n",
|
|
" <div style=\"position: relative;\">\n",
|
|
" <select id=\"lang\" style=\"\n",
|
|
" appearance: none;\n",
|
|
" background: #202124;\n",
|
|
" color: #e8eaed;\n",
|
|
" border: 1px solid #5f6368;\n",
|
|
" border-radius: 4px;\n",
|
|
" padding: 8px 32px 8px 12px;\n",
|
|
" font-size: 14px;\n",
|
|
" outline: none;\n",
|
|
" cursor: pointer;\n",
|
|
" font-family: inherit;\n",
|
|
" \" onmouseover=\"this.style.borderColor='#dadce0'\" onmouseout=\"this.style.borderColor='#5f6368'\">\n",
|
|
" <option value=\"en\">English</option>\n",
|
|
" <option value=\"ru\">Russian</option>\n",
|
|
" </select>\n",
|
|
" <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",
|
|
" </div>\n",
|
|
" </div>\n",
|
|
" <div style=\"text-align: left;\">\n",
|
|
" <label style=\"color: #9aa0a6; font-size: 12px; display: block; margin-bottom: 6px; font-weight: 500;\">Max FPS</label>\n",
|
|
" <div style=\"position: relative;\">\n",
|
|
" <select id=\"max_fps\" style=\"\n",
|
|
" appearance: none;\n",
|
|
" background: #202124;\n",
|
|
" color: #e8eaed;\n",
|
|
" border: 1px solid #5f6368;\n",
|
|
" border-radius: 4px;\n",
|
|
" padding: 8px 32px 8px 12px;\n",
|
|
" font-size: 14px;\n",
|
|
" outline: none;\n",
|
|
" cursor: pointer;\n",
|
|
" font-family: inherit;\n",
|
|
" \" onmouseover=\"this.style.borderColor='#dadce0'\" onmouseout=\"this.style.borderColor='#5f6368'\" onchange=\"document.getElementById('custom_fps_container').style.display = this.value === 'custom' ? 'block' : 'none'\">\n",
|
|
" <option value=\"\">Unlocked</option>\n",
|
|
" <option value=\"30\">30 FPS</option>\n",
|
|
" <option value=\"60\">60 FPS</option>\n",
|
|
" <option value=\"120\">120 FPS</option>\n",
|
|
" <option value=\"240\">240 FPS</option>\n",
|
|
" <option value=\"custom\">Custom</option>\n",
|
|
" </select>\n",
|
|
" <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",
|
|
" </div>\n",
|
|
" <div id=\"custom_fps_container\" style=\"display: none; margin-top: 8px;\">\n",
|
|
" <input type=\"number\" id=\"custom_fps\" min=\"1\" max=\"240\" placeholder=\"1-240\" style=\"\n",
|
|
" background: #202124;\n",
|
|
" color: #e8eaed;\n",
|
|
" border: 1px solid #5f6368;\n",
|
|
" border-radius: 4px;\n",
|
|
" padding: 8px 12px;\n",
|
|
" font-size: 14px;\n",
|
|
" outline: none;\n",
|
|
" width: 80px;\n",
|
|
" font-family: inherit;\n",
|
|
" \" onmouseover=\"this.style.borderColor='#dadce0'\" onmouseout=\"this.style.borderColor='#5f6368'\">\n",
|
|
" </div>\n",
|
|
" </div>\n",
|
|
" <div style=\"text-align: left;\">\n",
|
|
" <label style=\"color: #9aa0a6; font-size: 12px; display: block; margin-bottom: 6px; font-weight: 500;\">Options</label>\n",
|
|
" <label style=\"display: flex; align-items: center; cursor: pointer; color: #e8eaed; font-size: 14px;\">\n",
|
|
" <input type=\"checkbox\" id=\"cheats\" style=\"\n",
|
|
" appearance: none;\n",
|
|
" width: 18px;\n",
|
|
" height: 18px;\n",
|
|
" border: 2px solid #9aa0a6;\n",
|
|
" border-radius: 2px;\n",
|
|
" margin-right: 8px;\n",
|
|
" position: relative;\n",
|
|
" cursor: pointer;\n",
|
|
" outline: none;\n",
|
|
" \" onchange=\"this.style.backgroundColor = this.checked ? '#8ab4f8' : 'transparent'; this.style.borderColor = this.checked ? '#8ab4f8' : '#9aa0a6';\">\n",
|
|
" Enable Cheats Menu (F3)\n",
|
|
" </label>\n",
|
|
" <label style=\"display: flex; align-items: center; cursor: pointer; color: #e8eaed; font-size: 14px; margin-top: 8px;\">\n",
|
|
" <input type=\"checkbox\" id=\"req_orig\" style=\"\n",
|
|
" appearance: none;\n",
|
|
" width: 18px;\n",
|
|
" height: 18px;\n",
|
|
" border: 2px solid #9aa0a6;\n",
|
|
" border-radius: 2px;\n",
|
|
" margin-right: 8px;\n",
|
|
" position: relative;\n",
|
|
" cursor: pointer;\n",
|
|
" outline: none;\n",
|
|
" \" onchange=\"this.style.backgroundColor = this.checked ? '#8ab4f8' : 'transparent'; this.style.borderColor = this.checked ? '#8ab4f8' : '#9aa0a6';\">\n",
|
|
" Request Original Game Files\n",
|
|
" </label>\n",
|
|
" <label style=\"display: flex; align-items: center; cursor: pointer; color: #e8eaed; font-size: 14px; margin-top: 8px;\">\n",
|
|
" <input type=\"checkbox\" id=\"fullscreen\" checked style=\"\n",
|
|
" appearance: none;\n",
|
|
" width: 18px;\n",
|
|
" height: 18px;\n",
|
|
" border: 2px solid #9aa0a6;\n",
|
|
" border-radius: 2px;\n",
|
|
" margin-right: 8px;\n",
|
|
" position: relative;\n",
|
|
" cursor: pointer;\n",
|
|
" outline: none;\n",
|
|
" background-color: #8ab4f8;\n",
|
|
" border-color: #8ab4f8;\n",
|
|
" \" onchange=\"this.style.backgroundColor = this.checked ? '#8ab4f8' : 'transparent'; this.style.borderColor = this.checked ? '#8ab4f8' : '#9aa0a6';\">\n",
|
|
" Auto Fullscreen\n",
|
|
" </label>\n",
|
|
" </div>\n",
|
|
" </div>\n",
|
|
"\n",
|
|
" <button onclick=\"copyAndOpen()\" style=\"\n",
|
|
" background-color: #8ab4f8;\n",
|
|
" color: #202124;\n",
|
|
" border: none;\n",
|
|
" padding: 12px 32px;\n",
|
|
" font-size: 14px;\n",
|
|
" font-weight: 500;\n",
|
|
" border-radius: 24px;\n",
|
|
" cursor: pointer;\n",
|
|
" transition: background-color 0.2s, box-shadow 0.2s;\n",
|
|
" outline: none;\n",
|
|
" \" 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",
|
|
" onmouseout=\"this.style.backgroundColor='#8ab4f8';this.style.boxShadow='none'\">\n",
|
|
" Launch Game\n",
|
|
" </button>\n",
|
|
" \n",
|
|
" <div id=\"status\" style=\"color: #8ab4f8; margin-top: 16px; font-size: 13px; height: 20px;\"></div>\n",
|
|
" \n",
|
|
" <div style=\"margin-top: 24px; padding: 16px; background-color: #292a2d; border-radius: 8px; text-align: left; border: 1px solid #3c4043;\">\n",
|
|
" <div style=\"margin-bottom: 8px;\">\n",
|
|
" <span style=\"color: #9aa0a6; font-size: 12px; display: block; margin-bottom: 2px;\">Tunnel URL</span>\n",
|
|
" <span style=\"color: #8ab4f8; font-size: 14px; word-break: break-all;\">{tunnel_url}</span>\n",
|
|
" </div>\n",
|
|
" <div>\n",
|
|
" <span style=\"color: #9aa0a6; font-size: 12px; display: block; margin-bottom: 2px;\">Tunnel Password</span>\n",
|
|
" <span style=\"color: #e8eaed; font-size: 14px; font-family: monospace;\">{password}</span>\n",
|
|
" </div>\n",
|
|
" </div>\n",
|
|
" </div>\n",
|
|
" <script>\n",
|
|
" function copyAndOpen() {{\n",
|
|
" const lang = document.getElementById('lang').value;\n",
|
|
" const cheats = document.getElementById('cheats').checked ? '&cheats=1' : '';\n",
|
|
" const reqOrig = document.getElementById('req_orig').checked ? '&request_original_game=1' : '';\n",
|
|
" const fullscreen = document.getElementById('fullscreen').checked ? '' : '&fullscreen=0';\n",
|
|
" const maxFpsSelect = document.getElementById('max_fps').value;\n",
|
|
" let maxFps = '';\n",
|
|
" if (maxFpsSelect === 'custom') {{\n",
|
|
" const customVal = document.getElementById('custom_fps').value;\n",
|
|
" if (customVal && parseInt(customVal) >= 1 && parseInt(customVal) <= 240) {{\n",
|
|
" maxFps = '&max_fps=' + customVal;\n",
|
|
" }}\n",
|
|
" }} else if (maxFpsSelect) {{\n",
|
|
" maxFps = '&max_fps=' + maxFpsSelect;\n",
|
|
" }}\n",
|
|
" const finalUrl = `{tunnel_url}/?lang=${{lang}}${{cheats}}${{reqOrig}}${{fullscreen}}${{maxFps}}`;\n",
|
|
" \n",
|
|
" navigator.clipboard.writeText(\"{password}\").then(function() {{\n",
|
|
" document.getElementById(\"status\").innerHTML = \"✓ Password copied! Opening game...\";\n",
|
|
" setTimeout(function() {{\n",
|
|
" window.open(finalUrl, \"_blank\");\n",
|
|
" }}, 500);\n",
|
|
" }}).catch(function() {{\n",
|
|
" document.getElementById(\"status\").innerHTML = \"Password: {password} (copy manually)\";\n",
|
|
" window.open(finalUrl, \"_blank\");\n",
|
|
" }});\n",
|
|
" }}\n",
|
|
" </script>\n",
|
|
" '''\n",
|
|
" display(HTML(html))\n",
|
|
"else:\n",
|
|
" print(\"❌ Failed to get tunnel URL\")\n",
|
|
"\n",
|
|
"# Keep running\n",
|
|
"try:\n",
|
|
" while tunnel.poll() is None:\n",
|
|
" time.sleep(10)\n",
|
|
"except KeyboardInterrupt:\n",
|
|
" tunnel.terminate()\n",
|
|
" server.terminate()"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"colab": {
|
|
"name": "reVCDOS Server",
|
|
"provenance": []
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"name": "python"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 0
|
|
}
|