void D_SendServerInfoChange (const FBaseCVar *cvar, UCVarValue value, ECVarType type) { size_t namelen; namelen = strlen (cvar->GetName ()); Net_WriteByte (DEM_SINFCHANGED); Net_WriteByte ((BYTE)(namelen | (type << 6))); Net_WriteBytes ((BYTE *)cvar->GetName (), (int)namelen); switch (type) { case CVAR_Bool: Net_WriteByte (value.Bool); break; case CVAR_Int: Net_WriteLong (value.Int); break; case CVAR_Float: Net_WriteFloat (value.Float); break; case CVAR_String: Net_WriteString (value.String); break; default: break; // Silence GCC } }
void FWeaponSlots::SendDifferences(const FWeaponSlots &other) { int i, j; for (i = 0; i < NUM_WEAPON_SLOTS; ++i) { if (other.Slots[i].Size() == Slots[i].Size()) { for (j = (int)Slots[i].Size(); j-- > 0; ) { if (other.Slots[i].GetWeapon(j) != Slots[i].GetWeapon(j)) { break; } } if (j < 0) { // The two slots are the same. continue; } } // The slots differ. Send mine. Net_WriteByte(DEM_SETSLOT); Net_WriteByte(i); Net_WriteByte(Slots[i].Size()); for (j = 0; j < Slots[i].Size(); ++j) { Net_WriteWeapon(Slots[i].GetWeapon(j)); } } }
/* * @brief Pumps the command cycle, sending the most recently gathered movement * to the server. */ void Cl_SendCmd(void) { mem_buf_t buf; byte data[128]; if (cls.state <= CL_CONNECTING) return; if (cls.state == CL_CONNECTED) { // send any reliable messages and / or don't timeout if (cls.net_chan.message.size || cls.real_time - cls.net_chan.last_sent > 1000) Netchan_Transmit(&cls.net_chan, NULL, 0); return; } // send a user info update if needed if (cvar_user_info_modified) { Net_WriteByte(&cls.net_chan.message, CL_CMD_USER_INFO); Net_WriteString(&cls.net_chan.message, Cvar_UserInfo()); cvar_user_info_modified = false; } // finalize the current command Cl_FinalizeCmd(); // and write it out Mem_InitBuffer(&buf, data, sizeof(data)); Net_WriteByte(&buf, CL_CMD_MOVE); // let the server know what the last frame we got was, so the next // message can be delta compressed if (!cl.frame.valid || (cls.demo_file && Fs_Tell(cls.demo_file) == 0)) Net_WriteLong(&buf, -1); // no compression else Net_WriteLong(&buf, cl.frame.frame_num); // send this and the previous two cmds in the message, so // if the last packet was dropped, it can be recovered static user_cmd_t null_cmd; cl_cmd_t *cmd = &cl.cmds[(cls.net_chan.outgoing_sequence - 2) & CMD_MASK]; Net_WriteDeltaUserCmd(&buf, &null_cmd, &cmd->cmd); user_cmd_t *old_cmd = &cmd->cmd; cmd = &cl.cmds[(cls.net_chan.outgoing_sequence - 1) & CMD_MASK]; Net_WriteDeltaUserCmd(&buf, old_cmd, &cmd->cmd); old_cmd = &cmd->cmd; cmd = &cl.cmds[(cls.net_chan.outgoing_sequence) & CMD_MASK]; Net_WriteDeltaUserCmd(&buf, old_cmd, &cmd->cmd); // deliver the message Netchan_Transmit(&cls.net_chan, buf.data, buf.size); cl.packet_counter++; // initialize the next command Cl_InitCmd(); }
void Net_WriteWeapon(PClassWeapon *type) { int index, *index_p; index_p = Weapons_hton.CheckKey(type); if (index_p == NULL) { index = 0; } else { index = *index_p; } // 32767 weapons better be enough for anybody. assert(index >= 0 && index <= 32767); if (index < 128) { Net_WriteByte(index); } else { Net_WriteByte(0x80 | index); Net_WriteByte(index >> 7); } }
static void SummonActor (int command, int command2, FCommandLine argv) { if (CheckCheatmode ()) return; if (argv.argc() > 1) { const PClass *type = PClass::FindClass (argv[1]); if (type == NULL) { Printf ("Unknown class '%s'\n", argv[1]); return; } Net_WriteByte (argv.argc() > 2 ? command2 : command); Net_WriteString (type->TypeName.GetChars()); if (argv.argc () > 2) { Net_WriteWord (atoi (argv[2])); // angle if (argv.argc () > 3) Net_WriteWord (atoi (argv[3])); // TID else Net_WriteWord (0); if (argv.argc () > 4) Net_WriteByte (atoi (argv[4])); // special else Net_WriteByte (0); for(int i = 5; i < 10; i++) { // args[5] if(i < argv.argc()) Net_WriteLong (atoi (argv[i])); else Net_WriteLong (0); } } } }
static void SummonActor (int command, int command2, FCommandLine argv) { if (CheckCheatmode ()) return; if (argv.argc() > 1) { PClassActor *type = PClass::FindActor(argv[1]); if (type == NULL) { Printf ("Unknown actor '%s'\n", argv[1]); return; } Net_WriteByte (argv.argc() > 2 ? command2 : command); Net_WriteString (type->TypeName.GetChars()); if (argv.argc () > 2) { Net_WriteWord (atoi (argv[2])); // angle Net_WriteWord ((argv.argc() > 3) ? atoi(argv[3]) : 0); // TID Net_WriteByte ((argv.argc() > 4) ? atoi(argv[4]) : 0); // special for (int i = 5; i < 10; i++) { // args[5] Net_WriteLong((i < argv.argc()) ? atoi(argv[i]) : 0); } } } }
void D_SendServerFlagChange (const FBaseCVar *cvar, int bitnum, bool set) { int namelen; namelen = (int)strlen (cvar->GetName ()); Net_WriteByte (DEM_SINFCHANGEDXOR); Net_WriteByte ((BYTE)namelen); Net_WriteBytes ((BYTE *)cvar->GetName (), namelen); Net_WriteByte (BYTE(bitnum | (set << 5))); }
/* * @brief Called when the player is totally leaving the server, either willingly * or unwillingly. This is NOT called if the entire server is quitting * or crashing. */ void Sv_DropClient(sv_client_t *cl) { g_entity_t *ent; Mem_ClearBuffer(&cl->net_chan.message); Mem_ClearBuffer(&cl->datagram.buffer); if (cl->datagram.messages) { g_list_free_full(cl->datagram.messages, Mem_Free); } if (cl->state > SV_CLIENT_FREE) { // send the disconnect if (cl->state == SV_CLIENT_ACTIVE) { // after informing the game module svs.game->ClientDisconnect(cl->entity); } Net_WriteByte(&cl->net_chan.message, SV_CMD_DISCONNECT); Netchan_Transmit(&cl->net_chan, cl->net_chan.message.data, cl->net_chan.message.size); } if (cl->download.buffer) { Fs_Free(cl->download.buffer); } ent = cl->entity; memset(cl, 0, sizeof(*cl)); cl->entity = ent; cl->last_frame = -1; }
void D_UserInfoChanged (FBaseCVar *cvar) { UCVarValue val; FString escaped_val; char foo[256]; if (cvar == &autoaim) { if (autoaim < 0.0f) { autoaim = 0.0f; return; } else if (autoaim > 5000.0f) { autoaim = 5000.f; return; } } val = cvar->GetGenericRep (CVAR_String); escaped_val = D_EscapeUserInfo(val.String); if (4 + strlen(cvar->GetName()) + escaped_val.Len() > 256) I_Error ("User info descriptor too big"); mysnprintf (foo, countof(foo), "\\%s\\%s", cvar->GetName(), escaped_val.GetChars()); Net_WriteByte (DEM_UINFCHANGED); Net_WriteString (foo); }
bool DIntermissionController::Responder (event_t *ev) { if (mScreen != NULL) { if (!mScreen->mPaletteChanged && ev->type == EV_KeyDown) { const char *cmd = Bindings.GetBind (ev->data1); if (cmd != NULL && (!stricmp(cmd, "toggleconsole") || !stricmp(cmd, "screenshot"))) { return false; } } if (mScreen->mTicker < 2) return false; // prevent some leftover events from auto-advancing int res = mScreen->Responder(ev); if (res == -1 && !mSentAdvance) { Net_WriteByte(DEM_ADVANCEINTER); mSentAdvance = true; } return !!res; } return false; }
/** * @brief Sends the user info string to the server over the reliable channel. */ static void Cl_WriteUserInfoCommand(void) { if (cvar_user_info_modified) { cvar_user_info_modified = false; Net_WriteByte(&cls.net_chan.message, CL_CMD_USER_INFO); Net_WriteString(&cls.net_chan.message, Cvar_UserInfo()); } }
/* * @brief A download message has been received from the server. */ static void Cl_ParseDownload(void) { int32_t size, percent; // read the data size = Net_ReadShort(&net_message); percent = Net_ReadByte(&net_message); if (size < 0) { Com_Debug("Server does not have this file\n"); if (cls.download.file) { // if here, we tried to resume a file but the server said no Fs_Close(cls.download.file); cls.download.file = NULL; } Cl_RequestNextDownload(); return; } // open the file if not opened yet if (!cls.download.file) { if (!(cls.download.file = Fs_OpenWrite(cls.download.tempname))) { net_message.read += size; Com_Warn("Failed to open %s\n", cls.download.tempname); Cl_RequestNextDownload(); return; } } Fs_Write(cls.download.file, net_message.data + net_message.read, 1, size); net_message.read += size; if (percent != 100) { Net_WriteByte(&cls.net_chan.message, CL_CMD_STRING); Net_WriteString(&cls.net_chan.message, "nextdl"); } else { Fs_Close(cls.download.file); cls.download.file = NULL; // add new archives to the search path if (Fs_Rename(cls.download.tempname, cls.download.name)) { if (strstr(cls.download.name, ".zip")) { Fs_AddToSearchPath(cls.download.name); } } else { Com_Error(ERR_DROP, "Failed to rename %s\n", cls.download.name); } // get another file if needed Cl_RequestNextDownload(); } }
//***************************************************************************** // void chat_SendMessage( ULONG ulMode, const char *pszString ) { FString ChatMessage = pszString; // [CW] Substitute the message if necessary. chat_DoSubstitution( ChatMessage ); // If we're the client, let the server handle formatting/sending the msg to other players. if ( NETWORK_GetState( ) == NETSTATE_CLIENT ) { CLIENTCOMMANDS_Say( ulMode, ChatMessage.GetChars( )); return; } if ( demorecording ) { Net_WriteByte( DEM_SAY ); Net_WriteByte( ulMode ); Net_WriteString( ChatMessage.GetChars( )); } else CHAT_PrintChatString( consoleplayer, ulMode, ChatMessage.GetChars( )); }
/** * @brief */ void Sv_WriteClientFrame(sv_client_t *client, mem_buf_t *msg) { sv_frame_t *frame, *delta_frame; int32_t delta_frame_num; // this is the frame we are creating frame = &client->frames[sv.frame_num & PACKET_MASK]; if (client->last_frame < 0) { // client is asking for a retransmit delta_frame = NULL; delta_frame_num = -1; } else if (sv.frame_num - client->last_frame >= (PACKET_BACKUP - 3)) { // client hasn't gotten a good message through in a long time delta_frame = NULL; delta_frame_num = -1; } else { // we have a valid message to delta from delta_frame = &client->frames[client->last_frame & PACKET_MASK]; delta_frame_num = client->last_frame; } Net_WriteByte(msg, SV_CMD_FRAME); Net_WriteLong(msg, sv.frame_num); Net_WriteLong(msg, delta_frame_num); // what we are delta'ing from Net_WriteByte(msg, client->suppress_count); // rate dropped packets client->suppress_count = 0; // send over the area bits Net_WriteByte(msg, frame->area_bytes); Net_WriteData(msg, frame->area_bits, frame->area_bytes); // delta encode the player state Sv_WritePlayerState(delta_frame, frame, msg); // delta encode the entities Sv_WriteEntities(delta_frame, frame, msg); }
/* * @brief Client implementation of Cmd_ForwardToServer. Any commands not recognized * locally by the client will be sent to the server. Some will undergo parameter * expansion so that players can use macros for locations, weapons, etc. */ static void Cl_ForwardCmdToServer(void) { if (cls.state <= CL_DISCONNECTED) { Com_Print("%s: Not connected\n", Cmd_Argv(0)); return; } const char *cmd = Cmd_Argv(0); const char *args = Cmd_Args(); Net_WriteByte(&cls.net_chan.message, CL_CMD_STRING); Net_WriteString(&cls.net_chan.message, va("%s %s", cmd, args)); //Com_Debug("Forwarding '%s %s'\n", cmd, args); }
static void ShoveChatStr (const char *str, BYTE who) { FString substBuff; if (str[0] == '/' && (str[1] == 'm' || str[1] == 'M') && (str[2] == 'e' || str[2] == 'E')) { // This is a /me message str += 3; who |= 2; } Net_WriteByte (DEM_SAY); Net_WriteByte (who); if (!chat_substitution || !DoSubstitution (substBuff, str)) { Net_WriteString (str); } else { Net_WriteString (substBuff); } }
/** * @brief Writes the most recent movement command(s) using delta-compression if available. */ static void Cl_WriteMovementCommand(mem_buf_t *buf) { static cl_cmd_t null_cmd; Net_WriteByte(buf, CL_CMD_MOVE); if (!cl.frame.valid || (cls.demo_file && Fs_Tell(cls.demo_file) == 0)) { Net_WriteLong(buf, -1); } else { Net_WriteLong(buf, cl.frame.frame_num); } cl_cmd_t *from = &null_cmd, *to = &cl.cmds[(cls.net_chan.outgoing_sequence - 2) & CMD_MASK]; Net_WriteDeltaMoveCmd(buf, &from->cmd, &to->cmd); from = to; to = &cl.cmds[(cls.net_chan.outgoing_sequence - 1) & CMD_MASK]; Net_WriteDeltaMoveCmd(buf, &from->cmd, &to->cmd); from = to; to = &cl.cmds[(cls.net_chan.outgoing_sequence) & CMD_MASK]; Net_WriteDeltaMoveCmd(buf, &from->cmd, &to->cmd); }
void DAutosaver::Tick () { Net_WriteByte (DEM_CHECKAUTOSAVE); Destroy (); }
static bool Cht_Generic (cheatseq_t *cheat) { Net_WriteByte (DEM_GENERICCHEAT); Net_WriteByte (cheat->Args[0]); return true; }
/* * @brief Tries to send an unreliable message to a connection, and handles the * transmission / retransmission of the reliable messages. * * A 0 size will still generate a packet and deal with the reliable messages. */ void Netchan_Transmit(net_chan_t *chan, byte *data, size_t len) { mem_buf_t send; byte send_buffer[MAX_MSG_SIZE]; // check for message overflow if (chan->message.overflowed) { chan->fatal_error = true; Com_Print("%s:Outgoing message overflow\n", Net_NetaddrToString(&chan->remote_address)); return; } const _Bool send_reliable = Netchan_NeedReliable(chan); if (!chan->reliable_size && chan->message.size) { memcpy(chan->reliable_buffer, chan->message_buffer, chan->message.size); chan->reliable_size = chan->message.size; chan->message.size = 0; chan->reliable_sequence ^= 1; } // write the packet header Mem_InitBuffer(&send, send_buffer, sizeof(send_buffer)); const uint32_t w1 = (chan->outgoing_sequence & ~(1 << 31)) | (send_reliable << 31); const uint32_t w2 = (chan->incoming_sequence & ~(1 << 31)) | (chan->incoming_reliable_sequence << 31); chan->outgoing_sequence++; chan->last_sent = quake2world.time; Net_WriteLong(&send, w1); Net_WriteLong(&send, w2); // send the qport if we are a client if (chan->source == NS_UDP_CLIENT) Net_WriteByte(&send, chan->qport); // copy the reliable message to the packet first if (send_reliable) { Mem_WriteBuffer(&send, chan->reliable_buffer, chan->reliable_size); chan->last_reliable_sequence = chan->outgoing_sequence; } // add the unreliable part if space is available if (send.max_size - send.size >= len) Mem_WriteBuffer(&send, data, len); else Com_Warn("Netchan_Transmit: dumped unreliable\n"); // send the datagram Net_SendDatagram(chan->source, &chan->remote_address, send.data, send.size); if (net_showpackets->value) { if (send_reliable) Com_Print("Send %u bytes: s=%i reliable=%i ack=%i rack=%i\n", (uint32_t) send.size, chan->outgoing_sequence - 1, chan->reliable_sequence, chan->incoming_sequence, chan->incoming_reliable_sequence); else Com_Print("Send %u bytes : s=%i ack=%i rack=%i\n", (uint32_t) send.size, chan->outgoing_sequence - 1, chan->incoming_sequence, chan->incoming_reliable_sequence); } }
bool FCajunMaster::SpawnBot (const char *name, int color) { //COLORS static const char colors[11][17] = { "\\color\\40 cf 00", //0 = Green "\\color\\b0 b0 b0", //1 = Gray "\\color\\50 50 60", //2 = Indigo "\\color\\8f 00 00", //3 = Deep Red "\\color\\ff ff ff", //4 = White "\\color\\ff af 3f", //5 = Bright Brown "\\color\\bf 00 00", //6 = Red "\\color\\00 00 ff", //7 = Blue "\\color\\00 00 7f", //8 = Dark Blue "\\color\\ff ff 00", //9 = Yellow "\\color\\cf df 90" //10 = Bleached Bone }; botinfo_t *thebot = botinfo; int botshift = 0; if (name) { // Check if exist or already in the game. while (thebot && stricmp (name, thebot->name)) { botshift++; thebot = thebot->next; } if (thebot == NULL) { Printf ("couldn't find %s in %s\n", name, BOTFILENAME); return false; } else if (thebot->inuse == BOTINUSE_Waiting) { return false; } else if (thebot->inuse == BOTINUSE_Yes) { Printf ("%s is already in the thick\n", name); return false; } } else { //Spawn a random bot from bots.cfg if no name given. TArray<botinfo_t *> BotInfoAvailable; while (thebot) { if (thebot->inuse == BOTINUSE_No) BotInfoAvailable.Push (thebot); thebot = thebot->next; } if (BotInfoAvailable.Size () == 0) { Printf ("Couldn't spawn bot; no bot left in %s\n", BOTFILENAME); return false; } thebot = BotInfoAvailable[pr_botspawn() % BotInfoAvailable.Size ()]; botinfo_t *thebot2 = botinfo; while (thebot2) { if (thebot == thebot2) break; botshift++; thebot2 = thebot2->next; } } thebot->inuse = BOTINUSE_Waiting; Net_WriteByte (DEM_ADDBOT); Net_WriteByte (botshift); { //Set color. char concat[512]; strcpy (concat, thebot->info); if (color == NOCOLOR && bot_next_color < NOCOLOR && bot_next_color >= 0) { strcat (concat, colors[bot_next_color]); } if (TeamLibrary.IsValidTeam (thebot->lastteam)) { // Keep the bot on the same team when switching levels mysnprintf (concat + strlen(concat), countof(concat) - strlen(concat), "\\team\\%d\n", thebot->lastteam); } Net_WriteString (concat); } Net_WriteByte(thebot->skill.aiming); Net_WriteByte(thebot->skill.perfection); Net_WriteByte(thebot->skill.reaction); Net_WriteByte(thebot->skill.isp); return true; }
bool FCajunMaster::SpawnBot (const char *name, int color) { int playernumber; //COLORS static const char colors[11][17] = { "\\color\\40 cf 00", //0 = Green "\\color\\b0 b0 b0", //1 = Gray "\\color\\50 50 60", //2 = Indigo "\\color\\8f 00 00", //3 = Deep Red "\\color\\ff ff ff", //4 = White "\\color\\ff af 3f", //5 = Bright Brown "\\color\\bf 00 00", //6 = Red "\\color\\00 00 ff", //7 = Blue "\\color\\00 00 7f", //8 = Dark Blue "\\color\\ff ff 00", //9 = Yellow "\\color\\cf df 90" //10 = Bleached Bone }; for (playernumber = 0; playernumber < MAXPLAYERS; playernumber++) { if (!playeringame[playernumber] && !waitingforspawn[playernumber]) { break; } } if (playernumber == MAXPLAYERS) { Printf ("The maximum of %d players/bots has been reached\n", MAXPLAYERS); return false; } botinfo_t *thebot; if (name) { thebot = botinfo; // Check if exist or already in the game. while (thebot && stricmp (name, thebot->name)) thebot = thebot->next; if (thebot == NULL) { Printf ("couldn't find %s in %s\n", name, BOTFILENAME); return false; } else if (thebot->inuse) { Printf ("%s is already in the thick\n", name); return false; } } else if (botnum < loaded_bots) { bool vacant = false; //Spawn a random bot from bots.cfg if no name given. while (!vacant) { int rnum = (pr_botspawn() % loaded_bots); thebot = botinfo; while (rnum) --rnum, thebot = thebot->next; if (!thebot->inuse) vacant = true; } } else { Printf ("Couldn't spawn bot; no bot left in %s\n", BOTFILENAME); return false; } waitingforspawn[playernumber] = true; Net_WriteByte (DEM_ADDBOT); Net_WriteByte (playernumber); { //Set color. char concat[512]; strcpy (concat, thebot->info); if (color == NOCOLOR && bot_next_color < NOCOLOR && bot_next_color >= 0) { strcat (concat, colors[bot_next_color]); } if (TeamLibrary.IsValidTeam (thebot->lastteam)) { // Keep the bot on the same team when switching levels mysnprintf (concat + strlen(concat), countof(concat) - strlen(concat), "\\team\\%d\n", thebot->lastteam); } Net_WriteString (concat); } players[playernumber].skill = thebot->skill; thebot->inuse = true; //Increment this. botnum++; return true; }
/** * @brief Writes server_data, config_strings, and baselines once a non-delta * compressed frame arrives from the server. */ static void Cl_WriteDemoHeader(void) { static entity_state_t null_state; mem_buf_t msg; byte buffer[MAX_MSG_SIZE]; // write out messages to hold the startup information Mem_InitBuffer(&msg, buffer, sizeof(buffer)); // write the server data Net_WriteByte(&msg, SV_CMD_SERVER_DATA); Net_WriteShort(&msg, PROTOCOL_MAJOR); Net_WriteShort(&msg, cls.cgame->protocol); Net_WriteByte(&msg, 1); // demo_server byte Net_WriteString(&msg, Cvar_GetString("game")); Net_WriteShort(&msg, cl.client_num); Net_WriteString(&msg, cl.config_strings[CS_NAME]); // and config_strings for (int32_t i = 0; i < MAX_CONFIG_STRINGS; i++) { if (*cl.config_strings[i] != '\0') { if (msg.size + strlen(cl.config_strings[i]) + 32 > msg.max_size) { // write it out const int32_t len = LittleLong((int32_t) msg.size); Fs_Write(cls.demo_file, &len, sizeof(len), 1); Fs_Write(cls.demo_file, msg.data, msg.size, 1); msg.size = 0; } Net_WriteByte(&msg, SV_CMD_CONFIG_STRING); Net_WriteShort(&msg, i); Net_WriteString(&msg, cl.config_strings[i]); } } // and baselines for (size_t i = 0; i < lengthof(cl.entities); i++) { entity_state_t *ent = &cl.entities[i].baseline; if (!ent->number) { continue; } if (msg.size + 64 > msg.max_size) { // write it out const int32_t len = LittleLong((int32_t) msg.size); Fs_Write(cls.demo_file, &len, sizeof(len), 1); Fs_Write(cls.demo_file, msg.data, msg.size, 1); msg.size = 0; } Net_WriteByte(&msg, SV_CMD_BASELINE); Net_WriteDeltaEntity(&msg, &null_state, &cl.entities[i].baseline, true); } Net_WriteByte(&msg, SV_CMD_CBUF_TEXT); Net_WriteString(&msg, "precache 0\n"); // write it to the demo file const int32_t len = LittleLong((int32_t) msg.size); Fs_Write(cls.demo_file, &len, sizeof(len), 1); Fs_Write(cls.demo_file, msg.data, msg.size, 1); Com_Debug(DEBUG_CLIENT, "Demo started\n"); // the rest of the demo file will be individual frames }
/* * @brief Returns true if the file exists, otherwise it attempts to start a download * from the server. */ _Bool Cl_CheckOrDownloadFile(const char *filename) { char cmd[MAX_STRING_CHARS]; if (cls.state == CL_DISCONNECTED) { Com_Print("Not connected\n"); return true; } if (IS_INVALID_DOWNLOAD(filename)) { Com_Warn("Refusing to download \"%s\"\n", filename); return true; } Com_Debug("Checking for %s\n", filename); if (Fs_Exists(filename)) { // it exists, no need to download return true; } Com_Debug("Attempting to download %s\n", filename); strncpy(cls.download.name, filename, sizeof(cls.download.name)); // udp downloads to a temp name, and only renames when done StripExtension(cls.download.name, cls.download.tempname); g_strlcat(cls.download.tempname, ".tmp", sizeof(cls.download.tempname)); // attempt an http download if available if (cls.download_url[0] && Cl_HttpDownload()) return false; // check to see if we already have a tmp for this file, if so, try to resume // open the file if not opened yet if (Fs_Exists(cls.download.tempname)) { // a temp file exists, resume download int64_t len = Fs_Load(cls.download.tempname, NULL); if ((cls.download.file = Fs_OpenAppend(cls.download.tempname))) { if (Fs_Seek(cls.download.file, len - 1)) { // give the server the offset to start the download Com_Debug("Resuming %s...\n", cls.download.name); g_snprintf(cmd, sizeof(cmd), "download %s %u", cls.download.name, (uint32_t) len); Net_WriteByte(&cls.net_chan.message, CL_CMD_STRING); Net_WriteString(&cls.net_chan.message, cmd); return false; } } } // or start if from the beginning Com_Debug("Downloading %s...\n", cls.download.name); g_snprintf(cmd, sizeof(cmd), "download %s", cls.download.name); Net_WriteByte(&cls.net_chan.message, CL_CMD_STRING); Net_WriteString(&cls.net_chan.message, cmd); return false; }