OpenTTD Source 20260721-master-g25ec12c62d
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 "../newgrf.h"
18#include "../base_media_base.h"
20#include "../base_media_music.h"
22#include "../settings_type.h"
23#include "../strings_func.h"
24#include "../timer/timer.h"
27#include "network_content.h"
28
29#include "table/strings.h"
30
31#if defined(WITH_ZLIB)
32# include <zlib.h>
33# if defined(_WIN32)
34 /* Required for: dup, fileno, close */
35# include <io.h>
36# endif
37#endif
38
39#ifdef __EMSCRIPTEN__
40# include <emscripten.h>
41#endif
42
43#include "../safeguards.h"
44
45extern bool HasScenario(const ContentInfo &ci, bool md5sum);
46
49
51static bool HasGRFConfig(const ContentInfo &ci, bool md5sum)
52{
53 return FindGRFConfig(UnflattenNewGRFLabel<GrfID>(ci.unique_id), md5sum ? FindGRFConfigMode::Exact : FindGRFConfigMode::Any, md5sum ? &ci.md5sum : nullptr) != nullptr;
54}
55
63using HasContentProc = bool(const ContentInfo &ci, bool md5sum);
64
86
88{
89 auto ci = std::make_unique<ContentInfo>();
90 ci->type = (ContentType)p.Recv_uint8();
91 ci->id = (ContentID)p.Recv_uint32();
92 ci->filesize = p.Recv_uint32();
93
98
99 ci->unique_id = p.Recv_uint32();
100 p.Recv_bytes(ci->md5sum);
101
102 uint dependency_count = p.Recv_uint8();
103 ci->dependencies.reserve(dependency_count);
104 for (uint i = 0; i < dependency_count; i++) {
105 ContentID dependency_cid = (ContentID)p.Recv_uint32();
106 ci->dependencies.push_back(dependency_cid);
107 this->reverse_dependency_map.emplace(dependency_cid, ci->id);
108 }
109
110 uint tag_count = p.Recv_uint8();
111 ci->tags.reserve(tag_count);
112 for (uint i = 0; i < tag_count; i++) ci->tags.push_back(p.Recv_string(NETWORK_CONTENT_TAG_LENGTH));
113
114 if (!ci->IsValid()) {
115 this->CloseConnection();
116 return false;
117 }
118
120 if (proc != nullptr) {
121 if (proc(*ci, true)) {
123 } else {
125 if (proc(*ci, false)) ci->upgrade = true;
126 }
127 } else {
129 }
130
131 /* Something we don't have and has filesize 0 does not exist in the system */
132 if (ci->state == ContentInfo::State::Unselected && ci->filesize == 0) ci->state = ContentInfo::State::DoesNotExist;
133
134 /* Do we already have a stub for this? */
135 for (const auto &ici : this->infos) {
136 if (ici->type == ci->type && ici->unique_id == ci->unique_id && ci->md5sum == ici->md5sum) {
137 /* Preserve the name if possible */
138 if (ci->name.empty()) ci->name = ici->name;
139 if (ici->IsSelected()) ci->state = ici->state;
140
141 /*
142 * As ici might be selected by the content window we cannot delete that.
143 * However, we want to keep most of the values of ci, except the values
144 * we (just) already preserved.
145 */
146 *ici = *ci;
147
148 this->OnReceiveContentInfo(*ici);
149 return true;
150 }
151 }
152
153 /* Missing content info? Don't list it */
154 if (ci->filesize == 0) {
155 return true;
156 }
157
158 ContentInfo *info = this->infos.emplace_back(std::move(ci)).get();
159
160 /* Incoming data means that we might need to reconsider dependencies */
161 ConstContentVector parents;
162 this->ReverseLookupTreeDependency(parents, info);
163 for (const ContentInfo *ici : parents) {
164 this->CheckDependencyState(*ici);
165 }
166
167 this->OnReceiveContentInfo(*info);
168
169 return true;
170}
171
177{
178 if (type == ContentType::End) {
189 return;
190 }
191
192 this->Connect();
193
194 auto p = std::make_unique<Packet>(this, PacketContentType::ClientInfoList);
195 p->Send_uint8 (to_underlying(type));
196 p->Send_uint32(0xffffffff);
197 p->Send_uint8 (1);
198 p->Send_string("vanilla");
199 p->Send_string(_openttd_content_version);
200
201 /* Patchpacks can extend the list with one. In BaNaNaS metadata you can
202 * add a branch in the 'compatibility' list, to filter on this. If you want
203 * your patchpack to be mentioned in the BaNaNaS web-interface, create an
204 * issue on https://github.com/OpenTTD/bananas-api asking for this.
205
206 p->Send_string("patchpack"); // Or what-ever the name of your patchpack is.
207 p->Send_string(_openttd_content_version_patchpack);
208
209 */
210
211 this->SendPacket(std::move(p));
212}
213
218void ClientNetworkContentSocketHandler::RequestContentList(std::span<const ContentID> content_ids)
219{
220 /* We can "only" send a limited number of IDs in a single packet.
221 * A packet begins with the packet size and a byte for the type.
222 * Then this packet adds a uint16_t for the count in this packet.
223 * The rest of the packet can be used for the IDs. */
224 static constexpr size_t MAX_CONTENT_IDS_PER_PACKET = (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint16_t)) / sizeof(uint32_t);
225
226 if (content_ids.empty()) return;
227
228 this->Connect();
229
230 for (auto it = std::begin(content_ids); it != std::end(content_ids); /* nothing */) {
231 auto last = std::ranges::next(it, MAX_CONTENT_IDS_PER_PACKET, std::end(content_ids));
232
233 auto p = std::make_unique<Packet>(this, PacketContentType::ClientInfoID, TCP_MTU);
234 p->Send_uint16(std::distance(it, last));
235
236 for (; it != last; ++it) {
237 p->Send_uint32(*it);
238 }
239
240 this->SendPacket(std::move(p));
241 }
242}
243
250{
251 if (cv == nullptr) return;
252
253 this->Connect();
254
255 assert(cv->size() < 255);
256 assert(cv->size() < (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint8_t)) /
257 (sizeof(uint8_t) + sizeof(uint32_t) + (send_md5sum ? MD5_HASH_BYTES : 0)));
258
259 auto p = std::make_unique<Packet>(this, send_md5sum ? PacketContentType::ClientInfoExternalIDMD5 : PacketContentType::ClientInfoExternalID, TCP_MTU);
260 p->Send_uint8((uint8_t)cv->size());
261
262 for (const auto &ci : *cv) {
263 p->Send_uint8((uint8_t)ci->type);
264 p->Send_uint32(ci->unique_id);
265 if (!send_md5sum) continue;
266 p->Send_bytes(ci->md5sum);
267 }
268
269 this->SendPacket(std::move(p));
270
271 for (auto &ci : *cv) {
272 bool found = false;
273 for (const auto &ci2 : this->infos) {
274 if (ci->type == ci2->type && ci->unique_id == ci2->unique_id &&
275 (!send_md5sum || ci->md5sum == ci2->md5sum)) {
276 found = true;
277 break;
278 }
279 }
280 if (!found) {
281 this->infos.push_back(std::move(ci));
282 }
283 }
284}
285
292void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes, bool fallback)
293{
294 bytes = 0;
295
296 ContentIDList content;
297 for (const auto &ci : this->infos) {
298 if (!ci->IsSelected() || ci->state == ContentInfo::State::AlreadyHere) continue;
299
300 content.push_back(ci->id);
301 bytes += ci->filesize;
302 }
303
304 files = (uint)content.size();
305
306 /* If there's nothing to download, do nothing. */
307 if (files == 0) return;
308
309 this->is_cancelled = false;
310
311 if (fallback) {
312 this->DownloadSelectedContentFallback(content);
313 } else {
314 this->DownloadSelectedContentHTTP(content);
315 }
316}
317
323{
324 std::string content_request;
325 for (const ContentID &id : content) {
326 format_append(content_request, "{}\n", id);
327 }
328
329 this->http_response_index = -1;
330
331 NetworkHTTPSocketHandler::Connect(NetworkContentMirrorUriString(), this, std::move(content_request));
332}
333
339{
340 uint count = (uint)content.size();
341 const ContentID *content_ids = content.data();
342 this->Connect();
343
344 while (count > 0) {
345 /* We can "only" send a limited number of IDs in a single packet.
346 * A packet begins with the packet size and a byte for the type.
347 * Then this packet adds a uint16_t for the count in this packet.
348 * The rest of the packet can be used for the IDs. */
349 uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint16_t)) / sizeof(uint32_t));
350
351 auto p = std::make_unique<Packet>(this, PacketContentType::ClientContent, TCP_MTU);
352 p->Send_uint16(p_count);
353
354 for (uint i = 0; i < p_count; i++) {
355 p->Send_uint32(content_ids[i]);
356 }
357
358 this->SendPacket(std::move(p));
359 count -= p_count;
360 content_ids += p_count;
361 }
362}
363
371static std::string GetFullFilename(const ContentInfo &ci, bool compressed)
372{
374 if (dir == Subdirectory::None) return {};
375
376 std::string buf = FioGetDirectory(Searchpath::AutodownloadDir, dir);
377 buf += ci.filename;
378 buf += compressed ? ".tar.gz" : ".tar";
379
380 return buf;
381}
382
388static bool GunzipFile(const ContentInfo &ci)
389{
390#if defined(WITH_ZLIB)
391 bool ret = true;
392
393 /* Need to open the file with fopen() to support non-ASCII on Windows. */
394 auto ftmp = FileHandle::Open(GetFullFilename(ci, true), "rb");
395 if (!ftmp.has_value()) return false;
396 /* Duplicate the handle, and close the FILE*, to avoid double-closing the handle later. */
397 int fdup = dup(fileno(*ftmp));
398 gzFile fin = gzdopen(fdup, "rb");
399 ftmp.reset();
400
401 auto fout = FileHandle::Open(GetFullFilename(ci, false), "wb");
402
403 if (fin == nullptr || !fout.has_value()) {
404 ret = false;
405 } else {
406 uint8_t buff[8192];
407 for (;;) {
408 int read = gzread(fin, buff, sizeof(buff));
409 if (read == 0) {
410 /* If gzread() returns 0, either the end-of-file has been
411 * reached or an underlying read error has occurred.
412 *
413 * gzeof() can't be used, because:
414 * 1.2.5 - it is safe, 1 means 'everything was OK'
415 * 1.2.3.5, 1.2.4 - 0 or 1 is returned 'randomly'
416 * 1.2.3.3 - 1 is returned for truncated archive
417 *
418 * So we use gzerror(). When proper end of archive
419 * has been reached, then:
420 * errnum == Z_STREAM_END in 1.2.3.3,
421 * errnum == 0 in 1.2.4 and 1.2.5 */
422 int errnum;
423 gzerror(fin, &errnum);
424 if (errnum != 0 && errnum != Z_STREAM_END) ret = false;
425 break;
426 }
427 if (read < 0 || static_cast<size_t>(read) != fwrite(buff, 1, read, *fout)) {
428 /* If gzread() returns -1, there was an error in archive */
429 ret = false;
430 break;
431 }
432 /* DO NOT DO THIS! It will fail to detect broken archive with 1.2.3.3!
433 * if (read < sizeof(buff)) break; */
434 }
435 }
436
437 if (fin != nullptr) {
438 gzclose(fin);
439 } else if (fdup != -1) {
440 /* Failing gzdopen does not close the passed file descriptor. */
441 close(fdup);
442 }
443
444 return ret;
445#else
446 NOT_REACHED();
447#endif /* defined(WITH_ZLIB) */
448}
449
451{
452 if (!this->cur_file.has_value()) {
453 /* When we haven't opened a file this must be our first packet with metadata. */
454 this->cur_info = std::make_unique<ContentInfo>();
455 this->cur_info->type = (ContentType)p.Recv_uint8();
456 this->cur_info->id = (ContentID)p.Recv_uint32();
457 this->cur_info->filesize = p.Recv_uint32();
459
460 if (!this->BeforeDownload()) {
461 this->CloseConnection();
462 return false;
463 }
464 } else {
465 /* We have a file opened, thus are downloading internal content */
466 ssize_t to_read = p.RemainingBytesToTransfer();
467 auto write_to_disk = [this](std::span<const uint8_t> buffer) {
468 return fwrite(buffer.data(), 1, buffer.size(), *this->cur_file);
469 };
470 if (to_read != 0 && p.TransferOut(write_to_disk) != to_read) {
473 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD),
474 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE),
476 this->CloseConnection();
477 this->cur_file.reset();
478
479 return false;
480 }
481
482 this->OnDownloadProgress(*this->cur_info, (int)to_read);
483
484 if (to_read == 0) this->AfterDownload();
485 }
486
487 return true;
488}
489
495{
496 if (!this->cur_info->IsValid()) {
497 this->cur_info.reset();
498 return false;
499 }
500
501 if (this->cur_info->filesize != 0) {
502 /* The filesize is > 0, so we are going to download it */
503 std::string filename = GetFullFilename(*this->cur_info, true);
504 if (filename.empty() || !(this->cur_file = FileHandle::Open(filename, "wb")).has_value()) {
505 /* Unless that fails of course... */
508 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD),
509 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE),
511 return false;
512 }
513 }
514 return true;
515}
516
522{
523 /* We read nothing; that's our marker for end-of-stream.
524 * Now gunzip the tar and make it known. */
525 this->cur_file.reset();
526
527 if (GunzipFile(*this->cur_info)) {
528 FioRemove(GetFullFilename(*this->cur_info, true));
529
531 if (sd == Subdirectory::None) NOT_REACHED();
532
533 TarScanner ts;
534 std::string fname = GetFullFilename(*this->cur_info, false);
535 ts.AddFile(sd, fname);
536
537 if (this->cur_info->type == ContentType::BaseMusic) {
538 /* Music can't be in a tar. So extract the tar! */
540 FioRemove(fname);
541 }
542
543#ifdef __EMSCRIPTEN__
544 EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
545#endif
546
547 this->OnDownloadComplete(this->cur_info->id);
548 } else {
549 ShowErrorMessage(GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_EXTRACT), {}, WarningLevel::Error);
550 }
551}
552
554{
555 return this->is_cancelled;
556}
557
558/* Also called to just clean up the mess. */
560{
561 this->http_response.clear();
562 this->http_response.shrink_to_fit();
563 this->http_response_index = -2;
564
565 if (this->cur_file.has_value()) {
566 this->OnDownloadProgress(*this->cur_info, -1);
567
568 this->cur_file.reset();
569 }
570
571 /* If we fail, download the rest via the 'old' system. */
572 if (!this->is_cancelled) {
573 uint files, bytes;
574
575 this->DownloadSelectedContent(files, bytes, true);
576 }
577}
578
579void ClientNetworkContentSocketHandler::OnReceiveData(std::unique_ptr<char[]> data, size_t length)
580{
581 assert(data.get() == nullptr || length != 0);
582
583 /* Ignore any latent data coming from a connection we closed. */
584 if (this->http_response_index == -2) {
585 return;
586 }
587
588 if (this->http_response_index == -1) {
589 if (data != nullptr) {
590 /* Append the rest of the response. */
591 this->http_response.insert(this->http_response.end(), data.get(), data.get() + length);
592 return;
593 } else {
594 /* Make sure the response is properly terminated. */
595 this->http_response.push_back('\0');
596
597 /* And prepare for receiving the rest of the data. */
598 this->http_response_index = 0;
599 }
600 }
601
602 if (data != nullptr) {
603 /* We have data, so write it to the file. */
604 if (fwrite(data.get(), 1, length, *this->cur_file) != length) {
605 /* Writing failed somehow, let try via the old method. */
606 this->OnFailure();
607 } else {
608 /* Just received the data. */
609 this->OnDownloadProgress(*this->cur_info, (int)length);
610 }
611
612 /* Nothing more to do now. */
613 return;
614 }
615
616 if (this->cur_file.has_value()) {
617 /* We've finished downloading a file. */
618 this->AfterDownload();
619 }
620
621 if ((uint)this->http_response_index >= this->http_response.size()) {
622 /* It's not a real failure, but if there's
623 * nothing more to download it helps with
624 * cleaning up the stuff we allocated. */
625 this->OnFailure();
626 return;
627 }
628
629 /* When we haven't opened a file this must be our first packet with metadata. */
630 this->cur_info = std::make_unique<ContentInfo>();
631
632 try {
633 for (;;) {
634 std::string_view buffer{this->http_response.data(), this->http_response.size()};
635 buffer.remove_prefix(this->http_response_index);
636 auto len = buffer.find('\n');
637 if (len == std::string_view::npos) throw std::exception{};
638 /* Update the index for the next one */
639 this->http_response_index += static_cast<int>(len + 1);
640
641 StringConsumer consumer{buffer.substr(0, len)};
642
643 /* Read the ID */
644 this->cur_info->id = static_cast<ContentID>(consumer.ReadIntegerBase<uint>(10));
645 if (!consumer.ReadIf(",")) throw std::exception{};
646
647 /* Read the type */
648 this->cur_info->type = static_cast<ContentType>(consumer.ReadIntegerBase<uint>(10));
649 if (!consumer.ReadIf(",")) throw std::exception{};
650
651 /* Read the file size */
652 this->cur_info->filesize = consumer.ReadIntegerBase<uint32_t>(10);
653 if (!consumer.ReadIf(",")) throw std::exception{};
654
655 /* Read the URL */
656 auto url = consumer.GetLeftData();
657
658 /* Is it a fallback URL? If so, just continue with the next one. */
659 if (consumer.ReadIf("ottd")) {
660 /* Have we gone through all lines? */
661 if (static_cast<size_t>(this->http_response_index) >= this->http_response.size()) throw std::exception{};
662 continue;
663 }
664
666 std::string_view filename;
667 /* Skip all but the last part. There must be at least one / though */
668 do {
669 if (!consumer.ReadIf("/")) throw std::exception{};
670 filename = consumer.ReadUntilChar('/', StringConsumer::KEEP_SEPARATOR);
671 } while (consumer.AnyBytesLeft());
672
673 /* Remove the extension from the string. */
674 for (uint i = 0; i < 2; i++) {
675 auto pos = filename.find_last_of('.');
676 if (pos == std::string::npos) throw std::exception{};
677 filename = filename.substr(0, pos);
678 }
679
680 /* Copy the string, without extension, to the filename. */
681 this->cur_info->filename = filename;
682
683 /* Request the next file. */
684 if (!this->BeforeDownload()) throw std::exception{};
685
687 break;
688 }
689 } catch (const std::exception&) {
690 this->OnFailure();
691 }
692}
693
695class NetworkContentConnecter : public TCPConnecter {
696public:
702
703 void OnFailure() override
704 {
705 _network_content_client.is_connecting = false;
706 _network_content_client.OnConnect(false);
707 }
708
709 void OnConnect(SOCKET s) override
710 {
711 assert(_network_content_client.sock == INVALID_SOCKET);
712 _network_content_client.last_activity = std::chrono::steady_clock::now();
713 _network_content_client.is_connecting = false;
716 _network_content_client.OnConnect(true);
717 }
718};
719
724{
725 if (this->sock != INVALID_SOCKET || this->is_connecting) return;
726
727 this->is_cancelled = false;
728 this->is_connecting = true;
729
731}
732
735{
737
738 if (this->sock == INVALID_SOCKET) return NetworkRecvStatus::Okay;
739
740 this->CloseSocket();
741 this->OnDisconnect();
742
744}
745
750{
751 this->is_cancelled = true;
752 this->CloseConnection();
753}
754
760{
761 if (this->sock == INVALID_SOCKET || this->is_connecting) return;
762
763 /* Close the connection to the content server after inactivity; there can still be downloads pending via HTTP. */
764 if (std::chrono::steady_clock::now() > this->last_activity + IDLE_TIMEOUT) {
765 this->CloseConnection();
766 return;
767 }
768
769 if (this->CanSendReceive()) {
770 if (this->ReceivePackets()) {
771 /* Only update activity once a packet is received, instead of every time we try it. */
772 this->last_activity = std::chrono::steady_clock::now();
773 }
774 }
775
776 this->SendPackets();
777}
778
780static constexpr auto CONTENT_QUEUE_TIMEOUT = std::chrono::milliseconds(100);
781
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);
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) {
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
901{
902 switch (ci.state) {
905 this->Unselect(ci.id);
906 break;
907
909 this->Select(ci.id);
910 break;
911
912 default:
913 break;
914 }
915}
916
923{
924 auto range = this->reverse_dependency_map.equal_range(child.id);
925
926 for (auto iter = range.first; iter != range.second; ++iter) {
927 parents.push_back(GetContent(iter->second));
928 }
929}
930
937{
938 tree.push_back(child);
939
940 /* First find all direct parents. We can't use the "normal" iterator as
941 * we are including stuff into the vector and as such the vector's data
942 * store can be reallocated (and thus move), which means our iterating
943 * pointer gets invalid. So fall back to the indices. */
944 for (size_t i = 0; i < tree.size(); i++) {
945 ConstContentVector parents;
946 this->ReverseLookupDependency(parents, *tree[i]);
947
948 for (const ContentInfo *ci : parents) {
949 include(tree, ci);
950 }
951 }
952}
953
959{
961 /* Selection is easy; just walk all children and set the
962 * autoselected state. That way we can see what we automatically
963 * selected and thus can unselect when a dependency is removed. */
964 for (auto &dependency : ci.dependencies) {
965 ContentInfo *c = this->GetContent(dependency);
966 if (c == nullptr) {
967 this->DownloadContentInfo(dependency);
968 } else if (c->state == ContentInfo::State::Unselected) {
970 this->CheckDependencyState(*c);
971 }
972 }
973 return;
974 }
975
976 if (ci.state != ContentInfo::State::Unselected) return;
977
978 /* For unselection we need to find the parents of us. We need to
979 * unselect them. After that we unselect all children that we
980 * depend on and are not used as dependency for us, but only when
981 * we automatically selected them. */
982 ConstContentVector parents;
983 this->ReverseLookupDependency(parents, ci);
984 for (const ContentInfo *c : parents) {
985 if (!c->IsSelected()) continue;
986
987 this->Unselect(c->id);
988 }
989
990 for (auto &dependency : ci.dependencies) {
991 const ContentInfo *c = this->GetContent(dependency);
992 if (c == nullptr) {
993 DownloadContentInfo(dependency);
994 continue;
995 }
996 if (c->state != ContentInfo::State::Autoselected) continue;
997
998 /* Only unselect when WE are the only parent. */
999 parents.clear();
1000 this->ReverseLookupDependency(parents, *c);
1001
1002 /* First check whether anything depends on us */
1003 int sel_count = 0;
1004 bool force_selection = false;
1005 for (const ContentInfo *parent_ci : parents) {
1006 if (parent_ci->IsSelected()) sel_count++;
1007 if (parent_ci->state == ContentInfo::State::Selected) force_selection = true;
1008 }
1009 if (sel_count == 0) {
1010 /* Nothing depends on us */
1011 this->Unselect(c->id);
1012 continue;
1013 }
1014 /* Something manually selected depends directly on us */
1015 if (force_selection) continue;
1016
1017 /* "Flood" search to find all items in the dependency graph*/
1018 parents.clear();
1019 this->ReverseLookupTreeDependency(parents, c);
1020
1021 /* Is there anything that is "force" selected?, if so... we're done. */
1022 for (const ContentInfo *parent_ci : parents) {
1023 if (parent_ci->state != ContentInfo::State::Selected) continue;
1024
1025 force_selection = true;
1026 break;
1027 }
1028
1029 /* So something depended directly on us */
1030 if (force_selection) continue;
1031
1032 /* Nothing depends on us, mark the whole graph as unselected.
1033 * After that's done run over them once again to test their children
1034 * to unselect. Don't do it immediately because it'll do exactly what
1035 * we're doing now. */
1036 for (const ContentInfo *parent : parents) {
1037 if (parent->state == ContentInfo::State::Autoselected) this->Unselect(parent->id);
1038 }
1039 for (const ContentInfo *parent : parents) {
1040 this->CheckDependencyState(*this->GetContent(parent->id));
1041 }
1042 }
1043}
1044
1047{
1048 this->infos.clear();
1049 this->requested.clear();
1050 this->queued.clear();
1051 this->reverse_dependency_map.clear();
1052}
1053
1054/*** CALLBACK ***/
1055
1057{
1058 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1059 ContentCallback *cb = this->callbacks[i];
1060 /* the callback may remove itself from this->callbacks */
1061 cb->OnConnect(success);
1062 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1063 }
1064}
1065
1067{
1068 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1069 ContentCallback *cb = this->callbacks[i];
1070 cb->OnDisconnect();
1071 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1072 }
1073}
1074
1076{
1077 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1078 ContentCallback *cb = this->callbacks[i];
1079 /* the callback may add items and/or remove itself from this->callbacks */
1080 cb->OnReceiveContentInfo(ci);
1081 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1082 }
1083}
1084
1086{
1087 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1088 ContentCallback *cb = this->callbacks[i];
1089 cb->OnDownloadProgress(ci, bytes);
1090 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1091 }
1092}
1093
1095{
1096 ContentInfo *ci = this->GetContent(cid);
1097 if (ci != nullptr) {
1099 }
1100
1101 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1102 ContentCallback *cb = this->callbacks[i];
1103 /* the callback may remove itself from this->callbacks */
1104 cb->OnDownloadComplete(cid);
1105 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1106 }
1107}
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.
static bool HasAILibrary(const ContentInfo &ci, bool md5sum)
Check whether we have an AI library with the exact characteristics as ci.
Definition ai_core.cpp:342
static bool HasAI(const ContentInfo &ci, bool md5sum)
Wrapper function for AIScanner::HasAI.
Definition ai_core.cpp:331
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 ReceiveServerInfo(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.
void Connect()
Connect with the content server.
std::unordered_multimap< ContentID, ContentID > reverse_dependency_map
Content reverse dependency map.
bool ReceiveServerContent(Packet &p) override
Server sending list of content info: uint32_t unique id uint32_t file size (0 == does not exist) stri...
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:1219
static bool HasGameLibrary(const ContentInfo &ci, bool md5sum)
Check whether we have an Game library with the exact characteristics as ci.
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:93
virtual NetworkRecvStatus CloseConnection(bool error=true)
This will put this socket handler in a close state.
Definition tcp.cpp:40
SOCKET sock
The socket currently connected to.
Definition tcp.h:35
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:57
SendPacketsState SendPackets(bool closing_down=false)
Sends all the buffered packets out for this client.
Definition tcp.cpp:75
void CloseSocket()
Close the actual socket of the connection.
Definition tcp.cpp:28
bool CanSendReceive()
Check whether this socket can send or receive something.
Definition tcp.cpp:193
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:100
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:146
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:451
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
@ Okay
Everything is okay.
Definition core.h:22
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23).
Definition enum_type.hpp:21
Functions related to errors.
@ 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:601
bool FioRemove(const std::string &filename)
Remove a file.
Definition fileio.cpp:334
Functions for standard in/out file operations.
@ AutodownloadDir
Search within the autodownload directory.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
@ None
A path without any base directory.
@ Baseset
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:673
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)
Check whether NewGRF content exists.
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:673
static bool GunzipFile(const ContentInfo &ci)
Gunzip a given file and remove the .gz if successful.
static TimeoutTimer< TimerWindow > _request_queue_timeout
Timer delay requesting content, so it can be batched more efficiently in asynchronous contexts.
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.
Base for the NewGRF implementation.
constexpr T UnflattenNewGRFLabel(uint32_t value)
Unflatten a NewGRF related label from a 32 bits integer.
Definition newgrf.h:267
const GRFConfig * FindGRFConfig(GrfID grfid, FindGRFConfigMode mode, const MD5Hash *md5sum, uint32_t desired_version)
Find a NewGRF in the scanned list.
@ Exact
Only find Grfs matching md5sum.
@ Any
Use first found.
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:154
Subdirectory GetContentInfoSubDir(ContentType type)
Helper to get the subdirectory a ContentInfo is located in.
@ ClientContent
Request a content file given an internal ID.
Definition tcp_content.h:29
@ ClientInfoExternalID
Queries the content server for information about a list of external IDs.
Definition tcp_content.h:26
@ ClientInfoID
Queries the content server for information about a list of internal IDs.
Definition tcp_content.h:25
@ ClientInfoExternalIDMD5
Queries the content server for information about a list of external IDs and MD5.
Definition tcp_content.h:27
@ ClientInfoList
Queries the content server for a list of info of a given content type.
Definition tcp_content.h:24
uint32_t ContentID
Unique identifier for the content.
ContentType
The values in the enum are important; they are received over the network from the content servers.
@ BaseSounds
The content consists of base sounds.
@ GsLibrary
The content consists of a GS library.
@ Scenario
The content consists of a scenario.
@ Heightmap
The content consists of a heightmap.
@ BaseGraphics
The content consists of base graphics.
@ Ai
The content consists of an AI.
@ End
Helper to mark the end of the types.
@ BaseMusic
The content consists of base music.
@ Gs
The content consists of a game script.
@ NewGRF
The content consists of a NewGRF.
@ AiLibrary
The content consists of an AI library.
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:1201
Window functions not directly related to making/drawing windows.
@ ContentDownload
Network content download status.
Definition window_type.h:57