From 5cfa67ae028b57d315b212e220efc7b36b9a7177 Mon Sep 17 00:00:00 2001 From: rcelyte Date: Fri, 8 Mar 2024 07:23:05 +0000 Subject: [PATCH] 1.35.0 fixes --- BeatUpClient/BeatUpClient.csproj | 2 +- BeatUpClient/MakeThingsPublic.cs | 4 ++-- BeatUpClient/cs/Patches/6_PlayerData.cs | 4 ++-- BeatUpClient/cs/Patches/7_Entitlement.cs | 2 +- BeatUpClient/cs/Patches/8_Load.cs | 14 +++++++------- BeatUpClient/cs/Selector/NetworkConfig.cs | 5 +++-- BeatUpClient/cs/Sharing/LevelUnzip.cs | 9 ++++++--- BeatUpClient/makefile | 5 +++-- BeatUpClient/qpm.json | 2 +- README.md | 2 +- common/packets.txt | 1 + makefile | 8 +------- src/instance/instance.c | 2 +- src/packets.gen.h | 2 ++ src/status/http.c | 2 +- src/status/internal.c | 11 +++++++++-- 16 files changed, 42 insertions(+), 33 deletions(-) diff --git a/BeatUpClient/BeatUpClient.csproj b/BeatUpClient/BeatUpClient.csproj index b51cc11..8bb78c9 100644 --- a/BeatUpClient/BeatUpClient.csproj +++ b/BeatUpClient/BeatUpClient.csproj @@ -10,7 +10,7 @@ ..\Refs $(LocalRefsDir) full - 0.8.2 + 0.9.0 diff --git a/BeatUpClient/MakeThingsPublic.cs b/BeatUpClient/MakeThingsPublic.cs index 93b7023..7845d3c 100644 --- a/BeatUpClient/MakeThingsPublic.cs +++ b/BeatUpClient/MakeThingsPublic.cs @@ -47,7 +47,6 @@ "HMUI.HoverHint::_hoverHintController", "HMUI.HoverHintController::_isHiding", "HMUI.HoverHintController::SetupAndShowHintPanel", - "HMUI.IconSegmentedControl::_container", "HMUI.ImageView::_flipGradientColors", "HMUI.ImageView::_skew", "HMUI.InputFieldView::_placeholderText", @@ -55,8 +54,8 @@ "HMUI.InputFieldView::UpdateClearButton", "HMUI.ModalView::_dismissPanelAnimation", "HMUI.PanelAnimationSO::_duration", + "HMUI.SegmentedControl::_container", "HMUI.SimpleTextDropdown::_text", - "HMUI.TextSegmentedControl::_container", "HMUI.UIKeyboard::_buttonBinder", "HMUI.UIKeyboard::keyWasPressedEvent", "HMUI.UIKeyboardKey::_canBeUppercase", @@ -158,6 +157,7 @@ ("Libs", "System.IO.Compression.dll", null), ("Plugins", "SongCore.dll", null), (managed, "BeatmapCore.dll", null), + (managed, "BeatSaber.Init.dll", null), (managed, "BGLib.AppFlow.dll", new[] { "SceneInfo::_sceneName", }), diff --git a/BeatUpClient/cs/Patches/6_PlayerData.cs b/BeatUpClient/cs/Patches/6_PlayerData.cs index c24e2df..bba4486 100644 --- a/BeatUpClient/cs/Patches/6_PlayerData.cs +++ b/BeatUpClient/cs/Patches/6_PlayerData.cs @@ -54,7 +54,7 @@ static partial class BeatUpClient { [Patch(PatchType.Prefix, typeof(LobbyPlayersDataModel), nameof(LobbyPlayersDataModel.HandleMenuRpcManagerGetRecommendedBeatmap))] static void LobbyPlayersDataModel_HandleMenuRpcManagerGetRecommendedBeatmap(LobbyPlayersDataModel __instance, string userId) { BeatmapKey key = __instance[__instance.localUserId].beatmapKey; - BeatmapLevel? beatmapLevel = Resolve()!.GetBeatmapLevelForLevelId(key.levelId); + BeatmapLevel? beatmapLevel = Resolve()!.GetBeatmapLevel(key.levelId); RecommendPreview preview = playerData.previews[PlayerIndex(Resolve()?.localPlayer)]; if(!string.IsNullOrEmpty(HashForLevelID(beatmapLevel?.levelID))) Net.Send(new MpBeatmapPacket(preview, beatmapLevel!, key)); // TODO: maybe avoid alloc? @@ -63,7 +63,7 @@ static partial class BeatUpClient { [Detour(typeof(LobbyPlayersDataModel), nameof(LobbyPlayersDataModel.SetLocalPlayerBeatmapLevel))] static void LobbyPlayersDataModel_SetLocalPlayerBeatmapLevel(LobbyPlayersDataModel self, ref BeatmapKey beatmapKey) { - BeatmapLevel? beatmapLevel = Resolve()!.GetBeatmapLevelForLevelId(beatmapKey.levelId); + BeatmapLevel? beatmapLevel = Resolve()!.GetBeatmapLevel(beatmapKey.levelId); if(beatmapKey.IsValid()) { RecommendPreview? preview = playerData.previews[PlayerIndex(Resolve()?.localPlayer)]; if(preview.levelID != beatmapKey.levelId) { diff --git a/BeatUpClient/cs/Patches/7_Entitlement.cs b/BeatUpClient/cs/Patches/7_Entitlement.cs index db9b1ab..97f8e30 100644 --- a/BeatUpClient/cs/Patches/7_Entitlement.cs +++ b/BeatUpClient/cs/Patches/7_Entitlement.cs @@ -6,7 +6,7 @@ static partial class BeatUpClient { Log.Debug($"AnnounceWrapper(task.Result={status})"); ShareInfo info = new ShareInfo(0, new ShareMeta(0), new ShareId {usage = ShareableType.BeatmapSet, mimeType = "application/json", name = levelId}); if(status == EntitlementsStatus.Ok) { - IBeatmapLevelData? beatmapLevelData = (await Resolve()!.GetBeatmapLevelDataAsync(levelId, System.Threading.CancellationToken.None)).beatmapLevelData; + IBeatmapLevelData? beatmapLevelData = (await Resolve()!.LoadBeatmapLevelDataAsync(levelId, System.Threading.CancellationToken.None)).beatmapLevelData; if(beatmapLevelData == null) { Log.Debug(" Announce: failed to load beatmap"); status = EntitlementsStatus.NotOwned; diff --git a/BeatUpClient/cs/Patches/8_Load.cs b/BeatUpClient/cs/Patches/8_Load.cs index c5e34ce..c03d2ed 100644 --- a/BeatUpClient/cs/Patches/8_Load.cs +++ b/BeatUpClient/cs/Patches/8_Load.cs @@ -20,13 +20,13 @@ enum MultiplayerBeatmapLoaderState { WaitingForCountdown } - static async System.Threading.Tasks.Task DownloadLevel(ShareTracker.DownloadPreview preview, System.Threading.CancellationToken cancellationToken) { + static async System.Threading.Tasks.Task DownloadLevel(ShareTracker.DownloadPreview preview, System.Threading.CancellationToken cancellationToken) { byte[]? data = await preview.Fetch(progress => { Net.SetLocalProgressUnreliable(new LoadProgress(LoadState.Downloading, progress)); }); if(!(data?.Length > 0)) { Log.Debug("Fetch failed"); - return new(true, null); + return LoadBeatmapLevelDataResult.Error; } Net.SetLocalProgress(new LoadProgress(LoadState.Loading, 0)); Log.Debug("Unzipping level"); @@ -35,20 +35,20 @@ enum MultiplayerBeatmapLoaderState { System.Threading.CancellationTokenSource? loaderCTS = Resolve()?._getBeatmapCancellationTokenSource; if(!haveMpCore && loaderCTS != null && cancellationToken == loaderCTS.Token && (int)HarmonyLib.AccessTools.Field(typeof(MultiplayerLevelLoader), "_loaderState").GetValue(Resolve()) == (int)MultiplayerBeatmapLoaderState.LoadingBeatmap) Resolve()?.SetIsEntitledToLevel(preview.levelID, /*(level == null) ? EntitlementsStatus.NotOwned :*/ EntitlementsStatus.Ok); - return new(level == null, level); // TODO: free zipped data to cut down on memory usage + return LoadBeatmapLevelDataResult.FromValue(level); // TODO: free zipped data to cut down on memory usage } [Detour(typeof(BeatmapLevelLoader), nameof(BeatmapLevelLoader.LoadBeatmapLevelDataAsync))] - static System.Threading.Tasks.Task BeatmapLevelLoader_LoadBeatmapLevelDataAsync(BeatmapLevelLoader self, BeatmapLevel beatmapLevel, System.Threading.CancellationToken cancellationToken) { + static System.Threading.Tasks.Task BeatmapLevelLoader_LoadBeatmapLevelDataAsync(BeatmapLevelLoader self, BeatmapLevel beatmapLevel, System.Threading.CancellationToken cancellationToken) { ShareTracker.DownloadPreview? preview = beatmapLevel as ShareTracker.DownloadPreview; if(preview == null) - return (System.Threading.Tasks.Task)Base(self, beatmapLevel, cancellationToken); + return (System.Threading.Tasks.Task)Base(self, beatmapLevel, cancellationToken); System.Threading.CancellationTokenSource? loaderCTS = Resolve()?._getBeatmapCancellationTokenSource; if(loaderCTS == null || cancellationToken != loaderCTS.Token) - return System.Threading.Tasks.Task.FromResult(new(true, null)); + return System.Threading.Tasks.Task.FromResult(LoadBeatmapLevelDataResult.Error); if(waitForMpCore) { // MultiplayerCore causes this method to run twice, discarding the first result waitForMpCore = false; - return System.Threading.Tasks.Task.FromResult(new(true, null)); + return System.Threading.Tasks.Task.FromResult(LoadBeatmapLevelDataResult.Error); } return DownloadLevel(preview, cancellationToken); } diff --git a/BeatUpClient/cs/Selector/NetworkConfig.cs b/BeatUpClient/cs/Selector/NetworkConfig.cs index 1aecc89..4e5ff8c 100644 --- a/BeatUpClient/cs/Selector/NetworkConfig.cs +++ b/BeatUpClient/cs/Selector/NetworkConfig.cs @@ -11,9 +11,10 @@ struct VanillaConfig : INetworkConfig { public string graphAccessToken {get;} public bool forceGameLift {get; set;} public ServiceEnvironment serviceEnvironment {get;} + public string appId {get;} public VanillaConfig(NetworkConfigSO from) => // Bypass all getters other mods might patch - (this.maxPartySize, this.discoveryPort, this.partyPort, this.multiplayerPort, this.masterServerEndPoint, this.multiplayerStatusUrl, this.quickPlaySetupUrl, this.graphUrl, this.graphAccessToken, this.forceGameLift, this.serviceEnvironment) = - (/*from._maxPartySize*/5, from._discoveryPort, from._partyPort, from._multiplayerPort, new DnsEndPoint(from._masterServerHostName, from._masterServerPort), from._multiplayerStatusUrl, from._quickPlaySetupUrl, from.graphUrl, from.graphAccessToken, from._forceGameLift, from._serviceEnvironment); + (this.maxPartySize, this.discoveryPort, this.partyPort, this.multiplayerPort, this.masterServerEndPoint, this.multiplayerStatusUrl, this.quickPlaySetupUrl, this.graphUrl, this.graphAccessToken, this.forceGameLift, this.serviceEnvironment, this.appId) = + (/*from._maxPartySize*/5, from._discoveryPort, from._partyPort, from._multiplayerPort, new DnsEndPoint(from._masterServerHostName, from._masterServerPort), from._multiplayerStatusUrl, from._quickPlaySetupUrl, from.graphUrl, from.graphAccessToken, from._forceGameLift, from._serviceEnvironment, from.appId); } static VanillaConfig officialConfig; diff --git a/BeatUpClient/cs/Sharing/LevelUnzip.cs b/BeatUpClient/cs/Sharing/LevelUnzip.cs index 41e4755..bd2c1f7 100644 --- a/BeatUpClient/cs/Sharing/LevelUnzip.cs +++ b/BeatUpClient/cs/Sharing/LevelUnzip.cs @@ -9,11 +9,12 @@ public struct DifficultyKey { public struct DifficultyData { public string lightshowFilename, beatmapFilename; }; + public string songName; public string? audioData; public System.Collections.Generic.Dictionary difficulties; UnityEngine.AudioClip songAudio; public string hash; - public static async System.Threading.Tasks.Task From(System.IO.Compression.ZipArchive archive, byte[] infoData, string songFilename, string audioDataFilename, System.Collections.Generic.IEnumerable> difficultyFilenames) { + public static async System.Threading.Tasks.Task From(System.IO.Compression.ZipArchive archive, byte[] infoData, string songName, string songFilename, string audioDataFilename, System.Collections.Generic.IEnumerable> difficultyFilenames) { using System.Security.Cryptography.SHA1 hash = System.Security.Cryptography.SHA1.Create(); hash.HashCore(infoData, 0, infoData.Length); string? ReadFile(string filename) { @@ -27,6 +28,7 @@ public struct DifficultyData { } System.IO.Compression.ZipArchiveEntry songFile = archive.GetEntry(songFilename) ?? throw new System.IO.FileNotFoundException("File not found in archive: " + songFilename); return new SharedBeatmapLevelData { + songName = songName, audioData = ReadFile(audioDataFilename), difficulties = new(difficultyFilenames.Select(difficulty => System.Collections.Generic.KeyValuePair.Create( difficulty.Key, (ReadFile(difficulty.Value.lightshowFilename), ReadFile(difficulty.Value.beatmapFilename))))), @@ -43,6 +45,7 @@ public struct DifficultyData { difficulties.TryGetValue(new DifficultyKey {characteristic = key.beatmapCharacteristic, difficulty = key.difficulty}, out var difficulty); return difficulty.lightshow; } + string IBeatmapLevelData.name => songName; System.Threading.Tasks.Task IBeatmapLevelData.GetAudioDataStringAsync() => System.Threading.Tasks.Task.FromResult(GetAudioDataString()); System.Threading.Tasks.Task IBeatmapLevelData.GetBeatmapStringAsync(in BeatmapKey key) => @@ -76,7 +79,7 @@ public struct DifficultyData { } static System.Threading.Tasks.Task UnzipV3(string infoText, byte[] infoData, System.IO.Compression.ZipArchive archive) { StandardLevelInfoSaveData info = StandardLevelInfoSaveData.DeserializeFromJSONString(infoText); - return SharedBeatmapLevelData.From(archive, infoData, info.songFilename, string.Empty, info.difficultyBeatmapSets + return SharedBeatmapLevelData.From(archive, infoData, info.songName, info.songFilename, string.Empty, info.difficultyBeatmapSets .SelectMany((StandardLevelInfoSaveData.DifficultyBeatmapSet set) => { BeatmapCharacteristicSO? characteristic = Resolve()! .GetBeatmapCharacteristicBySerializedName(set.beatmapCharacteristicName); @@ -96,7 +99,7 @@ public struct DifficultyData { static System.Threading.Tasks.Task UnzipV4(string infoText, byte[] infoData, System.IO.Compression.ZipArchive archive) { BeatmapLevelSaveDataVersion4.BeatmapLevelSaveData info = UnityEngine.JsonUtility.FromJson(infoText); - return SharedBeatmapLevelData.From(archive, infoData, info.audio.songFilename, info.audio.audioDataFilename, info.difficultyBeatmaps + return SharedBeatmapLevelData.From(archive, infoData, info.song.title, info.audio.songFilename, info.audio.audioDataFilename, info.difficultyBeatmaps .Select((BeatmapLevelSaveDataVersion4.BeatmapLevelSaveData.DifficultyBeatmap beatmap) => { BeatmapCharacteristicSO? characteristic = Resolve()! .GetBeatmapCharacteristicBySerializedName(beatmap.characteristic); diff --git a/BeatUpClient/makefile b/BeatUpClient/makefile index 026dbdf..726e821 100644 --- a/BeatUpClient/makefile +++ b/BeatUpClient/makefile @@ -1,5 +1,5 @@ #!/bin/make -VERSION := 0.8.2 +VERSION := 0.9.0 .SILENT: @@ -71,7 +71,7 @@ BeatUpClient.dll: $(CSFILES) .obj/MakeThingsPublic.exe makefile \"\$$schema\": \"https://raw.githubusercontent.com/bsmg/BSIPA-MetadataFileSchema/master/Schema.json\",\n\ \"author\": \"rcelyte\",\n\ \"description\": \"Tweaks and enhancements for enabling modded multiplayer\",\n\ - \"gameVersion\": \"1.34.5_7757266186\",\n\ + \"gameVersion\": \"1.35.0_8016709773\",\n\ \"dependsOn\": {\"BSIPA\": \"^4.3.0\"},\n\ \"conflictsWith\": {\"BeatTogether\": \"*\"},\n\ \"loadBefore\": [\"MultiplayerCore\"],\n\ @@ -90,6 +90,7 @@ BeatUpClient.dll: $(CSFILES) .obj/MakeThingsPublic.exe makefile -r:.obj/Refs/Libs/MonoMod.Utils.dll \ -r:.obj/Refs/Libs/Newtonsoft.Json.dll \ -r:.obj/Refs/Managed/BeatmapCore.dll \ + -r:.obj/Refs/Managed/BeatSaber.Init.dll \ -r:.obj/Refs/Managed/BGNetCore.dll \ -r:.obj/Refs/Managed/Colors.dll \ -r:.obj/Refs/Managed/GameplayCore.dll \ diff --git a/BeatUpClient/qpm.json b/BeatUpClient/qpm.json index bcc3ae2..fb0ae89 100644 --- a/BeatUpClient/qpm.json +++ b/BeatUpClient/qpm.json @@ -4,7 +4,7 @@ "info": { "name": "BeatUpClient", "id": "BeatUpClient", - "version": "0.8.2", + "version": "0.9.0", "url": null, "additionalData": {} }, diff --git a/README.md b/README.md index 8cdb1ef..5ab87a5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ============ *"beat saber gettin violent these days" -some Discord user* -A lightweight server to enable modded multiplayer in Beat Saber 1.19.0 and newer (Cross-version lobbies supported for 1.20.0<->1.31.1 or 1.32.0<->1.34.6). +A lightweight server to enable modded multiplayer in Beat Saber 1.19.0 and newer (Cross-version lobbies supported for 1.20.0<->1.31.1 or 1.32.0<->1.35.0). Ways to Join ------------ diff --git a/common/packets.txt b/common/packets.txt index a0f8542..fde2500 100644 --- a/common/packets.txt +++ b/common/packets.txt @@ -28,6 +28,7 @@ u8 GameVersion 1_34_4 1_34_5 1_34_6 + 1_35_0 n PacketContext u8 netVersion u8 protocolVersion diff --git a/makefile b/makefile index bc4667d..ece88ff 100644 --- a/makefile +++ b/makefile @@ -9,9 +9,8 @@ HOST := $(shell uname -m) NATIVE_CC ?= cc OBJDIR := .obj/$(shell $(CC) -dumpmachine) FILES := $(wildcard src/*.c src/*/*.c) -HTMLS := $(wildcard src/status/*.html) LIBS := libmbedtls.a libmbedx509.a libmbedcrypto.a -OBJS := $(FILES:%=$(OBJDIR)/%.o) $(HTMLS:%=$(OBJDIR)/%.s) $(LIBS:%=$(OBJDIR)/%) +OBJS := $(FILES:%=$(OBJDIR)/%.o) $(LIBS:%=$(OBJDIR)/%) DEPS := $(FILES:%=$(OBJDIR)/%.d) CFLAGS := -std=gnu2x -ffunction-sections -fdata-sections -isystem thirdparty/mbedtls/include -isystem thirdparty/enet/Source/Native -DMP_EXTENDED_ROUTING @@ -38,11 +37,6 @@ $(OBJDIR)/%.c.o: %.c src/packets.gen.h $(OBJDIR)/libs.mk thirdparty/mbedtls/.git @mkdir -p "$(@D)" $(CC) $(CFLAGS) -c "$<" -o "$@" -MMD -MP -$(OBJDIR)/%.html.s: %.html makefile # waiting for `#embed`... - @mkdir -p "$(@D)" - tr -d '\r\n\t' < "$<" > "$(basename $@)" - printf "\t.global $(basename $(notdir $<))_html\n$(basename $(notdir $<))_html:\n\t.incbin \"$(basename $@)\"\n\t.global $(basename $(notdir $<))_html_end\n$(basename $(notdir $<))_html_end:\n.section \".note.GNU-stack\"\n" > "$@" - $(OBJDIR)/libmbed%.a: thirdparty/mbedtls/.git @echo "[make $(notdir $@)]" mkdir -p "$@.build/" diff --git a/src/instance/instance.c b/src/instance/instance.c index 76ab41e..5d0a454 100644 --- a/src/instance/instance.c +++ b/src/instance/instance.c @@ -254,7 +254,7 @@ static void mapPool_init(const char *filename) { } fseek(file, 0, SEEK_END); size_t raw_len = (size_t)ftell(file); - fseek(file, 0, SEEK_SET); + rewind(file); uint8_t raw[3145728], *raw_end = &raw[raw_len]; if(raw_len > sizeof(raw)) { uprintf("Failed to read %s: File too large\n", filename); diff --git a/src/packets.gen.h b/src/packets.gen.h index b6f6dc4..9c76dd8 100644 --- a/src/packets.gen.h +++ b/src/packets.gen.h @@ -33,6 +33,7 @@ enum { GameVersion_1_34_4, GameVersion_1_34_5, GameVersion_1_34_6, + GameVersion_1_35_0, }; [[maybe_unused]] static const char *_reflect_GameVersion(GameVersion value) { switch(value) { @@ -65,6 +66,7 @@ enum { case GameVersion_1_34_4: return "1_34_4"; case GameVersion_1_34_5: return "1_34_5"; case GameVersion_1_34_6: return "1_34_6"; + case GameVersion_1_35_0: return "1_35_0"; default: return "???"; } } diff --git a/src/status/http.c b/src/status/http.c index 884c020..1715f4c 100644 --- a/src/status/http.c +++ b/src/status/http.c @@ -153,7 +153,7 @@ void HttpContext_respond(struct HttpContext *const self, const uint16_t code, co default: uprintf("unexpected HTTP response code: %hu\n", code); abort(); } char header[0x400] = {0}; - const uint32_t header_len = (uint32_t)snprintf(header, sizeof(header), "%s%s%s%zu%s%s%s", + const uint32_t header_len = (uint32_t)snprintf(header, lengthof(header), "%s%s%s%zu%s%s%s", "HTTP/1.1 ", codeText, "\r\n" "Connection: close\r\n" "Content-Length: ", data_len, "\r\n" diff --git a/src/status/internal.c b/src/status/internal.c index bdfe05e..d709971 100644 --- a/src/status/internal.c +++ b/src/status/internal.c @@ -13,6 +13,12 @@ static const uint64_t TEST_maintenanceStartTime = 0; static const uint64_t TEST_maintenanceEndTime = 0; static const char TEST_maintenanceMessage[] = ""; +__asm__( + ".global head_html;" + "head_html:" + ".incbin \"src/status/head.html\";" + ".global head_html_end;" + "head_html_end:"); extern const uint8_t head_html[], head_html_end[]; struct PacketBuffer { @@ -151,7 +157,7 @@ static void status_web_index(struct HttpContext *const http) { [6] = "1.19.0", [7] = "1.19.1", [8] = "1.20.0 ⬌ 1.31.1", - [9] = "1.32.0 ⬌ 1.34.6", + [9] = "1.32.0 ⬌ 1.35.0", }; char cover[(sizeof(entry.levelCover.data) * 4 + 3) / 3 + 53] = "\0style=background-image:url(data:image/jpeg;base64,"; if(entry.levelCover.length > 4 && memcmp(entry.levelCover.data, (const uint8_t[4]){0xff,0xd8,0xff,0xe0}, 4) == 0) { @@ -230,7 +236,7 @@ static UserAgent ProbeHeaders(const char *buf, const char *end, size_t *contentL static void status_status(struct HttpContext *http, bool isGame) { char msg[65536], *msg_end = msg; PUT("{\"minimum_app_version\":\"1.19.0%s\"" - ",\"maximumAppVersion\":\"1.34.6_🅱️\"" + ",\"maximumAppVersion\":\"1.35.0_🅱️\"" ",\"status\":%u", isGame ? "b2147483647" : STATUS_APPVER_POSTFIX, TEST_maintenanceStartTime != 0); if(TEST_maintenanceStartTime) { PUT(",\"maintenance_start_time\":%" PRIu64, TEST_maintenanceStartTime); @@ -302,6 +308,7 @@ static void status_graph(struct HttpContext *http, struct HttpRequest req, struc case '34.4': connectInfo.gameVersion = GameVersion_1_34_4; break; case '34.5': connectInfo.gameVersion = GameVersion_1_34_5; break; case '34.6': connectInfo.gameVersion = GameVersion_1_34_6; break; + case '35.0': connectInfo.gameVersion = GameVersion_1_35_0; break; default: { connectInfo.gameVersion = GameVersion_Unknown; uprintf("Unexpected game version: %.*s\n", version.length, version.data);