22using Bunkum . Core ;
33using Bunkum . Core . Endpoints ;
44using Bunkum . Core . RateLimit ;
5+ using Bunkum . Core . Responses ;
56using Bunkum . Core . Storage ;
67using Bunkum . Listener . Protocol ;
78using Bunkum . Protocols . Http ;
89using Refresh . Common . Constants ;
910using Refresh . Core . Authentication . Permission ;
1011using Refresh . Core . Configuration ;
12+ using Refresh . Core . Helpers ;
13+ using Refresh . Core . Importing ;
1114using Refresh . Core . RateLimits . Users ;
1215using Refresh . Core . Services ;
16+ using Refresh . Core . Types . Assets . Validation ;
1317using Refresh . Core . Types . Data ;
1418using Refresh . Database ;
19+ using Refresh . Database . Models . Assets ;
1520using Refresh . Database . Models . Authentication ;
1621using Refresh . Database . Models . Users ;
1722using Refresh . Interfaces . Game . Endpoints . DataTypes . Response ;
@@ -73,7 +78,8 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont
7378 [ NullStatusCode ( BadRequest ) ]
7479 [ RateLimitSettings ( UserModificationEndpointLimits . TimeoutDuration , UserModificationEndpointLimits . GameRequestAmount ,
7580 UserModificationEndpointLimits . BlockDuration , UserModificationEndpointLimits . GameRequestBucket ) ]
76- public string ? UpdateUser ( RequestContext context , DataContext dataContext , GameUser user , string body , GuidCheckerService guidChecker )
81+ public Response UpdateUser ( RequestContext context , DataContext dataContext , GameUser user , string body , GuidCheckerService guidChecker ,
82+ AssetImporter importer , AipiService aipi )
7783 {
7884 SerializedUpdateData ? data = null ;
7985
@@ -83,7 +89,7 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont
8389 {
8490 XmlSerializer profileSerializer = new ( typeof ( SerializedUpdateDataProfile ) ) ;
8591 if ( profileSerializer . Deserialize ( new StringReader ( body ) ) is not SerializedUpdateDataProfile profileData )
86- return null ;
92+ return BadRequest ;
8793
8894 data ??= profileData ;
8995 }
@@ -96,7 +102,7 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont
96102 {
97103 XmlSerializer planetSerializer = new ( typeof ( SerializedUpdateDataPlanets ) ) ;
98104 if ( planetSerializer . Deserialize ( new StringReader ( body ) ) is not SerializedUpdateDataPlanets planetsData )
99- return null ;
105+ return BadRequest ;
100106
101107 data ??= planetsData ;
102108 }
@@ -108,47 +114,85 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont
108114 if ( data == null )
109115 {
110116 dataContext . Database . AddErrorNotification ( "Profile update failed" , "Your profile failed to update because the data could not be read." , user ) ;
111- return null ;
117+ return BadRequest ;
112118 }
113119
114120 if ( data . IconHash != null )
115121 {
116- //If the icon is a GUID
117- if ( data . IconHash . StartsWith ( 'g' ) )
118- {
119- //Parse out the GUID
120- long guid = long . Parse ( data . IconHash . AsSpan ( ) [ 1 ..] ) ;
121-
122- //If its not a valid GUID, block the request
123- if ( data . IconHash . StartsWith ( 'g' ) && ! guidChecker . IsTextureGuid ( dataContext . Game , guid ) )
124- {
125- dataContext . Database . AddErrorNotification ( "Profile update failed" , "Your avatar failed to update because the asset was an invalid GUID." , user ) ;
126- return null ;
127- }
128- }
129- else if ( data . IconHash . IsBlankHash ( ) )
122+ ValidatedAssetResult iconResult = ResourceValidationHelper . ValidateReference ( new ( data . IconHash , dataContext , importer , aipi )
130123 {
131- // Force hash to be a specific value if the icon is supposed to be reset/default to a PSN avatar,
132- // to not allow uncontrolled values which would still count as blank/empty hash (e.g. unlimited whitespaces)
133- data . IconHash = "0" ;
134- }
135- else if ( ! dataContext . DataStore . ExistsInStore ( data . IconHash ) )
124+ MustBeTexture = true ,
125+ AssetContextTypeStr = "avatar" ,
126+ } , context . Logger ) ;
127+ data . IconHash = iconResult . NewAssetRef ;
128+
129+ if ( iconResult . Status != OK )
136130 {
137- //If the asset does not exist on the server, block the request
138- dataContext . Database . AddErrorNotification ( "Profile update failed" , "Your avatar failed to update because the asset was missing on the server." , user ) ;
139- return null ;
131+ if ( iconResult . ErrorMessage != null ) dataContext . Database . AddErrorNotification ( "Profile update failed" , iconResult . ErrorMessage , user ) ;
132+ return iconResult . Status ;
140133 }
141134 }
142135
143- if ( data . LevelLocations != null && data . LevelLocations . Count > 0 )
136+ AssetValidationParameters faceParams = new ( null ! , dataContext , importer , aipi )
144137 {
145- dataContext . Database . UpdateLevelLocations ( data . LevelLocations , user ) ;
138+ MayBeBlank = false ,
139+ MayBeGuid = false ,
140+ MustBeTexture = true ,
141+ AssetContextTypeStr = "image" ,
142+ } ;
143+
144+ if ( data . YayFaceHash != null )
145+ {
146+ faceParams . AssetRef = data . YayFaceHash ;
147+ ValidatedAssetResult yayResult = ResourceValidationHelper . ValidateReference ( faceParams , context . Logger ) ;
148+ data . YayFaceHash = yayResult . NewAssetRef ;
149+
150+ if ( yayResult . Status != OK ) return yayResult . Status ; // no need to notify, these are always updated in the background
146151 }
147-
148- if ( ! string . IsNullOrEmpty ( data . PlanetsHash ) && data . PlanetsHash != "0" /* Empty planets */ && ! dataContext . DataStore . ExistsInStore ( data . PlanetsHash ) )
152+
153+ if ( data . MehFaceHash != null )
149154 {
150- dataContext . Database . AddErrorNotification ( "Profile update failed" , "Your planets failed to update because the asset was missing on the server." , user ) ;
151- return null ;
155+ faceParams . AssetRef = data . MehFaceHash ;
156+ ValidatedAssetResult mehResult = ResourceValidationHelper . ValidateReference ( faceParams , context . Logger ) ;
157+ data . MehFaceHash = mehResult . NewAssetRef ;
158+
159+ if ( mehResult . Status != OK ) return mehResult . Status ;
160+ }
161+
162+ if ( data . BooFaceHash != null )
163+ {
164+ faceParams . AssetRef = data . BooFaceHash ;
165+ ValidatedAssetResult booResult = ResourceValidationHelper . ValidateReference ( faceParams , context . Logger ) ;
166+ data . BooFaceHash = booResult . NewAssetRef ;
167+
168+ if ( booResult . Status != OK ) return booResult . Status ;
169+ }
170+
171+ if ( data . PlanetsHash != null )
172+ {
173+ // Some LBP2 alpha builds like to insert newlines here
174+ data . PlanetsHash = data . PlanetsHash . Replace ( "\n " , "" ) ;
175+
176+ ValidatedAssetResult planetResult = ResourceValidationHelper . ValidateReference ( new ( data . PlanetsHash , dataContext , importer )
177+ {
178+ // blank = reset planets, but GUIDs should never happen
179+ MayBeGuid = false ,
180+ AssetContextTypeStr = "planet asset" ,
181+ OnNewAssetRefCallback = delegate ( string newRef ) { data . PlanetsHash = newRef ; }
182+ } , context . Logger ) ;
183+ data . PlanetsHash = planetResult . NewAssetRef ;
184+
185+ if ( planetResult . Status != OK )
186+ {
187+ if ( planetResult . ErrorMessage != null ) dataContext . Database . AddErrorNotification ( "Planet update failed" , planetResult . ErrorMessage , user ) ;
188+ return planetResult . Status ;
189+ }
190+ // TODO also read contents and ensure the asset actually contains an earth and a moon
191+ else if ( planetResult . AssetInfo != null && planetResult . AssetInfo . AssetType != GameAssetType . Level )
192+ {
193+ if ( planetResult . ErrorMessage != null ) dataContext . Database . AddErrorNotification ( "Planet update failed" , "The asset was badly formatted." , user ) ;
194+ return BadRequest ;
195+ }
152196 }
153197
154198 // Trim description
@@ -157,8 +201,13 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont
157201 data . Description = data . Description [ ..UgcLimits . DescriptionLimit ] ;
158202 }
159203
204+ if ( data . LevelLocations != null && data . LevelLocations . Count > 0 )
205+ {
206+ dataContext . Database . UpdateLevelLocations ( data . LevelLocations , user ) ;
207+ }
208+
160209 dataContext . Database . UpdateUserData ( user , data , dataContext . Game ) ;
161- return string . Empty ;
210+ return OK ;
162211 }
163212
164213 private const int PinTimeoutDuration = 480 ;
0 commit comments