diff --git a/packages/tosu/src/api/types/v2.ts b/packages/tosu/src/api/types/v2.ts index 9f778ae5..40a50e05 100644 --- a/packages/tosu/src/api/types/v2.ts +++ b/packages/tosu/src/api/types/v2.ts @@ -1,5 +1,5 @@ -import { IRankedPlay } from '@/memory/types'; -import { CalculateMods } from '@/utils/osuMods.types'; +import { type IRankedPlay, type IRoom } from '@/memory/types'; +import { type CalculateMods } from '@/utils/osuMods.types'; export type ApiAnswer = TosuAPi | { error?: string }; export type ApiAnswerPrecise = TosuPreciseAnswer | { error?: string }; @@ -22,6 +22,7 @@ export interface TosuAPi { directPath: DirectPath; tourney: Tourney | undefined; rankedPlay: IRankedPlay | undefined; + room: IRoom | undefined; } export interface Game { diff --git a/packages/tosu/src/api/utils/buildResultV2.ts b/packages/tosu/src/api/utils/buildResultV2.ts index 4a57fcc5..99ef763f 100644 --- a/packages/tosu/src/api/utils/buildResultV2.ts +++ b/packages/tosu/src/api/utils/buildResultV2.ts @@ -102,7 +102,8 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => { resultScreen, beatmapPP, user, - rankedPlay + rankedPlay, + room } = osuInstance.getServices([ 'settings', 'bassDensity', @@ -112,7 +113,8 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => { 'resultScreen', 'beatmapPP', 'user', - 'rankedPlay' + 'rankedPlay', + 'room' ]); const currentMods = @@ -145,6 +147,7 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => { playCount: 0 // need counting }, rankedPlay: rankedPlay.info, + room: room.info, settings: { interfaceVisible: global.showInterface, replayUIVisible: global.isReplayUiHidden === false, diff --git a/packages/tosu/src/assets/offsets.json b/packages/tosu/src/assets/offsets.json index 950774e7..0a2477e5 100644 --- a/packages/tosu/src/assets/offsets.json +++ b/packages/tosu/src/assets/offsets.json @@ -1 +1 @@ -{"OsuVersion":"2026.624.0","osu.Game.OsuGame":{"osuLogo":1584,"k__BackingField":1544,"k__BackingField":1480,"channelManager":1320,"sentryLogger":1512,"chatOverlay":1312},"osu.Framework.Game":{"k__BackingField":824},"osu.Game.OsuGameBase":{"k__BackingField":1080,"k__BackingField":1192,"k__BackingField":1024,"k__BackingField":1200,"k__BackingField":1168,"k__BackingField":992,"beatmapClock":1232,"k__BackingField":1088,"k__BackingField":1104,"k__BackingField":1040,"k__BackingField":976,"k__BackingField":984,"rulesetConfigCache":1176,"realm":1216},"osu.Game.Screens.Select.SongSelect":{"k__BackingField":1032},"osu.Game.Screens.Play.SubmittingPlayer":{"k__BackingField":1264,"k__BackingField":1272},"osu.Game.Screens.Play.PlayerLoader":{"osuLogo":1176,"k__BackingField":1168},"osu.Game.Screens.Play.SpectatorPlayer":{"score":1272,"k__BackingField":1264},"osu.Game.Beatmaps.FramedBeatmapClock":{"finalClockSource":528,"decoupledTrack":552},"osu.Framework.Timing.DecouplingFramedClock":{"k__BackingField":24},"osu.Framework.Audio.Track.Track":{"length":72},"osu.Framework.Timing.FramedClock":{"k__BackingField":48},"osu.Game.Screens.OsuScreen":{"k__BackingField":896},"osu.Game.Screens.Ranking.SoloResultsScreen":{"k__BackingField":1040,"SelectedScore":920},"osu.Game.Screens.Edit.Editor":{"k__BackingField":960,"k__BackingField":1096},"osu.Game.Screens.OnlinePlay.OnlinePlayScreen":{"k__BackingField":960},"osu.Game.Screens.OnlinePlay.Multiplayer.Multiplayer":{"k__BackingField":976},"osu.Game.Online.Multiplayer.MultiplayerRoom":{"RoomID":56,"k__BackingField":68,"k__BackingField":32},"osu.Game.Screens.Spectate.SpectatorScreen":{"k__BackingField":944,"gameplayStates":992},"osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.MultiSpectatorScreen":{"k__BackingField":1032},"osu.Game.Online.Multiplayer.OnlineMultiplayerClient":{"k__BackingField":904},"osu.Game.Online.Multiplayer.MultiplayerClient":{"room":800},"osu.Game.Overlays.ChatOverlay":{"State":808},"osu.Game.Screens.Menu.OsuLogo":{"visualizer":928},"osu.Game.Screens.Menu.LogoVisualisation":{"frequencyAmplitudes":504},"osu.Game.Screens.Play.Player":{"k__BackingField":1008,"k__BackingField":1000,"k__BackingField":1152,"k__BackingField":1096,"k__BackingField":1104,"k__BackingField":1120,"k__BackingField":1112,"dependencies":1168},"osu.Framework.Screens.ScreenStack":{"stack":800},"osu.Game.Rulesets.RulesetConfigCache":{"configCache":520},"osu.Game.Online.Chat.ExternalLinkOpener":{"k__BackingField":536},"osu.Game.Online.API.APIAccess":{"localUserState":864,"game":784},"osu.Game.Online.API.LocalUserState":{"localUser":512},"osu.Desktop.OsuGameDesktop":{"AvailableMods":1128,"SelectedMods":1120,"Ruleset":1112},"osu.Game.Scoring.ScoreInfo":{"k__BackingField":184,"k__BackingField":192,"k__BackingField":80,"k__BackingField":120,"k__BackingField":72,"k__BackingField":160,"k__BackingField":204,"k__BackingField":212,"k__BackingField":220,"k__BackingField":48,"k__BackingField":176,"k__BackingField":256,"statistics":128,"maximumStatistics":136,"user":112},"osu.Framework.Platform.Storage":{"k__BackingField":8},"osu.Game.IO.WrappedStorage":{"k__BackingField":16},"osu.Game.Beatmaps.WorkingBeatmapCache+BeatmapManagerWorkingBeatmap":{"BeatmapInfo":8,"BeatmapSetInfo":16},"osu.Game.Models.RealmUser":{"k__BackingField":24,"k__BackingField":40},"osu.Game.Rulesets.RulesetInfo":{"k__BackingField":48},"osu.Game.Screens.Play.SoloPlayer":{"RestartCount":916},"osu.Game.Rulesets.Osu.Scoring.OsuScoreProcessor":{"Combo":592},"osu.Game.Rulesets.Scoring.ScoreProcessor":{"hitEvents":656},"osu.Game.Online.API.Requests.Responses.APIMe":{"PlayMode":136},"osu.Game.Online.API.Requests.Responses.APIUser":{"k__BackingField":248,"k__BackingField":8,"countryCodeString":32,"statistics":160,"MatchmakingStatistics":240},"osu.Game.Online.API.Requests.Responses.APIUserMatchmakingStatistics":{"k__BackingField":24,"k__BackingField":44,"k__BackingField":28,"k__BackingField":36,"k__BackingField":40},"osu.Game.Online.Chat.ChannelManager":{"joinedChannels":800},"osu.Game.Online.Chat.Channel":{"Id":96,"Messages":16},"osu.Game.Online.Chat.Message":{"Timestamp":88,"Content":8,"Sender":16},"osu.Game.Users.UserStatistics":{"RankedScore":32,"GlobalRank":84,"PlayCount":56,"Accuracy":40,"Level":76,"PP":112},"osu.Game.Rulesets.Osu.Scoring.OsuHealthProcessor":{"Health":560},"osu.Game.Screens.Play.HUDOverlay":{"InputCountController":832,"k__BackingField":880},"osu.Game.Screens.Play.HUD.InputCountController":{"triggers":512},"osu.Game.Rulesets.UI.DrawableRuleset":{"k__BackingField":808},"osu.Game.Beatmaps.BeatmapInfo":{"k__BackingField":140,"k__BackingField":88,"k__BackingField":136,"k__BackingField":48,"k__BackingField":40,"k__BackingField":24,"k__BackingField":148,"k__BackingField":80},"osu.Game.Beatmaps.BeatmapSetInfo":{"k__BackingField":48},"osu.Game.Beatmaps.BeatmapMetadata":{"k__BackingField":24,"<TitleUnicode>k__BackingField":32,"<Artist>k__BackingField":40,"<ArtistUnicode>k__BackingField":48,"<Author>k__BackingField":56,"<Source>k__BackingField":64,"<Tags>k__BackingField":72,"<UserTags>k__BackingField":80,"<PreviewTime>k__BackingField":104,"<AudioFile>k__BackingField":88,"<BackgroundFile>k__BackingField":96},"osu.Game.Beatmaps.BeatmapDifficulty":{"<DrainRate>k__BackingField":40,"<CircleSize>k__BackingField":44,"<OverallDifficulty>k__BackingField":48,"<ApproachRate>k__BackingField":52,"<SliderMultiplier>k__BackingField":24,"<SliderTickRate>k__BackingField":32},"osu.Game.Online.Multiplayer.MultiplayerRoomUser":{"UserID":40,"<State>k__BackingField":44,"<BeatmapAvailability>k__BackingField":8,"<Mods>k__BackingField":16,"<MatchState>k__BackingField":24,"RulesetId":56,"BeatmapId":64,"<User>k__BackingField":32},"osu.Game.Online.Multiplayer.MatchTypes.RankedPlay.RankedPlayRoomState":{"<Stage>k__BackingField":32,"<CurrentRound>k__BackingField":36,"<DamageMultiplier>k__BackingField":16,"<Users>k__BackingField":8,"<ActiveUserId>k__BackingField":40,"<StarRating>k__BackingField":24,"<WinningUserId>k__BackingField":48},"osu.Game.Online.Multiplayer.MatchTypes.RankedPlay.RankedPlayUserInfo":{"<Rating>k__BackingField":32,"<Life>k__BackingField":36,"<Hand>k__BackingField":8,"<RatingAfter>k__BackingField":40,"DamageInfo":16,"<RoundsWon>k__BackingField":44,"<DamageMultiplier>k__BackingField":24},"osu.Game.Skinning.SkinManager":{"CurrentSkin":80},"osu.Game.Skinning.Skin":{"<Name>k__BackingField":64},"osu.Framework.Platform.GameHost":{"<inputConfig>k__BackingField":48},"osu.Framework.Configuration.InputConfigManager":{"<InputHandlers>k__BackingField":32},"osu.Framework.Input.Handlers.Tablet.OpenTabletDriverHandler":{"<AreaOffset>k__BackingField":72,"<AreaSize>k__BackingField":80,"<Rotation>k__BackingField":104,"<PressureThreshold>k__BackingField":112},"osu.Framework.Input.Handlers.Mouse.MouseHandler":{"<UseRelativeMode>k__BackingField":40},"osu.Game.Utils.SentryLogger":{"sentrySession":16},"Sentry.SentrySdk+DisposeHandle":{"_localHub":8},"Sentry.Internal.Hub":{"_options":32},"Sentry.SentryOptions":{"<Release>k__BackingField":176},"GameBaseVtable":7765317648384} \ No newline at end of file +{"OsuVersion":"2026.624.0","osu.Game.OsuGame":{"osuLogo":1584,"<ScreenStack>k__BackingField":1544,"<frameworkConfig>k__BackingField":1480,"channelManager":1320,"sentryLogger":1512,"chatOverlay":1312},"osu.Framework.Game":{"<Host>k__BackingField":824},"osu.Game.OsuGameBase":{"<API>k__BackingField":1080,"<SpectatorClient>k__BackingField":1192,"<ScoreManager>k__BackingField":1024,"<MultiplayerClient>k__BackingField":1200,"<LeaderboardManager>k__BackingField":1168,"<SessionStatics>k__BackingField":992,"beatmapClock":1232,"<Storage>k__BackingField":1088,"<Beatmap>k__BackingField":1104,"<SkinManager>k__BackingField":1040,"<VersionHash>k__BackingField":976,"<LocalConfig>k__BackingField":984,"rulesetConfigCache":1176,"realm":1216},"osu.Game.Screens.Select.SongSelect":{"<game>k__BackingField":1032},"osu.Game.Screens.Play.SubmittingPlayer":{"<api>k__BackingField":1264,"<spectatorClient>k__BackingField":1272},"osu.Game.Screens.Play.PlayerLoader":{"osuLogo":1176,"<leaderboardManager>k__BackingField":1168},"osu.Game.Screens.Play.SpectatorPlayer":{"score":1272,"<SpectatorClient>k__BackingField":1264},"osu.Game.Beatmaps.FramedBeatmapClock":{"finalClockSource":528,"decoupledTrack":552},"osu.Framework.Timing.DecouplingFramedClock":{"<Source>k__BackingField":24},"osu.Framework.Audio.Track.Track":{"length":72},"osu.Framework.Timing.FramedClock":{"<CurrentTime>k__BackingField":48},"osu.Game.Screens.OsuScreen":{"<logo>k__BackingField":896},"osu.Game.Screens.Ranking.SoloResultsScreen":{"<api>k__BackingField":1040,"SelectedScore":920},"osu.Game.Screens.Edit.Editor":{"<realm>k__BackingField":960,"<api>k__BackingField":1096},"osu.Game.Screens.OnlinePlay.OnlinePlayScreen":{"<API>k__BackingField":960},"osu.Game.Screens.OnlinePlay.Multiplayer.Multiplayer":{"<client>k__BackingField":976},"osu.Game.Online.Multiplayer.MultiplayerRoom":{"RoomID":56,"<ChannelID>k__BackingField":68,"<MatchState>k__BackingField":32},"osu.Game.Screens.Spectate.SpectatorScreen":{"<spectatorClient>k__BackingField":944,"gameplayStates":992},"osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.MultiSpectatorScreen":{"<multiplayerClient>k__BackingField":1032},"osu.Game.Online.Multiplayer.OnlineMultiplayerClient":{"<IsConnected>k__BackingField":904},"osu.Game.Online.Multiplayer.MultiplayerClient":{"room":800},"osu.Game.Overlays.ChatOverlay":{"State":808},"osu.Game.Screens.Menu.OsuLogo":{"visualizer":928},"osu.Game.Screens.Menu.LogoVisualisation":{"frequencyAmplitudes":504},"osu.Game.Screens.Play.Player":{"<api>k__BackingField":1008,"<scoreManager>k__BackingField":1000,"<Score>k__BackingField":1152,"<ScoreProcessor>k__BackingField":1096,"<HealthProcessor>k__BackingField":1104,"<HUDOverlay>k__BackingField":1120,"<DrawableRuleset>k__BackingField":1112,"dependencies":1168},"osu.Framework.Screens.ScreenStack":{"stack":800},"osu.Game.Rulesets.RulesetConfigCache":{"configCache":520},"osu.Game.Online.Chat.ExternalLinkOpener":{"<api>k__BackingField":536},"osu.Game.Online.API.APIAccess":{"localUserState":864,"game":784},"osu.Game.Online.API.LocalUserState":{"localUser":512},"osu.Desktop.OsuGameDesktop":{"AvailableMods":1128,"SelectedMods":1120,"Ruleset":1112},"osu.Game.Scoring.ScoreInfo":{"<OnlineID>k__BackingField":184,"<LegacyOnlineID>k__BackingField":192,"<ModsJson>k__BackingField":80,"<HitEvents>k__BackingField":120,"<RealmUser>k__BackingField":72,"<TotalScore>k__BackingField":160,"<MaxCombo>k__BackingField":204,"<Combo>k__BackingField":212,"<Passed>k__BackingField":220,"<Ruleset>k__BackingField":48,"<Accuracy>k__BackingField":176,"<Date>k__BackingField":256,"statistics":128,"maximumStatistics":136,"user":112},"osu.Framework.Platform.Storage":{"<BasePath>k__BackingField":8},"osu.Game.IO.WrappedStorage":{"<UnderlyingStorage>k__BackingField":16},"osu.Game.Beatmaps.WorkingBeatmapCache+BeatmapManagerWorkingBeatmap":{"BeatmapInfo":8,"BeatmapSetInfo":16},"osu.Game.Models.RealmUser":{"<Username>k__BackingField":24,"<OnlineID>k__BackingField":40},"osu.Game.Rulesets.RulesetInfo":{"<OnlineID>k__BackingField":48},"osu.Game.Screens.Play.SoloPlayer":{"RestartCount":916},"osu.Game.Rulesets.Osu.Scoring.OsuScoreProcessor":{"Combo":592},"osu.Game.Rulesets.Scoring.ScoreProcessor":{"hitEvents":656},"osu.Game.Online.API.Requests.Responses.APIMe":{"PlayMode":136},"osu.Game.Online.API.Requests.Responses.APIUser":{"<Id>k__BackingField":248,"<Username>k__BackingField":8,"AvatarUrl":56,"countryCodeString":32,"statistics":160,"MatchmakingStatistics":240},"osu.Game.Online.API.Requests.Responses.APIUserMatchmakingStatistics":{"<Rating>k__BackingField":24,"<Rank>k__BackingField":44,"<Plays>k__BackingField":28,"<FirstPlacements>k__BackingField":36,"<IsRatingProvisional>k__BackingField":40},"osu.Game.Online.Chat.ChannelManager":{"joinedChannels":800},"osu.Game.Online.Chat.Channel":{"Id":96,"Messages":16},"osu.Game.Online.Chat.Message":{"Timestamp":88,"Content":8,"Sender":16},"osu.Game.Users.UserStatistics":{"RankedScore":32,"GlobalRank":84,"PlayCount":56,"Accuracy":40,"Level":76,"PP":112},"osu.Game.Rulesets.Osu.Scoring.OsuHealthProcessor":{"Health":560},"osu.Game.Screens.Play.HUDOverlay":{"InputCountController":832,"<ShowHud>k__BackingField":880},"osu.Game.Screens.Play.HUD.InputCountController":{"triggers":512},"osu.Game.Rulesets.UI.DrawableRuleset":{"<ReplayScore>k__BackingField":808},"osu.Game.Beatmaps.BeatmapInfo":{"<OnlineID>k__BackingField":140,"<MD5Hash>k__BackingField":88,"<StatusInt>k__BackingField":136,"<Metadata>k__BackingField":48,"<Difficulty>k__BackingField":40,"<DifficultyName>k__BackingField":24,"<TotalObjectCount>k__BackingField":148,"<Hash>k__BackingField":80},"osu.Game.Beatmaps.BeatmapSetInfo":{"<OnlineID>k__BackingField":48},"osu.Game.Beatmaps.BeatmapMetadata":{"<Title>k__BackingField":24,"<TitleUnicode>k__BackingField":32,"<Artist>k__BackingField":40,"<ArtistUnicode>k__BackingField":48,"<Author>k__BackingField":56,"<Source>k__BackingField":64,"<Tags>k__BackingField":72,"<UserTags>k__BackingField":80,"<PreviewTime>k__BackingField":104,"<AudioFile>k__BackingField":88,"<BackgroundFile>k__BackingField":96},"osu.Game.Beatmaps.BeatmapDifficulty":{"<DrainRate>k__BackingField":40,"<CircleSize>k__BackingField":44,"<OverallDifficulty>k__BackingField":48,"<ApproachRate>k__BackingField":52,"<SliderMultiplier>k__BackingField":24,"<SliderTickRate>k__BackingField":32},"osu.Game.Online.Multiplayer.MultiplayerRoomUser":{"UserID":40,"<State>k__BackingField":44,"<BeatmapAvailability>k__BackingField":8,"<Mods>k__BackingField":16,"<MatchState>k__BackingField":24,"RulesetId":56,"BeatmapId":64,"<User>k__BackingField":32},"osu.Game.Online.Multiplayer.MatchTypes.RankedPlay.RankedPlayRoomState":{"<Stage>k__BackingField":32,"<CurrentRound>k__BackingField":36,"<DamageMultiplier>k__BackingField":16,"<Users>k__BackingField":8,"<ActiveUserId>k__BackingField":40,"<StarRating>k__BackingField":24,"<WinningUserId>k__BackingField":48},"osu.Game.Online.Multiplayer.MatchTypes.RankedPlay.RankedPlayUserInfo":{"<Rating>k__BackingField":32,"<Life>k__BackingField":36,"<Hand>k__BackingField":8,"<RatingAfter>k__BackingField":40,"DamageInfo":16,"<RoundsWon>k__BackingField":44,"<DamageMultiplier>k__BackingField":24},"osu.Game.Skinning.SkinManager":{"CurrentSkin":80},"osu.Game.Skinning.Skin":{"<Name>k__BackingField":64},"osu.Framework.Platform.GameHost":{"<inputConfig>k__BackingField":48},"osu.Framework.Configuration.InputConfigManager":{"<InputHandlers>k__BackingField":32},"osu.Framework.Input.Handlers.Tablet.OpenTabletDriverHandler":{"<AreaOffset>k__BackingField":72,"<AreaSize>k__BackingField":80,"<Rotation>k__BackingField":104,"<PressureThreshold>k__BackingField":112},"osu.Framework.Input.Handlers.Mouse.MouseHandler":{"<UseRelativeMode>k__BackingField":40},"osu.Game.Utils.SentryLogger":{"sentrySession":16},"Sentry.SentrySdk+DisposeHandle":{"_localHub":8},"Sentry.Internal.Hub":{"_options":32},"Sentry.SentryOptions":{"<Release>k__BackingField":176},"GameBaseVtable":7765317648384} \ No newline at end of file diff --git a/packages/tosu/src/instances/index.ts b/packages/tosu/src/instances/index.ts index b7ceca8b..37f55c77 100644 --- a/packages/tosu/src/instances/index.ts +++ b/packages/tosu/src/instances/index.ts @@ -16,6 +16,7 @@ import { LazerMultiSpectating } from '@/states/lazerMultiSpectating'; import { Menu } from '@/states/menu'; import { RankedPlay } from '@/states/rankedplay'; import { ResultScreen } from '@/states/resultScreen'; +import { Room } from '@/states/room'; import { Settings } from '@/states/settings'; import { TourneyManager } from '@/states/tourney'; import { User } from '@/states/user'; @@ -34,6 +35,7 @@ export interface DataRepoList { user: User; lazerMultiSpectating: LazerMultiSpectating; rankedPlay: RankedPlay; + room: Room; } export type OsuVersion = `b${number}${'tourney' | 'cuttingedge' | ''}`; @@ -89,6 +91,7 @@ export abstract class AbstractInstance { this.set('user', new User(this)); this.set('lazerMultiSpectating', new LazerMultiSpectating(this)); this.set('rankedPlay', new RankedPlay(this)); + this.set('room', new Room(this)); this.watchProcessHealth = this.watchProcessHealth.bind(this); this.preciseDataLoop = this.preciseDataLoop.bind(this); diff --git a/packages/tosu/src/instances/lazerInstance.ts b/packages/tosu/src/instances/lazerInstance.ts index d5cb8229..44e47b4d 100644 --- a/packages/tosu/src/instances/lazerInstance.ts +++ b/packages/tosu/src/instances/lazerInstance.ts @@ -166,7 +166,8 @@ export class LazerInstance extends AbstractInstance { settings, user, lazerMultiSpectating, - rankedPlay + rankedPlay, + room } = this.getServices([ 'global', 'menu', @@ -178,7 +179,8 @@ export class LazerInstance extends AbstractInstance { 'tourneyManager', 'user', 'lazerMultiSpectating', - 'rankedPlay' + 'rankedPlay', + 'room' ]); while (!this.isDestroyed) { @@ -212,6 +214,7 @@ export class LazerInstance extends AbstractInstance { } rankedPlay.updateState(); + room.updateState(); settings.updateState(); diff --git a/packages/tosu/src/memory/index.ts b/packages/tosu/src/memory/index.ts index d2a13c72..9f5ae668 100644 --- a/packages/tosu/src/memory/index.ts +++ b/packages/tosu/src/memory/index.ts @@ -14,6 +14,7 @@ import type { IMenu, IRankedPlay, IResultScreen, + IRoom, ISettings, ITourney, ITourneyChat, @@ -60,6 +61,7 @@ export abstract class AbstractMemory<M extends Record<string, number>> { abstract tourneyUser(): ITourneyUser; abstract leaderboard(mode: number): ILeaderboard; abstract rankedPlay(): IRankedPlay; + abstract room(): IRoom; checkIsBasesValid(): boolean { Object.entries(this.patterns).map((entry) => diff --git a/packages/tosu/src/memory/lazer.ts b/packages/tosu/src/memory/lazer.ts index 60dfcf4a..abe265e3 100644 --- a/packages/tosu/src/memory/lazer.ts +++ b/packages/tosu/src/memory/lazer.ts @@ -36,6 +36,8 @@ import type { IMenu, IRankedPlay, IResultScreen, + IRoom, + IRoomUser, IScore, ISettings, ITourney, @@ -267,6 +269,7 @@ export interface Offsets { 'osu.Game.Online.API.Requests.Responses.APIUser': { '<Id>k__BackingField': number; '<Username>k__BackingField': number; + AvatarUrl: number; countryCodeString: number; statistics: number; MatchmakingStatistics: number; @@ -2002,6 +2005,12 @@ export class LazerMemory extends AbstractMemory<LazerPatternData> { 'osu.Game.Online.API.Requests.Responses.APIUser' ]['<Username>k__BackingField'] ), + avatarUrl: this.process.readSharpStringPtr( + user + + this.offsets[ + 'osu.Game.Online.API.Requests.Responses.APIUser' + ].AvatarUrl + ), accuracy, rankedScore, level, @@ -3770,6 +3779,77 @@ export class LazerMemory extends AbstractMemory<LazerPatternData> { }; } + room(): IRoom { + const multiplayerClient = this.multiplayerClient(); + + if (!multiplayerClient) { + return 'not-ready'; + } + + const room = this.process.readIntPtr( + multiplayerClient + + this.offsets['osu.Game.Online.Multiplayer.MultiplayerClient'] + .room + ); + + if (!room) { + return 'not-ready'; + } + + const roomId = this.process.readLong( + room + + this.offsets['osu.Game.Online.Multiplayer.MultiplayerRoom'] + .RoomID + ); + + const multiplayerUsers = this.process.readIntPtr(room + 0x10); + const multiplayerUsersItems = this.process.readIntPtr( + multiplayerUsers + 0x8 + ); + const multiplayerUsersCount = this.process.readInt( + multiplayerUsers + 0x10 + ); + + const users: IRoomUser[] = []; + + for (let i = 0; i < multiplayerUsersCount; i++) { + const current = this.process.readIntPtr( + multiplayerUsersItems + 0x10 + 0x8 * i + ); + + const userId = this.process.readInt( + current + + this.offsets[ + 'osu.Game.Online.Multiplayer.MultiplayerRoomUser' + ].UserID + ); + + const apiUser = this.process.readIntPtr( + current + + this.offsets[ + 'osu.Game.Online.Multiplayer.MultiplayerRoomUser' + ]['<User>k__BackingField'] + ); + + // during my tests, this field was never null. but just in case, lets skip a user if its null + if (apiUser) { + const user = this.readUser(apiUser); + + users.push({ + id: userId, + username: user.name, + countryCode: user.countryCode, + avatarUrl: user.avatarUrl + }); + } + } + + return { + roomID: roomId, + users + }; + } + settings(): ISettings { const values = this.osuConfig(); values['client.version'] = (this.game as LazerInstance).version; diff --git a/packages/tosu/src/memory/stable.ts b/packages/tosu/src/memory/stable.ts index 8ebb4dc0..371babf6 100644 --- a/packages/tosu/src/memory/stable.ts +++ b/packages/tosu/src/memory/stable.ts @@ -24,6 +24,7 @@ import type { IOffsets, IRankedPlay, IResultScreen, + IRoom, ISettings, ITourney, ITourneyChat, @@ -1451,4 +1452,8 @@ export class StableMemory extends AbstractMemory<OsuPatternData> { rankedPlay(): IRankedPlay | 'not-ready' { throw new Error('Stable does not have Ranked Play.'); } + + room(): IRoom { + return 'not-ready'; + } } diff --git a/packages/tosu/src/memory/types.ts b/packages/tosu/src/memory/types.ts index 2d3ea4d8..5c529418 100644 --- a/packages/tosu/src/memory/types.ts +++ b/packages/tosu/src/memory/types.ts @@ -1,3 +1,4 @@ +import { CountryCodes } from '@tosu/common'; import type { RankedPlayStage } from '@tosu/common/enums/osu'; import type { ITourneyManagerChatItem } from '@/states/tourney'; @@ -231,4 +232,18 @@ export type IRankedPlay = } | string; +export type IRoomUser = { + id: number; + username: string; + countryCode: CountryCodes; + avatarUrl: string | undefined; +}; + +export type IRoom = + | { + roomID: number; + users: IRoomUser[]; + } + | string; + export type ISettings = SettingsObject | Error; diff --git a/packages/tosu/src/states/room.ts b/packages/tosu/src/states/room.ts new file mode 100644 index 00000000..b17f0af6 --- /dev/null +++ b/packages/tosu/src/states/room.ts @@ -0,0 +1,32 @@ +import { ClientType, measureTime, wLogger } from '@tosu/common'; + +import { type IRoom } from '@/memory/types'; +import { AbstractState } from '@/states/index'; + +export class Room extends AbstractState { + info: IRoom | undefined = undefined; + + @measureTime + updateState() { + try { + const room = this.game.memory.room(); + this.info = room !== 'not-ready' ? room : undefined; + } catch (exc) { + this.info = undefined; + + this.game.reportError( + 'Room updatestate', + 10, + ClientType[this.game.client], + this.game.pid, + `Room updatestate`, + (exc as any).message + ); + wLogger.debug( + `%${ClientType[this.game.client]}%`, + `Error updating Room state:`, + exc + ); + } + } +}