OpenTTD Source 20260206-master-g4d4e37dbf1
network_content.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD 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, version 2.
4 * OpenTTD 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.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "../stdafx.h"
11#include "../rev.h"
12#include "../ai/ai.hpp"
13#include "../game/game.hpp"
14#include "../window_func.h"
15#include "../error.h"
16#include "../fileio_func.h"
17#include "../base_media_base.h"
19#include "../base_media_music.h"
21#include "../settings_type.h"
22#include "../strings_func.h"
23#include "../timer/timer.h"
26#include "network_content.h"
27
28#include "table/strings.h"
29
30#if defined(WITH_ZLIB)
31# include <zlib.h>
32# if defined(_WIN32)
33 /* Required for: dup, fileno, close */
34# include <io.h>
35# endif
36#endif
37
38#ifdef __EMSCRIPTEN__
39# include <emscripten.h>
40#endif
41
42#include "../safeguards.h"
43
44extern bool HasScenario(const ContentInfo &ci, bool md5sum);
45
48
50static bool HasGRFConfig(const ContentInfo &ci, bool md5sum)
51{
52 return FindGRFConfig(std::byteswap(ci.unique_id), md5sum ? FGCM_EXACT : FGCM_ANY, md5sum ? &ci.md5sum : nullptr) != nullptr;
53}
54
62using HasContentProc = bool(const ContentInfo &ci, bool md5sum);
63
70{
71 switch (type) {
76 case CONTENT_TYPE_AI: return AI::HasAI;
77 case CONTENT_TYPE_AI_LIBRARY: return AI::HasAILibrary;
79 case CONTENT_TYPE_GAME_LIBRARY: return Game::HasGameLibrary;
82 default: return nullptr;
83 }
84}
85
87{
88 auto ci = std::make_unique<ContentInfo>();
89 ci->type = (ContentType)p.Recv_uint8();
90 ci->id = (ContentID)p.Recv_uint32();
91 ci->filesize = p.Recv_uint32();
92
97
98 ci->unique_id = p.Recv_uint32();
99 p.Recv_bytes(ci->md5sum);
100
101 uint dependency_count = p.Recv_uint8();
102 ci->dependencies.reserve(dependency_count);
103 for (uint i = 0; i < dependency_count; i++) {
104 ContentID dependency_cid = (ContentID)p.Recv_uint32();
105 ci->dependencies.push_back(dependency_cid);
106 this->reverse_dependency_map.emplace(dependency_cid, ci->id);
107 }
108
109 uint tag_count = p.Recv_uint8();
110 ci->tags.reserve(tag_count);
111 for (uint i = 0; i < tag_count; i++) ci->tags.push_back(p.Recv_string(NETWORK_CONTENT_TAG_LENGTH));
112
113 if (!ci->IsValid()) {
114 this->CloseConnection();
115 return false;
116 }
117
119 if (proc != nullptr) {
120 if (proc(*ci, true)) {
122 } else {
124 if (proc(*ci, false)) ci->upgrade = true;
125 }
126 } else {
128 }
129
130 /* Something we don't have and has filesize 0 does not exist in the system */
131 if (ci->state == ContentInfo::State::Unselected && ci->filesize == 0) ci->state = ContentInfo::State::DoesNotExist;
132
133 /* Do we already have a stub for this? */
134 for (const auto &ici : this->infos) {
135 if (ici->type == ci->type && ici->unique_id == ci->unique_id && ci->md5sum == ici->md5sum) {
136 /* Preserve the name if possible */
137 if (ci->name.empty()) ci->name = ici->name;
138 if (ici->IsSelected()) ci->state = ici->state;
139
140 /*
141 * As ici might be selected by the content window we cannot delete that.
142 * However, we want to keep most of the values of ci, except the values
143 * we (just) already preserved.
144 */
145 *ici = *ci;
146
147 this->OnReceiveContentInfo(*ici);
148 return true;
149 }
150 }
151
152 /* Missing content info? Don't list it */
153 if (ci->filesize == 0) {
154 return true;
155 }
156
157 ContentInfo *info = this->infos.emplace_back(std::move(ci)).get();
158
159 /* Incoming data means that we might need to reconsider dependencies */
160 ConstContentVector parents;
161 this->ReverseLookupTreeDependency(parents, info);
162 for (const ContentInfo *ici : parents) {
163 this->CheckDependencyState(*ici);
164 }
165
166 this->OnReceiveContentInfo(*info);
167
168 return true;
169}
170
176{
177 if (type == CONTENT_TYPE_END) {
188 return;
189 }
190
191 this->Connect();
192
193 auto p = std::make_unique<Packet>(this, PACKET_CONTENT_CLIENT_INFO_LIST);
194 p->Send_uint8 ((uint8_t)type);
195 p->Send_uint32(0xffffffff);
196 p->Send_uint8 (1);
197 p->Send_string("vanilla");
198 p->Send_string(_openttd_content_version);
199
200 /* Patchpacks can extend the list with one. In BaNaNaS metadata you can
201 * add a branch in the 'compatibility' list, to filter on this. If you want
202 * your patchpack to be mentioned in the BaNaNaS web-interface, create an
203 * issue on https://github.com/OpenTTD/bananas-api asking for this.
204
205 p->Send_string("patchpack"); // Or what-ever the name of your patchpack is.
206 p->Send_string(_openttd_content_version_patchpack);
207
208 */
209
210 this->SendPacket(std::move(p));
211}
212
217void ClientNetworkContentSocketHandler::RequestContentList(std::span<const ContentID> content_ids)
218{
219 /* We can "only" send a limited number of IDs in a single packet.
220 * A packet begins with the packet size and a byte for the type.
221 * Then this packet adds a uint16_t for the count in this packet.
222 * The rest of the packet can be used for the IDs. */
223 static constexpr size_t MAX_CONTENT_IDS_PER_PACKET = (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint16_t)) / sizeof(uint32_t);
224
225 if (content_ids.empty()) return;
226
227 this->Connect();
228
229 for (auto it = std::begin(content_ids); it != std::end(content_ids); /* nothing */) {
230 auto last = std::ranges::next(it, MAX_CONTENT_IDS_PER_PACKET, std::end(content_ids));
231
232 auto p = std::make_unique<Packet>(this, PACKET_CONTENT_CLIENT_INFO_ID, TCP_MTU);
233 p->Send_uint16(std::distance(it, last));
234
235 for (; it != last; ++it) {
236 p->Send_uint32(*it);
237 }
238
239 this->SendPacket(std::move(p));
240 }
241}
242
249{
250 if (cv == nullptr) return;
251
252 this->Connect();
253
254 assert(cv->size() < 255);
255 assert(cv->size() < (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint8_t)) /
256 (sizeof(uint8_t) + sizeof(uint32_t) + (send_md5sum ? MD5_HASH_BYTES : 0)));
257
258 auto p = std::make_unique<Packet>(this, send_md5sum ? PACKET_CONTENT_CLIENT_INFO_EXTID_MD5 : PACKET_CONTENT_CLIENT_INFO_EXTID, TCP_MTU);
259 p->Send_uint8((uint8_t)cv->size());
260
261 for (const auto &ci : *cv) {
262 p->Send_uint8((uint8_t)ci->type);
263 p->Send_uint32(ci->unique_id);
264 if (!send_md5sum) continue;
265 p->Send_bytes(ci->md5sum);
266 }
267
268 this->SendPacket(std::move(p));
269
270 for (auto &ci : *cv) {
271 bool found = false;
272 for (const auto &ci2 : this->infos) {
273 if (ci->type == ci2->type && ci->unique_id == ci2->unique_id &&
274 (!send_md5sum || ci->md5sum == ci2->md5sum)) {
275 found = true;
276 break;
277 }
278 }
279 if (!found) {
280 this->infos.push_back(std::move(ci));
281 }
282 }
283}
284
291void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes, bool fallback)
292{
293 bytes = 0;
294
295 ContentIDList content;
296 for (const auto &ci : this->infos) {
297 if (!ci->IsSelected() || ci->state == ContentInfo::State::AlreadyHere) continue;
298
299 content.push_back(ci->id);
300 bytes += ci->filesize;
301 }
302
303 files = (uint)content.size();
304
305 /* If there's nothing to download, do nothing. */
306 if (files == 0) return;
307
308 this->is_cancelled = false;
309
310 if (fallback) {
311 this->DownloadSelectedContentFallback(content);
312 } else {
313 this->DownloadSelectedContentHTTP(content);
314 }
315}
316
322{
323 std::string content_request;
324 for (const ContentID &id : content) {
325 format_append(content_request, "{}\n", id);
326 }
327
328 this->http_response_index = -1;
329
330 NetworkHTTPSocketHandler::Connect(NetworkContentMirrorUriString(), this, std::move(content_request));
331}
332
338{
339 uint count = (uint)content.size();
340 const ContentID *content_ids = content.data();
341 this->Connect();
342
343 while (count > 0) {
344 /* We can "only" send a limited number of IDs in a single packet.
345 * A packet begins with the packet size and a byte for the type.
346 * Then this packet adds a uint16_t for the count in this packet.
347 * The rest of the packet can be used for the IDs. */
348 uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint16_t)) / sizeof(uint32_t));
349
350 auto p = std::make_unique<Packet>(this, PACKET_CONTENT_CLIENT_CONTENT, TCP_MTU);
351 p->Send_uint16(p_count);
352
353 for (uint i = 0; i < p_count; i++) {
354 p->Send_uint32(content_ids[i]);
355 }
356
357 this->SendPacket(std::move(p));
358 count -= p_count;
359 content_ids += p_count;
360 }
361}
362
370static std::string GetFullFilename(const ContentInfo &ci, bool compressed)
371{
373 if (dir == NO_DIRECTORY) return {};
374
375 std::string buf = FioGetDirectory(SP_AUTODOWNLOAD_DIR, dir);
376 buf += ci.filename;
377 buf += compressed ? ".tar.gz" : ".tar";
378
379 return buf;
380}
381
387static bool GunzipFile(const ContentInfo &ci)
388{
389#if defined(WITH_ZLIB)
390 bool ret = true;
391
392 /* Need to open the file with fopen() to support non-ASCII on Windows. */
393 auto ftmp = FileHandle::Open(GetFullFilename(ci, true), "rb");
394 if (!ftmp.has_value()) return false;
395 /* Duplicate the handle, and close the FILE*, to avoid double-closing the handle later. */
396 int fdup = dup(fileno(*ftmp));
397 gzFile fin = gzdopen(fdup, "rb");
398 ftmp.reset();
399
400 auto fout = FileHandle::Open(GetFullFilename(ci, false), "wb");
401
402 if (fin == nullptr || !fout.has_value()) {
403 ret = false;
404 } else {
405 uint8_t buff[8192];
406 for (;;) {
407 int read = gzread(fin, buff, sizeof(buff));
408 if (read == 0) {
409 /* If gzread() returns 0, either the end-of-file has been
410 * reached or an underlying read error has occurred.
411 *
412 * gzeof() can't be used, because:
413 * 1.2.5 - it is safe, 1 means 'everything was OK'
414 * 1.2.3.5, 1.2.4 - 0 or 1 is returned 'randomly'
415 * 1.2.3.3 - 1 is returned for truncated archive
416 *
417 * So we use gzerror(). When proper end of archive
418 * has been reached, then:
419 * errnum == Z_STREAM_END in 1.2.3.3,
420 * errnum == 0 in 1.2.4 and 1.2.5 */
421 int errnum;
422 gzerror(fin, &errnum);
423 if (errnum != 0 && errnum != Z_STREAM_END) ret = false;
424 break;
425 }
426 if (read < 0 || static_cast<size_t>(read) != fwrite(buff, 1, read, *fout)) {
427 /* If gzread() returns -1, there was an error in archive */
428 ret = false;
429 break;
430 }
431 /* DO NOT DO THIS! It will fail to detect broken archive with 1.2.3.3!
432 * if (read < sizeof(buff)) break; */
433 }
434 }
435
436 if (fin != nullptr) {
437 gzclose(fin);
438 } else if (fdup != -1) {
439 /* Failing gzdopen does not close the passed file descriptor. */
440 close(fdup);
441 }
442
443 return ret;
444#else
445 NOT_REACHED();
446#endif /* defined(WITH_ZLIB) */
447}
448
450{
451 if (!this->cur_file.has_value()) {
452 /* When we haven't opened a file this must be our first packet with metadata. */
453 this->cur_info = std::make_unique<ContentInfo>();
454 this->cur_info->type = (ContentType)p.Recv_uint8();
455 this->cur_info->id = (ContentID)p.Recv_uint32();
456 this->cur_info->filesize = p.Recv_uint32();
458
459 if (!this->BeforeDownload()) {
460 this->CloseConnection();
461 return false;
462 }
463 } else {
464 /* We have a file opened, thus are downloading internal content */
465 ssize_t to_read = p.RemainingBytesToTransfer();
466 auto write_to_disk = [this](std::span<const uint8_t> buffer) {
467 return fwrite(buffer.data(), 1, buffer.size(), *this->cur_file);
468 };
469 if (to_read != 0 && p.TransferOut(write_to_disk) != to_read) {
472 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD),
473 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE),
474 WL_ERROR);
475 this->CloseConnection();
476 this->cur_file.reset();
477
478 return false;
479 }
480
481 this->OnDownloadProgress(*this->cur_info, (int)to_read);
482
483 if (to_read == 0) this->AfterDownload();
484 }
485
486 return true;
487}
488
494{
495 if (!this->cur_info->IsValid()) {
496 this->cur_info.reset();
497 return false;
498 }
499
500 if (this->cur_info->filesize != 0) {
501 /* The filesize is > 0, so we are going to download it */
502 std::string filename = GetFullFilename(*this->cur_info, true);
503 if (filename.empty() || !(this->cur_file = FileHandle::Open(filename, "wb")).has_value()) {
504 /* Unless that fails of course... */
507 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD),
508 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE),
509 WL_ERROR);
510 return false;
511 }
512 }
513 return true;
514}
515
521{
522 /* We read nothing; that's our marker for end-of-stream.
523 * Now gunzip the tar and make it known. */
524 this->cur_file.reset();
525
526 if (GunzipFile(*this->cur_info)) {
527 FioRemove(GetFullFilename(*this->cur_info, true));
528
530 if (sd == NO_DIRECTORY) NOT_REACHED();
531
532 TarScanner ts;
533 std::string fname = GetFullFilename(*this->cur_info, false);
534 ts.AddFile(sd, fname);
535
536 if (this->cur_info->type == CONTENT_TYPE_BASE_MUSIC) {
537 /* Music can't be in a tar. So extract the tar! */
538 ExtractTar(fname, BASESET_DIR);
539 FioRemove(fname);
540 }
541
542#ifdef __EMSCRIPTEN__
543 EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
544#endif
545
546 this->OnDownloadComplete(this->cur_info->id);
547 } else {
548 ShowErrorMessage(GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_EXTRACT), {}, WL_ERROR);
549 }
550}
551
553{
554 return this->is_cancelled;
555}
556
557/* Also called to just clean up the mess. */
559{
560 this->http_response.clear();
561 this->http_response.shrink_to_fit();
562 this->http_response_index = -2;
563
564 if (this->cur_file.has_value()) {
565 this->OnDownloadProgress(*this->cur_info, -1);
566
567 this->cur_file.reset();
568 }
569
570 /* If we fail, download the rest via the 'old' system. */
571 if (!this->is_cancelled) {
572 uint files, bytes;
573
574 this->DownloadSelectedContent(files, bytes, true);
575 }
576}
577
578void ClientNetworkContentSocketHandler::OnReceiveData(std::unique_ptr<char[]> data, size_t length)
579{
580 assert(data.get() == nullptr || length != 0);
581
582 /* Ignore any latent data coming from a connection we closed. */
583 if (this->http_response_index == -2) {
584 return;
585 }
586
587 if (this->http_response_index == -1) {
588 if (data != nullptr) {
589 /* Append the rest of the response. */
590 this->http_response.insert(this->http_response.end(), data.get(), data.get() + length);
591 return;
592 } else {
593 /* Make sure the response is properly terminated. */
594 this->http_response.push_back('\0');
595
596 /* And prepare for receiving the rest of the data. */
597 this->http_response_index = 0;
598 }
599 }
600
601 if (data != nullptr) {
602 /* We have data, so write it to the file. */
603 if (fwrite(data.get(), 1, length, *this->cur_file) != length) {
604 /* Writing failed somehow, let try via the old method. */
605 this->OnFailure();
606 } else {
607 /* Just received the data. */
608 this->OnDownloadProgress(*this->cur_info, (int)length);
609 }
610
611 /* Nothing more to do now. */
612 return;
613 }
614
615 if (this->cur_file.has_value()) {
616 /* We've finished downloading a file. */
617 this->AfterDownload();
618 }
619
620 if ((uint)this->http_response_index >= this->http_response.size()) {
621 /* It's not a real failure, but if there's
622 * nothing more to download it helps with
623 * cleaning up the stuff we allocated. */
624 this->OnFailure();
625 return;
626 }
627
628 /* When we haven't opened a file this must be our first packet with metadata. */
629 this->cur_info = std::make_unique<ContentInfo>();
630
631 try {
632 for (;;) {
633 std::string_view buffer{this->http_response.data(), this->http_response.size()};
634 buffer.remove_prefix(this->http_response_index);
635 auto len = buffer.find('\n');
636 if (len == std::string_view::npos) throw std::exception{};
637 /* Update the index for the next one */
638 this->http_response_index += static_cast<int>(len + 1);
639
640 StringConsumer consumer{buffer.substr(0, len)};
641
642 /* Read the ID */
643 this->cur_info->id = static_cast<ContentID>(consumer.ReadIntegerBase<uint>(10));
644 if (!consumer.ReadIf(",")) throw std::exception{};
645
646 /* Read the type */
647 this->cur_info->type = static_cast<ContentType>(consumer.ReadIntegerBase<uint>(10));
648 if (!consumer.ReadIf(",")) throw std::exception{};
649
650 /* Read the file size */
651 this->cur_info->filesize = consumer.ReadIntegerBase<uint32_t>(10);
652 if (!consumer.ReadIf(",")) throw std::exception{};
653
654 /* Read the URL */
655 auto url = consumer.GetLeftData();
656
657 /* Is it a fallback URL? If so, just continue with the next one. */
658 if (consumer.ReadIf("ottd")) {
659 /* Have we gone through all lines? */
660 if (static_cast<size_t>(this->http_response_index) >= this->http_response.size()) throw std::exception{};
661 continue;
662 }
663
665 std::string_view filename;
666 /* Skip all but the last part. There must be at least one / though */
667 do {
668 if (!consumer.ReadIf("/")) throw std::exception{};
669 filename = consumer.ReadUntilChar('/', StringConsumer::KEEP_SEPARATOR);
670 } while (consumer.AnyBytesLeft());
671
672 /* Remove the extension from the string. */
673 for (uint i = 0; i < 2; i++) {
674 auto pos = filename.find_last_of('.');
675 if (pos == std::string::npos) throw std::exception{};
676 filename = filename.substr(0, pos);
677 }
678
679 /* Copy the string, without extension, to the filename. */
680 this->cur_info->filename = filename;
681
682 /* Request the next file. */
683 if (!this->BeforeDownload()) throw std::exception{};
684
686 break;
687 }
688 } catch (const std::exception&) {
689 this->OnFailure();
690 }
691}
692
694class NetworkContentConnecter : public TCPConnecter {
695public:
701
702 void OnFailure() override
703 {
704 _network_content_client.is_connecting = false;
705 _network_content_client.OnConnect(false);
706 }
707
708 void OnConnect(SOCKET s) override
709 {
710 assert(_network_content_client.sock == INVALID_SOCKET);
711 _network_content_client.last_activity = std::chrono::steady_clock::now();
712 _network_content_client.is_connecting = false;
715 _network_content_client.OnConnect(true);
716 }
717};
718
723{
724 if (this->sock != INVALID_SOCKET || this->is_connecting) return;
725
726 this->is_cancelled = false;
727 this->is_connecting = true;
728
730}
731
746
751{
752 this->is_cancelled = true;
753 this->CloseConnection();
754}
755
761{
762 if (this->sock == INVALID_SOCKET || this->is_connecting) return;
763
764 /* Close the connection to the content server after inactivity; there can still be downloads pending via HTTP. */
765 if (std::chrono::steady_clock::now() > this->last_activity + IDLE_TIMEOUT) {
766 this->CloseConnection();
767 return;
768 }
769
770 if (this->CanSendReceive()) {
771 if (this->ReceivePackets()) {
772 /* Only update activity once a packet is received, instead of every time we try it. */
773 this->last_activity = std::chrono::steady_clock::now();
774 }
775 }
776
777 this->SendPackets();
778}
779
781static constexpr auto CONTENT_QUEUE_TIMEOUT = std::chrono::milliseconds(100);
782
783static TimeoutTimer<TimerWindow> _request_queue_timeout = {CONTENT_QUEUE_TIMEOUT, []() {
784 _network_content_client.RequestQueuedContentInfo();
785}};
786
792{
793 /* When we tried to download it already, don't try again */
794 if (std::ranges::find(this->requested, cid) != this->requested.end()) return;
795
796 this->requested.push_back(cid);
797 this->queued.push_back(cid);
798 _request_queue_timeout.Reset();
799}
800
805{
806 if (this->queued.empty()) return;
807
808 /* Wait until we've briefly stopped receiving data (which will contain more content) before making the request. */
809 if (std::chrono::steady_clock::now() <= this->last_activity + CONTENT_QUEUE_TIMEOUT) {
810 _request_queue_timeout.Reset();
811 return;
812 }
813
814 /* Move the queue locally so more ids can be queued for later. */
815 ContentIDList queue;
816 queue.swap(this->queued);
817
818 /* Remove ids that have since been received since the request was queued. */
819 queue.erase(std::remove_if(std::begin(queue), std::end(queue), [this](ContentID content_id) {
820 return std::ranges::find(this->infos, content_id, &ContentInfo::id) != std::end(this->infos);
821 }), std::end(queue));
822
823 this->RequestContentList(queue);
824}
825
832{
833 for (const auto &ci : this->infos) {
834 if (ci->id == cid) return ci.get();
835 }
836 return nullptr;
837}
838
839
845{
846 ContentInfo *ci = this->GetContent(cid);
847 if (ci == nullptr || ci->state != ContentInfo::State::Unselected) return;
848
850 this->CheckDependencyState(*ci);
851}
852
858{
859 ContentInfo *ci = this->GetContent(cid);
860 if (ci == nullptr || !ci->IsSelected()) return;
861
863 this->CheckDependencyState(*ci);
864}
865
868{
869 for (const auto &ci : this->infos) {
870 if (ci->state == ContentInfo::State::Unselected) {
872 this->CheckDependencyState(*ci);
873 }
874 }
875}
876
879{
880 for (const auto &ci : this->infos) {
881 if (ci->state == ContentInfo::State::Unselected && ci->upgrade) {
883 this->CheckDependencyState(*ci);
884 }
885 }
886}
887
890{
891 for (const auto &ci : this->infos) {
892 if (ci->IsSelected() && ci->state != ContentInfo::State::AlreadyHere) ci->state = ContentInfo::State::Unselected;
893 }
894}
895
898{
899 switch (ci.state) {
902 this->Unselect(ci.id);
903 break;
904
906 this->Select(ci.id);
907 break;
908
909 default:
910 break;
911 }
912}
913
920{
921 auto range = this->reverse_dependency_map.equal_range(child.id);
922
923 for (auto iter = range.first; iter != range.second; ++iter) {
924 parents.push_back(GetContent(iter->second));
925 }
926}
927
934{
935 tree.push_back(child);
936
937 /* First find all direct parents. We can't use the "normal" iterator as
938 * we are including stuff into the vector and as such the vector's data
939 * store can be reallocated (and thus move), which means our iterating
940 * pointer gets invalid. So fall back to the indices. */
941 for (size_t i = 0; i < tree.size(); i++) {
942 ConstContentVector parents;
943 this->ReverseLookupDependency(parents, *tree[i]);
944
945 for (const ContentInfo *ci : parents) {
946 include(tree, ci);
947 }
948 }
949}
950
956{
958 /* Selection is easy; just walk all children and set the
959 * autoselected state. That way we can see what we automatically
960 * selected and thus can unselect when a dependency is removed. */
961 for (auto &dependency : ci.dependencies) {
962 ContentInfo *c = this->GetContent(dependency);
963 if (c == nullptr) {
964 this->DownloadContentInfo(dependency);
965 } else if (c->state == ContentInfo::State::Unselected) {
967 this->CheckDependencyState(*c);
968 }
969 }
970 return;
971 }
972
973 if (ci.state != ContentInfo::State::Unselected) return;
974
975 /* For unselection we need to find the parents of us. We need to
976 * unselect them. After that we unselect all children that we
977 * depend on and are not used as dependency for us, but only when
978 * we automatically selected them. */
979 ConstContentVector parents;
980 this->ReverseLookupDependency(parents, ci);
981 for (const ContentInfo *c : parents) {
982 if (!c->IsSelected()) continue;
983
984 this->Unselect(c->id);
985 }
986
987 for (auto &dependency : ci.dependencies) {
988 const ContentInfo *c = this->GetContent(dependency);
989 if (c == nullptr) {
990 DownloadContentInfo(dependency);
991 continue;
992 }
993 if (c->state != ContentInfo::State::Autoselected) continue;
994
995 /* Only unselect when WE are the only parent. */
996 parents.clear();
997 this->ReverseLookupDependency(parents, *c);
998
999 /* First check whether anything depends on us */
1000 int sel_count = 0;
1001 bool force_selection = false;
1002 for (const ContentInfo *parent_ci : parents) {
1003 if (parent_ci->IsSelected()) sel_count++;
1004 if (parent_ci->state == ContentInfo::State::Selected) force_selection = true;
1005 }
1006 if (sel_count == 0) {
1007 /* Nothing depends on us */
1008 this->Unselect(c->id);
1009 continue;
1010 }
1011 /* Something manually selected depends directly on us */
1012 if (force_selection) continue;
1013
1014 /* "Flood" search to find all items in the dependency graph*/
1015 parents.clear();
1016 this->ReverseLookupTreeDependency(parents, c);
1017
1018 /* Is there anything that is "force" selected?, if so... we're done. */
1019 for (const ContentInfo *parent_ci : parents) {
1020 if (parent_ci->state != ContentInfo::State::Selected) continue;
1021
1022 force_selection = true;
1023 break;
1024 }
1025
1026 /* So something depended directly on us */
1027 if (force_selection) continue;
1028
1029 /* Nothing depends on us, mark the whole graph as unselected.
1030 * After that's done run over them once again to test their children
1031 * to unselect. Don't do it immediately because it'll do exactly what
1032 * we're doing now. */
1033 for (const ContentInfo *parent : parents) {
1034 if (parent->state == ContentInfo::State::Autoselected) this->Unselect(parent->id);
1035 }
1036 for (const ContentInfo *parent : parents) {
1037 this->CheckDependencyState(*this->GetContent(parent->id));
1038 }
1039 }
1040}
1041
1044{
1045 this->infos.clear();
1046 this->requested.clear();
1047 this->queued.clear();
1048 this->reverse_dependency_map.clear();
1049}
1050
1051/*** CALLBACK ***/
1052
1054{
1055 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1056 ContentCallback *cb = this->callbacks[i];
1057 /* the callback may remove itself from this->callbacks */
1058 cb->OnConnect(success);
1059 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1060 }
1061}
1062
1064{
1065 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1066 ContentCallback *cb = this->callbacks[i];
1067 cb->OnDisconnect();
1068 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1069 }
1070}
1071
1073{
1074 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1075 ContentCallback *cb = this->callbacks[i];
1076 /* the callback may add items and/or remove itself from this->callbacks */
1077 cb->OnReceiveContentInfo(ci);
1078 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1079 }
1080}
1081
1083{
1084 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1085 ContentCallback *cb = this->callbacks[i];
1086 cb->OnDownloadProgress(ci, bytes);
1087 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1088 }
1089}
1090
1092{
1093 ContentInfo *ci = this->GetContent(cid);
1094 if (ci != nullptr) {
1096 }
1097
1098 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1099 ContentCallback *cb = this->callbacks[i];
1100 /* the callback may remove itself from this->callbacks */
1101 cb->OnDownloadComplete(cid);
1102 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1103 }
1104}
Base functions for all AIs.
Generic functions for replacing base data (graphics, sounds).
Generic functions for replacing base graphics data.
Generic functions for replacing base music data.
Generic functions for replacing base sounds data.
constexpr enable_if_t< is_integral_v< T >, T > byteswap(T x) noexcept
Custom implementation of std::byteswap; remove once we build with C++23.
static bool HasAI(const ContentInfo &ci, bool md5sum)
Wrapper function for AIScanner::HasAI.
Definition ai_core.cpp:337
static bool HasSet(const ContentInfo &ci, bool md5sum)
Socket handler for the content server connection.
void SelectUpgrade()
Select everything that's an update for something we've got.
void OnConnect(bool success) override
Callback for when the connection has finished.
void DownloadSelectedContentHTTP(const ContentIDList &content)
Initiate downloading the content over HTTP.
void DownloadSelectedContent(uint &files, uint &bytes, bool fallback=false)
Actually begin downloading the content we selected.
void ToggleSelectedState(const ContentInfo &ci)
Toggle the state of a content info and check its dependencies.
static constexpr std::chrono::seconds IDLE_TIMEOUT
The idle timeout; when to close the connection because it's idle.
void OnReceiveData(std::unique_ptr< char[]> data, size_t length) override
We're receiving data.
void Select(ContentID cid)
Select a specific content id.
void OnReceiveContentInfo(const ContentInfo &ci) override
We received a content info.
int http_response_index
Where we are, in the response, with handling it.
NetworkRecvStatus CloseConnection(bool error=true) override
Disconnect from the content server.
ContentIDList requested
ContentIDs we already requested (so we don't do it again).
void UnselectAll()
Unselect everything that we've not downloaded so far.
void RequestContentList(ContentType type)
Request the content list for the given type.
bool is_connecting
Whether we're connecting.
std::vector< char > http_response
The HTTP response to the requests we've been doing.
void ReverseLookupTreeDependency(ConstContentVector &tree, const ContentInfo *child) const
Reverse lookup the dependencies of all parents over a given child.
ContentIDList queued
ContentID queue to be requested.
void OnDownloadProgress(const ContentInfo &ci, int bytes) override
We have progress in the download of a file.
void DownloadContentInfo(ContentID cid)
Download information of a given Content ID if not already tried.
bool Receive_SERVER_INFO(Packet &p) override
Server sending list of content info: uint8_t type (invalid ID == does not exist) uint32_t id uint32_t...
void SendReceive()
Check whether we received/can send some data from/to the content server and when that's the case hand...
void OnDownloadComplete(ContentID cid) override
We have finished downloading a file.
ContentVector infos
All content info we received.
void OnFailure() override
An error has occurred and the connection has been closed.
std::vector< ContentID > ContentIDList
List of content IDs to (possibly) select.
ContentInfo * GetContent(ContentID cid) const
Get the content info based on a ContentID.
bool Receive_SERVER_CONTENT(Packet &p) override
Server sending list of content info: uint32_t unique id uint32_t file size (0 == does not exist) stri...
void Connect()
Connect with the content server.
std::unordered_multimap< ContentID, ContentID > reverse_dependency_map
Content reverse dependency map.
bool BeforeDownload()
Handle the opening of the file before downloading.
void CheckDependencyState(const ContentInfo &ci)
Check the dependencies (recursively) of this content info.
void Clear()
Clear all downloaded content information.
void RequestQueuedContentInfo()
Send a content request for queued content info download.
std::optional< FileHandle > cur_file
Currently downloaded file.
std::chrono::steady_clock::time_point last_activity
The last time there was network activity.
void AfterDownload()
Handle the closing and extracting of a file after downloading it has been done.
void ReverseLookupDependency(ConstContentVector &parents, const ContentInfo &child) const
Reverse lookup the dependencies of (direct) parents over a given child.
bool IsCancelled() const override
Check if there is a request to cancel the transfer.
std::vector< ContentCallback * > callbacks
Callbacks to notify "the world".
void DownloadSelectedContentFallback(const ContentIDList &content)
Initiate downloading the content over the fallback protocol.
void Unselect(ContentID cid)
Unselect a specific content id.
void Cancel()
Cancel the current download.
void OnDisconnect() override
Callback for when the connection got disconnected.
std::unique_ptr< ContentInfo > cur_info
Information about the currently downloaded file.
bool is_cancelled
Whether the download has been cancelled.
void SelectAll()
Select everything we can select.
static std::optional< FileHandle > Open(const std::string &filename, std::string_view mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1173
static bool HasGame(const ContentInfo &ci, bool md5sum)
Wrapper function for GameScanner::HasGame.
void OnFailure() override
Callback for when the connection attempt failed.
void OnConnect(SOCKET s) override
Callback when the connection succeeded.
NetworkContentConnecter(std::string_view connection_string)
Initiate the connecting.
bool ReceivePackets()
Receive a packet at TCP level.
static void Connect(std::string_view uri, HTTPCallback *callback, std::string &&data="")
Connect to the given URI.
Definition http_curl.cpp:91
virtual NetworkRecvStatus CloseConnection(bool error=true)
This will put this socket handler in a close state.
Definition tcp.cpp:39
SOCKET sock
The socket currently connected to.
Definition tcp.h:36
virtual void SendPacket(std::unique_ptr< Packet > &&packet)
This function puts the packet in the send-queue and it is send as soon as possible.
Definition tcp.cpp:56
SendPacketsState SendPackets(bool closing_down=false)
Sends all the buffered packets out for this client.
Definition tcp.cpp:74
void CloseSocket()
Close the actual socket of the connection.
Definition tcp.cpp:27
bool CanSendReceive()
Check whether this socket can send or receive something.
Definition tcp.cpp:192
Parse data from a string / buffer.
std::string_view ReadUntilChar(char c, SeparatorUsage sep)
Read data until the first occurrence of 8-bit char 'c', and advance reader.
void SkipUntilChar(char c, SeparatorUsage sep)
Skip data until the first occurrence of 8-bit char 'c'.
@ KEEP_SEPARATOR
Keep the separator in the data as next value to be read.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
bool ReadIf(std::string_view str)
Check whether the next data matches 'str', and skip it.
T ReadIntegerBase(int base, T def=0, bool clamp=false)
Read and parse an integer in number 'base', and advance the reader.
std::string_view GetLeftData() const noexcept
Get data left to read.
std::string connection_string
Current address we are connecting to (before resolving).
Definition tcp.h:101
static std::shared_ptr< TCPConnecter > Create(Args &&... args)
Create the connecter, and initiate connecting by putting it in the collection of TCP connections to m...
Definition tcp.h:147
Helper for scanning for files with tar as extension.
Definition fileio_func.h:59
bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename={}) override
Add a file with the given filename.
Definition fileio.cpp:442
A timeout timer will fire once after the interval.
Definition timer.h:116
std::string_view NetworkContentMirrorUriString()
Get the URI string for the content mirror from the environment variable OTTD_CONTENT_MIRROR_URI,...
Definition config.cpp:51
std::string_view NetworkContentServerConnectionString()
Get the connection string for the content server from the environment variable OTTD_CONTENT_SERVER_CS...
Definition config.cpp:41
static const uint16_t NETWORK_CONTENT_SERVER_PORT
The default port of the content server (TCP).
Definition config.h:22
static const uint NETWORK_CONTENT_URL_LENGTH
The maximum length of a content's url, in bytes including '\0'.
Definition config.h:63
static const uint NETWORK_CONTENT_VERSION_LENGTH
The maximum length of a content's version, in bytes including '\0'.
Definition config.h:62
static const size_t TCP_MTU
Number of bytes we can pack in a single TCP packet.
Definition config.h:43
static const uint NETWORK_CONTENT_FILENAME_LENGTH
The maximum length of a content's filename, in bytes including '\0'.
Definition config.h:60
static const uint NETWORK_CONTENT_DESC_LENGTH
The maximum length of a content's description, in bytes including '\0'.
Definition config.h:64
static const uint NETWORK_CONTENT_TAG_LENGTH
The maximum length of a content's tag, in bytes including '\0'.
Definition config.h:65
static const uint NETWORK_CONTENT_NAME_LENGTH
The maximum length of a content's name, in bytes including '\0'.
Definition config.h:61
bool include(Container &container, typename Container::const_reference &item)
Helper function to append an item to a container if it is not already contained.
NetworkRecvStatus
Status of a network client; reasons why a client has quit.
Definition core.h:21
@ NETWORK_RECV_STATUS_OKAY
Everything is okay.
Definition core.h:22
Functions related to errors.
@ WL_ERROR
Errors (eg. saving/loading failed).
Definition error.h:26
void ShowErrorMessage(EncodedString &&summary_msg, int x, int y, CommandCost &cc)
Display an error message in a window.
bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
Extract the tar with the given filename in the directory where the tar resides.
Definition fileio.cpp:592
bool FioRemove(const std::string &filename)
Remove a file.
Definition fileio.cpp:329
Functions for standard in/out file operations.
@ SP_AUTODOWNLOAD_DIR
Search within the autodownload directory.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
@ NO_DIRECTORY
A path without any base directory.
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game).
Definition fileio_type.h:96
bool HasScenario(const ContentInfo &ci, bool md5sum)
Check whether we've got a given scenario based on its unique ID.
Definition fios.cpp:682
Base functions for all Games.
bool(const ContentInfo &ci, bool md5sum) HasContentProc
Check whether a function piece of content is locally known.
static bool HasGRFConfig(const ContentInfo &ci, bool md5sum)
Wrapper function for the HasProc.
ClientNetworkContentSocketHandler _network_content_client
The client we use to connect to the server.
static HasContentProc * GetHasContentProcforContentType(ContentType type)
Get the has-content check function for the given content type.
bool HasScenario(const ContentInfo &ci, bool md5sum)
Check whether we've got a given scenario based on its unique ID.
Definition fios.cpp:682
static bool GunzipFile(const ContentInfo &ci)
Gunzip a given file and remove the .gz if successful.
static constexpr auto CONTENT_QUEUE_TIMEOUT
Timeout after queueing content for it to try to be requested.
static std::string GetFullFilename(const ContentInfo &ci, bool compressed)
Determine the full filename of a piece of content information.
Part of the network protocol handling content distribution.
std::vector< std::unique_ptr< ContentInfo > > ContentVector
Vector with content info.
std::vector< const ContentInfo * > ConstContentVector
Vector with constant content info.
const GRFConfig * FindGRFConfig(uint32_t grfid, FindGRFConfigMode mode, const MD5Hash *md5sum, uint32_t desired_version)
Find a NewGRF in the scanned list.
@ FGCM_ANY
Use first found.
@ FGCM_EXACT
Only find Grfs matching md5sum.
uint16_t PacketSize
Size of the whole packet.
Definition packet.h:19
Declaration of OTTD revision dependent variables.
A number of safeguards to prevent using unsafe methods.
Types related to global configuration settings.
Definition of base types and functions in a cross-platform compatible way.
Parse strings.
@ ReplaceWithQuestionMark
Replace the unknown/bad bits with question marks.
Definition string_type.h:45
@ AllowNewline
Allow newlines; replaces '\r ' with ' ' during processing.
Definition string_type.h:46
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:90
Functions related to OTTD's strings.
Callbacks for notifying others about incoming data.
virtual void OnDownloadProgress(const ContentInfo &ci, int bytes)
We have progress in the download of a file.
virtual void OnDisconnect()
Callback for when the connection got disconnected.
virtual void OnReceiveContentInfo(const ContentInfo &ci)
We received a content info.
virtual void OnDownloadComplete(ContentID cid)
We have finished downloading a file.
virtual void OnConnect(bool success)
Callback for when the connection has finished.
Container for all important information about a piece of content.
uint32_t unique_id
Unique ID; either GRF ID or shortname.
MD5Hash md5sum
The MD5 checksum.
State state
Whether the content info is selected (for download).
ContentID id
Unique (server side) ID for the content.
std::string filename
Filename (for the .tar.gz; only valid on download).
bool IsSelected() const
Is the state either selected or autoselected?
ContentType type
Type of content.
std::vector< ContentID > dependencies
The dependencies (unique server side ids).
@ Unselected
The content has not been selected.
@ AlreadyHere
The content is already at the client side.
@ Selected
The content has been manually selected.
@ Autoselected
The content has been selected as dependency.
@ DoesNotExist
The content does not exist in the content system.
size_t Recv_bytes(std::span< uint8_t > span)
Extract at most the length of the span bytes from the packet into the span.
Definition packet.cpp:401
uint32_t Recv_uint32()
Read a 32 bits integer from the packet.
Definition packet.cpp:345
std::string Recv_string(size_t length, StringValidationSettings settings=StringValidationSetting::ReplaceWithQuestionMark)
Reads characters (bytes) from the packet until it finds a '\0', or reaches a maximum of length charac...
Definition packet.cpp:423
uint8_t Recv_uint8()
Read a 8 bits integer from the packet.
Definition packet.cpp:316
size_t RemainingBytesToTransfer() const
Get the amount of bytes that are still available for the Transfer functions.
Definition packet.cpp:445
ssize_t TransferOut(F transfer_function)
Transfer data from the packet to the given function.
Definition packet.h:126
Subdirectory GetContentInfoSubDir(ContentType type)
Helper to get the subdirectory a ContentInfo is located in.
uint32_t ContentID
Unique identifier for the content.
ContentType
The values in the enum are important; they are used as database 'keys'.
@ CONTENT_TYPE_AI_LIBRARY
The content consists of an AI library.
@ CONTENT_TYPE_BASE_SOUNDS
The content consists of base sounds.
@ CONTENT_TYPE_GAME_LIBRARY
The content consists of a GS library.
@ CONTENT_TYPE_BASE_GRAPHICS
The content consists of base graphics.
@ CONTENT_TYPE_AI
The content consists of an AI.
@ CONTENT_TYPE_SCENARIO
The content consists of a scenario.
@ CONTENT_TYPE_NEWGRF
The content consists of a NewGRF.
@ CONTENT_TYPE_BASE_MUSIC
The content consists of base music.
@ CONTENT_TYPE_GAME
The content consists of a game script.
@ CONTENT_TYPE_END
Helper to mark the end of the types.
@ CONTENT_TYPE_HEIGHTMAP
The content consists of a heightmap.
@ PACKET_CONTENT_CLIENT_CONTENT
Request a content file given an internal ID.
@ PACKET_CONTENT_CLIENT_INFO_LIST
Queries the content server for a list of info of a given content type.
@ PACKET_CONTENT_CLIENT_INFO_EXTID_MD5
Queries the content server for information about a list of external IDs and MD5.
@ PACKET_CONTENT_CLIENT_INFO_ID
Queries the content server for information about a list of internal IDs.
@ PACKET_CONTENT_CLIENT_INFO_EXTID
Queries the content server for information about a list of external IDs.
Definition of Interval and OneShot timers.
Definition of the Window system.
void CloseWindowById(WindowClass cls, WindowNumber number, bool force, int data)
Close a window by its class and window number (if it is open).
Definition window.cpp:1198
Window functions not directly related to making/drawing windows.
@ WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD
Network content download status.
Definition window_type.h:45
@ WC_NETWORK_STATUS_WINDOW
Network status window; Window numbers: