ONE GAME ยท TWO DOORS ยท ZERO OTHER HUMANS
Door 1 โ the game in a browser tab, this minute: one link, any device
Door 2 โ the same game on your own computer, kept forever: the single file in the first drawer
| one tap, any device | one saved file, once | |
| ~143 MB | ~430 MB | |
| 26 | 26, plus any map you add | |
| 9, all on the other team | you choose 1โ10, respawn on | |
| needs the internet | runs from your own disk |
Door 1 โ in a browser tab
- Open cs-online.io/en/#offline โ tap SINGLE PLAYER
- Tap any map โ press 2
- Click the game once to take the mouse โ you are in, bots on the other side
Tab scoreboard ยท B buy menu ยท F4 change map
Door 2 โ on your own computer
- The main link first โ the gameโs own page: the whole game sits there, ~430 MB, and the file below pulls it
- Save the drawerโs file as
cs16-solo.shin a new folder, thenbash cs16-solo.shโ it pulls that build once (~430 MB) and adds the map - Open http://127.0.0.1:8099 โ F4 โ a map โ 1
127.0.0.1 is your own computer โ the name a machine uses for itself. The game opens from your own disk, nothing leaves it.
๐ The whole file โ copy this, save as cs16-solo.sh
Tap the copy icon at the top right of the block โ it takes the whole file in one go.
#!/usr/bin/env bash
# Counter-Strike 1.6 โ offline, in your browser, solo vs bots.
# One-time setup: pulls the public CS 1.6 web build + game files, adds a custom
# map, sets your enemy count and respawn, then serves on http://127.0.0.1:8099
set -euo pipefail
# -------- the lines you change --------
ENEMIES=7 # bots you face (1..10)
RESPAWN=1 # 1 = back the instant you die
MAP_NAME="aa_dima" # the custom map to add
MAP_URL="https://fastdl.gamehosting.rs/cstrike/maps/aa_dima.bsp"
NAV_URL="https://fastdl.gamehosting.rs/cstrike/maps/aa_dima.nav"
# ------------------------------------------
BASE="https://s3.ru1.storage.beget.cloud/fa44ebbed79a-mingame-cloud/cs16-web"
DIR="${1:-$HOME/cs16-solo}"
mkdir -p "$DIR/vendor" "$DIR/nav" "$DIR/cfg" "$DIR/maps"
cd "$DIR"
echo "โถ game files (one time, ~430 MB)โฆ"
[ -f valve.zip ] || curl -sk --tlsv1.2 -A 'Mozilla/5.0' "$BASE/valve.zip" -o valve.zip
curl -sk --tlsv1.2 -A 'Mozilla/5.0' "$BASE/index.html" -o index.html
for f in vendor/jszip.min.js vendor/raw.js vendor/extras.pk3 vendor/xash.wasm vendor/filesystem_stdio.wasm vendor/libref_webgl2.wasm vendor/menu_emscripten_wasm32.wasm vendor/cs_emscripten_wasm32.wasm vendor/client_emscripten_wasm32.wasm nav/manifest.json; do
curl -sk --tlsv1.2 -A 'Mozilla/5.0' "$BASE/$f" -o "$f"
done
for n in $(python3 -c "import json;print(' '.join(json.load(open('nav/manifest.json'))))"); do
curl -sk --tlsv1.2 -A 'Mozilla/5.0' "$BASE/nav/$n" -o "nav/$n"
done
echo "โถ memory fix (stops the page running out of RAM mid-map)"
python3 - index.html <<'PY'
import pathlib, sys
p = pathlib.Path(sys.argv[1])
s = p.read_text()
if 'cs16-solo patched' in s:
sys.exit(0)
a = 'const zip = await JSZip.loadAsync(await fetchZipBytes());'
b = ' files[path] = await file.async("uint8array")\n }))'
c = 'this.FS.writeFile(k, files[k]);'
for line in (a, b, c):
if line not in s:
sys.exit('unexpected build layout at: ' + line[:40])
s = s.replace(a, 'let zip = await JSZip.loadAsync(await fetchZipBytes()); // cs16-solo patched')
s = s.replace(b, b + '\n // cs16-solo patched: everything is unpacked, drop the archive copy\n zip = null;')
s = s.replace(c, c + '\n delete files[k]; // cs16-solo patched: the engine has its copy now')
p.write_text(s)
print(' patched', p)
PY
echo "โถ adding map: $MAP_NAME"
curl -sk --tlsv1.2 -A 'Mozilla/5.0' "$MAP_URL" -o "maps/$MAP_NAME.bsp"
if [ -n "$NAV_URL" ]; then curl -sk --tlsv1.2 -A 'Mozilla/5.0' "$NAV_URL" -o "nav/$MAP_NAME.nav"; fi
python3 - "$MAP_NAME" <<'PY'
import sys, zipfile
name = sys.argv[1]
inner = f"cstrike/maps/{name}.bsp"
with zipfile.ZipFile('valve.zip', 'a') as z:
if inner not in z.namelist():
z.write(f"maps/{name}.bsp", inner)
print(' map inside the archive:', inner)
PY
[ -f "nav/$MAP_NAME.nav" ] && python3 - "$MAP_NAME" <<'PY'
import json,sys
name=sys.argv[1]
nav=json.load(open('nav/manifest.json'))
if f"{name}.nav" not in nav:
nav.append(f"{name}.nav")
json.dump(sorted(nav), open('nav/manifest.json','w'), indent=2)
PY
echo "โถ $ENEMIES enemies ยท respawn $RESPAWN"
cat > cfg/listenserver.cfg <<EOF
mp_maxmoney 16000
mp_startmoney 16000
mp_timelimit 0
mp_roundtime 5
mp_freezetime 0
mp_buytime 0.5
mp_friendlyfire 0
mp_autoteambalance 0
mp_limitteams 0
bot_join_team T
bot_join_after_player 0
bot_quota $ENEMIES
bot_difficulty 2
mp_forcerespawn $RESPAWN
mp_randomspawn $RESPAWN
mp_respawn_immunitytime 2
mp_round_infinite 1
mp_buy_anywhere 1
humans_join_team ct
EOF
cat > cfg/userconfig.cfg <<EOF
name "Player"
maxplayers 16
sv_lan 1
alias bots_add "bot_add"
bind "=" "bots_add"
bind "BACKSPACE" "bot_quota 9"
bind "DEL" "bot_quota 0"
bind "CAPSLOCK" "+duck"
unbind "CTRL"
mp_autoteambalance 0
mp_limitteams 0
bot_quota $ENEMIES
EOF
echo "โถ ready โ open http://127.0.0.1:8099 (Ctrl+C stops)"
python3 -m http.server 8099 --bind 127.0.0.1
Needs: bash, python3, curl โ already on Linux and macOS. Windows: wsl --install, then the same three steps.
โ๏ธ Make it yours โ 1 vs 10, instant respawn, any map
In cs16-solo.sh, the top four lines are yours:
ENEMIES=7 # bots in front of you, 1 to 10
RESPAWN=1 # 1 = back the instant you die, 0 = wait for the round
MAP_NAME="aa_dima"
MAP_URL="https://fastdl.gamehosting.rs/cstrike/maps/aa_dima.bsp"
aa_dima is a small aim map, already included โ its page. Swap those two lines for any .bsp address and the map lands in the list.
In game: = adds a bot, - removes one, Backspace fills the team back up, Delete clears them, Caps Lock crouches.
Pick a door, spawn in, and every round is yours โ no lobby, no rank, no one shouting at you.
!