/* * Copyright (C) 1997-2001 Id Software, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place - Suite 330, Boston, MA 02111-1307, USA. * */ /* Modifications Copyright 2003-2004 Bytonic Software Copyright 2010 Google Inc. */ package com.googlecode.gwtquake.shared.server; import java.nio.ByteBuffer; import com.googlecode.gwtquake.shared.common.Buffers; import com.googlecode.gwtquake.shared.common.Com; import com.googlecode.gwtquake.shared.common.CommandBuffer; import com.googlecode.gwtquake.shared.common.ConsoleVariables; import com.googlecode.gwtquake.shared.common.Constants; import com.googlecode.gwtquake.shared.common.Globals; import com.googlecode.gwtquake.shared.common.Delta; import com.googlecode.gwtquake.shared.common.QuakeFileSystem; import com.googlecode.gwtquake.shared.common.ResourceLoader; import com.googlecode.gwtquake.shared.game.Commands; import com.googlecode.gwtquake.shared.game.Entity; import com.googlecode.gwtquake.shared.game.EntityState; import com.googlecode.gwtquake.shared.game.GameBase; import com.googlecode.gwtquake.shared.game.Info; import com.googlecode.gwtquake.shared.game.PlayerClient; import com.googlecode.gwtquake.shared.game.UserCommand; import com.googlecode.gwtquake.shared.util.Lib; public class User { static Entity sv_player; public static class ucmd_t { public ucmd_t(String n, Runnable r) { name = n; this.r = r; } String name; Runnable r; } static ucmd_t u1 = new ucmd_t("new", new Runnable() { public void run() { User.SV_New_f(); } }); static ucmd_t ucmds[] = { // auto issued new ucmd_t("new", new Runnable() { public void run() { User.SV_New_f(); } }), new ucmd_t("configstrings", new Runnable() { public void run() { User.SV_Configstrings_f(); } }), new ucmd_t("baselines", new Runnable() { public void run() { User.SV_Baselines_f(); } }), new ucmd_t("begin", new Runnable() { public void run() { User.SV_Begin_f(); } }), new ucmd_t("nextserver", new Runnable() { public void run() { User.SV_Nextserver_f(); } }), new ucmd_t("disconnect", new Runnable() { public void run() { User.SV_Disconnect_f(); } }), // issued by hand at client consoles new ucmd_t("info", new Runnable() { public void run() { User.SV_ShowServerinfo_f(); } }), new ucmd_t("download", new Runnable() { public void run() { User.SV_BeginDownload_f(); } }), new ucmd_t("nextdl", new Runnable() { public void run() { User.SV_NextDownload_f(); } }) }; public static final int MAX_STRINGCMDS = 8; /* * ============================================================ * * USER STRINGCMD EXECUTION * * sv_client and sv_player will be valid. * ============================================================ */ /* * ================== SV_BeginDemoServer ================== */ public static void SV_BeginDemoserver() { String name; name = "demos/" + ServerInit.sv.name; ResourceLoader.loadResourceAsync(name, new ResourceLoader.Callback() { public void onSuccess(ByteBuffer result) { ServerInit.sv.demofile = result; } }); } /* * ================ SV_New_f * * Sends the first message from the server to a connected client. This will * be sent on the initial connection and upon each server load. * ================ */ public static void SV_New_f() { String gamedir; int playernum; Entity ent; Com.DPrintf("New() from " + ServerMain.sv_client.name + "\n"); if (ServerMain.sv_client.state != Constants.cs_connected) { Com.Printf("New not valid -- already spawned\n"); return; } // demo servers just dump the file message if (ServerInit.sv.state == Constants.ss_demo) { SV_BeginDemoserver(); return; } // // serverdata needs to go over for all types of servers // to make sure the protocol is right, and to set the gamedir // gamedir = ConsoleVariables.VariableString("gamedir"); // send the serverdata Buffers.writeByte(ServerMain.sv_client.netchan.message, Constants.svc_serverdata); ServerMain.sv_client.netchan.message.putInt( Constants.PROTOCOL_VERSION); ServerMain.sv_client.netchan.message.putInt(ServerInit.svs.spawncount); Buffers.writeByte(ServerMain.sv_client.netchan.message, ServerInit.sv.attractloop ? 1 : 0); Buffers.WriteString(ServerMain.sv_client.netchan.message, gamedir); if (ServerInit.sv.state == Constants.ss_cinematic || ServerInit.sv.state == Constants.ss_pic) playernum = -1; else //playernum = sv_client - svs.clients; playernum = ServerMain.sv_client.serverindex; ServerMain.sv_client.netchan.message.WriteShort(playernum); // send full levelname Buffers.WriteString(ServerMain.sv_client.netchan.message, ServerInit.sv.configstrings[Constants.CS_NAME]); // // game server // if (ServerInit.sv.state == Constants.ss_game) { // set up the entity for the client ent = GameBase.g_edicts[playernum + 1]; ent.s.number = playernum + 1; ServerMain.sv_client.edict = ent; ServerMain.sv_client.lastcmd = new UserCommand(); // begin fetching configstrings Buffers.writeByte(ServerMain.sv_client.netchan.message, Constants.svc_stufftext); Buffers.WriteString(ServerMain.sv_client.netchan.message, "cmd configstrings " + ServerInit.svs.spawncount + " 0\n"); } } /* * ================== SV_Configstrings_f ================== */ public static void SV_Configstrings_f() { int start; Com.DPrintf("Configstrings() from " + ServerMain.sv_client.name + "\n"); if (ServerMain.sv_client.state != Constants.cs_connected) { Com.Printf("configstrings not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if (Lib.atoi(Commands.Argv(1)) != ServerInit.svs.spawncount) { Com.Printf("SV_Configstrings_f from different level\n"); SV_New_f(); return; } start = Lib.atoi(Commands.Argv(2)); // write a packet full of data while (ServerMain.sv_client.netchan.message.cursize < Constants.MAX_MSGLEN / 2 && start < Constants.MAX_CONFIGSTRINGS) { if (ServerInit.sv.configstrings[start] != null && ServerInit.sv.configstrings[start].length() != 0) { Buffers.writeByte(ServerMain.sv_client.netchan.message, Constants.svc_configstring); ServerMain.sv_client.netchan.message.WriteShort(start); Buffers.WriteString(ServerMain.sv_client.netchan.message, ServerInit.sv.configstrings[start]); } start++; } // send next command if (start == Constants.MAX_CONFIGSTRINGS) { Buffers.writeByte(ServerMain.sv_client.netchan.message, Constants.svc_stufftext); Buffers.WriteString(ServerMain.sv_client.netchan.message, "cmd baselines " + ServerInit.svs.spawncount + " 0\n"); } else { Buffers.writeByte(ServerMain.sv_client.netchan.message, Constants.svc_stufftext); Buffers.WriteString(ServerMain.sv_client.netchan.message, "cmd configstrings " + ServerInit.svs.spawncount + " " + start + "\n"); } } /* * ================== SV_Baselines_f ================== */ public static void SV_Baselines_f() { int start; EntityState nullstate; EntityState base; Com.DPrintf("Baselines() from " + ServerMain.sv_client.name + "\n"); if (ServerMain.sv_client.state != Constants.cs_connected) { Com.Printf("baselines not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if (Lib.atoi(Commands.Argv(1)) != ServerInit.svs.spawncount) { Com.Printf("SV_Baselines_f from different level\n"); SV_New_f(); return; } start = Lib.atoi(Commands.Argv(2)); //memset (&nullstate, 0, sizeof(nullstate)); nullstate = new EntityState(null); // write a packet full of data while (ServerMain.sv_client.netchan.message.cursize < Constants.MAX_MSGLEN / 2 && start < Constants.MAX_EDICTS) { base = ServerInit.sv.baselines[start]; if (base.modelindex != 0 || base.sound != 0 || base.effects != 0) { Buffers.writeByte(ServerMain.sv_client.netchan.message, Constants.svc_spawnbaseline); Delta.WriteDeltaEntity(nullstate, base, ServerMain.sv_client.netchan.message, true, true); } start++; } // send next command if (start == Constants.MAX_EDICTS) { Buffers.writeByte(ServerMain.sv_client.netchan.message, Constants.svc_stufftext); Buffers.WriteString(ServerMain.sv_client.netchan.message, "precache " + ServerInit.svs.spawncount + "\n"); } else { Buffers.writeByte(ServerMain.sv_client.netchan.message, Constants.svc_stufftext); Buffers.WriteString(ServerMain.sv_client.netchan.message, "cmd baselines " + ServerInit.svs.spawncount + " " + start + "\n"); } } /* * ================== SV_Begin_f ================== */ public static void SV_Begin_f() { Com.DPrintf("Begin() from " + ServerMain.sv_client.name + "\n"); // handle the case of a level changing while a client was connecting if (Lib.atoi(Commands.Argv(1)) != ServerInit.svs.spawncount) { Com.Printf("SV_Begin_f from different level\n"); SV_New_f(); return; } ServerMain.sv_client.state = Constants.cs_spawned; // call the game begin function PlayerClient.ClientBegin(User.sv_player); CommandBuffer.InsertFromDefer(); } //============================================================================= /* * ================== SV_NextDownload_f ================== */ public static void SV_NextDownload_f() { int r; int percent; int size; if (ServerMain.sv_client.download == null) return; r = ServerMain.sv_client.downloadsize - ServerMain.sv_client.downloadcount; if (r > 1024) r = 1024; Buffers.writeByte(ServerMain.sv_client.netchan.message, Constants.svc_download); ServerMain.sv_client.netchan.message.WriteShort(r); ServerMain.sv_client.downloadcount += r; size = ServerMain.sv_client.downloadsize; if (size == 0) size = 1; percent = ServerMain.sv_client.downloadcount * 100 / size; Buffers.writeByte(ServerMain.sv_client.netchan.message, percent); Buffers.Write(ServerMain.sv_client.netchan.message, ServerMain.sv_client.download, ServerMain.sv_client.downloadcount - r, r); if (ServerMain.sv_client.downloadcount != ServerMain.sv_client.downloadsize) return; QuakeFileSystem.FreeFile(ServerMain.sv_client.download); ServerMain.sv_client.download = null; } /* * ================== SV_BeginDownload_f ================== */ public static void SV_BeginDownload_f() { String name; int offset = 0; name = Commands.Argv(1); if (Commands.Argc() > 2) offset = Lib.atoi(Commands.Argv(2)); // downloaded offset // hacked by zoid to allow more conrol over download // first off, no .. or global allow check if (name.indexOf("..") != -1 || ServerMain.allow_download.value == 0 // leading dot is no good || name.charAt(0) == '.' // leading slash bad as well, must be // in subdir || name.charAt(0) == '/' // next up, skin check || (name.startsWith("players/") && 0 == ServerMain.allow_download_players.value) // now // models || (name.startsWith("models/") && 0 == ServerMain.allow_download_models.value) // now // sounds || (name.startsWith("sound/") && 0 == ServerMain.allow_download_sounds.value) // now maps (note special case for maps, must not be in pak) || (name.startsWith("maps/") && 0 == ServerMain.allow_download_maps.value) // MUST // be // in a // subdirectory || name.indexOf('/') == -1) { // don't allow anything with .. // path Buffers.writeByte(ServerMain.sv_client.netchan.message, Constants.svc_download); ServerMain.sv_client.netchan.message.WriteShort(-1); Buffers.writeByte(ServerMain.sv_client.netchan.message, 0); return; } if (ServerMain.sv_client.download != null) QuakeFileSystem.FreeFile(ServerMain.sv_client.download); ServerMain.sv_client.download = QuakeFileSystem.LoadFile(name); // rst: this handles loading errors, no message yet visible if (ServerMain.sv_client.download == null) { return; } ServerMain.sv_client.downloadsize = ServerMain.sv_client.download.length; ServerMain.sv_client.downloadcount = offset; if (offset > ServerMain.sv_client.downloadsize) ServerMain.sv_client.downloadcount = ServerMain.sv_client.downloadsize; if (ServerMain.sv_client.download == null // special check for maps, if it // came from a pak file, don't // allow // download ZOID || (name.startsWith("maps/") && QuakeFileSystem.file_from_pak != 0)) { Com.DPrintf("Couldn't download " + name + " to " + ServerMain.sv_client.name + "\n"); if (ServerMain.sv_client.download != null) { QuakeFileSystem.FreeFile(ServerMain.sv_client.download); ServerMain.sv_client.download = null; } Buffers.writeByte(ServerMain.sv_client.netchan.message, Constants.svc_download); ServerMain.sv_client.netchan.message.WriteShort(-1); Buffers.writeByte(ServerMain.sv_client.netchan.message, 0); return; } SV_NextDownload_f(); Com.DPrintf("Downloading " + name + " to " + ServerMain.sv_client.name + "\n"); } //============================================================================ /* * ================= SV_Disconnect_f * * The client is going to disconnect, so remove the connection immediately * ================= */ public static void SV_Disconnect_f() { // SV_EndRedirect (); ServerMain.SV_DropClient(ServerMain.sv_client); } /* * ================== SV_ShowServerinfo_f * * Dumps the serverinfo info string ================== */ public static void SV_ShowServerinfo_f() { Info.Print(ConsoleVariables.Serverinfo()); } public static void SV_Nextserver() { String v; //ZOID, ss_pic can be nextserver'd in coop mode if (ServerInit.sv.state == Constants.ss_game || (ServerInit.sv.state == Constants.ss_pic && 0 == ConsoleVariables.VariableValue("coop"))) return; // can't nextserver while playing a normal game ServerInit.svs.spawncount++; // make sure another doesn't sneak in v = ConsoleVariables.VariableString("nextserver"); //if (!v[0]) if (v.length() == 0) CommandBuffer.AddText("killserver\n"); else { CommandBuffer.AddText(v); CommandBuffer.AddText("\n"); } ConsoleVariables.Set("nextserver", ""); } /* * ================== SV_Nextserver_f * * A cinematic has completed or been aborted by a client, so move to the * next server, ================== */ public static void SV_Nextserver_f() { if (Lib.atoi(Commands.Argv(1)) != ServerInit.svs.spawncount) { Com.DPrintf("Nextserver() from wrong level, from " + ServerMain.sv_client.name + "\n"); return; // leftover from last server } Com.DPrintf("Nextserver() from " + ServerMain.sv_client.name + "\n"); SV_Nextserver(); } /* * ================== SV_ExecuteUserCommand ================== */ public static void SV_ExecuteUserCommand(String s) { Com.dprintln("SV_ExecuteUserCommand:" + s ); User.ucmd_t u = null; Commands.TokenizeString(s.toCharArray(), true); User.sv_player = ServerMain.sv_client.edict; // SV_BeginRedirect (RD_CLIENT); int i = 0; for (; i < User.ucmds.length; i++) { u = User.ucmds[i]; if (Commands.Argv(0).equals(u.name)) { u.r.run(); break; } } if (i == User.ucmds.length && ServerInit.sv.state == Constants.ss_game) Commands.ClientCommand(User.sv_player); // SV_EndRedirect (); } /* * =========================================================================== * * USER CMD EXECUTION * * =========================================================================== */ public static void SV_ClientThink(ClientData cl, UserCommand cmd) { cl.commandMsec -= cmd.msec & 0xFF; if (cl.commandMsec < 0 && ServerMain.sv_enforcetime.value != 0) { Com.DPrintf("commandMsec underflow from " + cl.name + "\n"); return; } PlayerClient.ClientThink(cl.edict, cmd); } /* * =================== SV_ExecuteClientMessage * * The current net_message is parsed for the given client * =================== */ public static void SV_ExecuteClientMessage(ClientData cl) { int c; String s; UserCommand nullcmd = new UserCommand(); UserCommand oldest = new UserCommand(), oldcmd = new UserCommand(), newcmd = new UserCommand(); int net_drop; int stringCmdCount; int checksum, calculatedChecksum; int checksumIndex; boolean move_issued; int lastframe; ServerMain.sv_client = cl; User.sv_player = ServerMain.sv_client.edict; // only allow one move command move_issued = false; stringCmdCount = 0; while (true) { if (Globals.net_message.readcount > Globals.net_message.cursize) { Com.Printf("SV_ReadClientMessage: bad read:\n"); Com.Printf(Lib.hexDump(Globals.net_message.data, 32, false)); ServerMain.SV_DropClient(cl); return; } c = Buffers.readUnsignedByte(Globals.net_message); if (c == -1) break; switch (c) { default: Com.Printf("SV_ReadClientMessage: unknown command char\n"); ServerMain.SV_DropClient(cl); return; case Constants.clc_nop: break; case Constants.clc_userinfo: cl.userinfo = Buffers.getString(Globals.net_message); ServerMain.SV_UserinfoChanged(cl); break; case Constants.clc_move: if (move_issued) return; // someone is trying to cheat... move_issued = true; checksumIndex = Globals.net_message.readcount; checksum = Buffers.readUnsignedByte(Globals.net_message); lastframe = Globals.net_message.getInt(); if (lastframe != cl.lastframe) { cl.lastframe = lastframe; if (cl.lastframe > 0) { cl.frame_latency[cl.lastframe & (Constants.LATENCY_COUNTS - 1)] = ServerInit.svs.realtime - cl.frames[cl.lastframe & Constants.UPDATE_MASK].senttime; } } //memset (nullcmd, 0, sizeof(nullcmd)); nullcmd = new UserCommand(); Delta.ReadDeltaUsercmd(Globals.net_message, nullcmd, oldest); Delta.ReadDeltaUsercmd(Globals.net_message, oldest, oldcmd); Delta.ReadDeltaUsercmd(Globals.net_message, oldcmd, newcmd); if (cl.state != Constants.cs_spawned) { cl.lastframe = -1; break; } // if the checksum fails, ignore the rest of the packet calculatedChecksum = Com.BlockSequenceCRCByte( Globals.net_message.data, checksumIndex + 1, Globals.net_message.readcount - checksumIndex - 1, cl.netchan.incoming_sequence); if ((calculatedChecksum & 0xff) != checksum) { Com.DPrintf("Failed command checksum for " + cl.name + " (" + calculatedChecksum + " != " + checksum + ")/" + cl.netchan.incoming_sequence + "\n"); return; } if (0 == ServerMain.sv_paused.value) { net_drop = cl.netchan.dropped; if (net_drop < 20) { //if (net_drop > 2) // Com.Printf ("drop %i\n", net_drop); while (net_drop > 2) { SV_ClientThink(cl, cl.lastcmd); net_drop--; } if (net_drop > 1) SV_ClientThink(cl, oldest); if (net_drop > 0) SV_ClientThink(cl, oldcmd); } SV_ClientThink(cl, newcmd); } // copy. cl.lastcmd.set(newcmd); break; case Constants.clc_stringcmd: s = Buffers.getString(Globals.net_message); // malicious users may try using too many string commands if (++stringCmdCount < User.MAX_STRINGCMDS) SV_ExecuteUserCommand(s); if (cl.state == Constants.cs_zombie) return; // disconnect command break; } } } }