the browser-facing portion of osu!
at master 3.3 kB view raw
1<?php 2 3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 4// See the LICENCE file in the repository root for full licence text. 5 6namespace App\Libraries; 7 8use App\Exceptions\InvariantException; 9use App\Models\Beatmap; 10use App\Models\Score\Best\Model as ScoreBest; 11use Illuminate\Contracts\Filesystem\Filesystem; 12 13class ReplayFile 14{ 15 const DEFAULT_VERSION = 20151228; 16 17 public function __construct(private ScoreBest $score) 18 { 19 } 20 21 public function delete(): void 22 { 23 $this->storage()->delete($this->path()); 24 } 25 26 /** 27 * Generates the end chunk for replay files. 28 * 29 * @return string Binary string of the chunk. 30 */ 31 public function endChunk() 32 { 33 return pack('q', $this->score->score_id); 34 } 35 36 public function get(): ?string 37 { 38 $body = $this->storage()->get($this->path()); 39 40 return $body === null 41 ? null 42 : $this->headerChunk() 43 .pack('i', strlen($body)) 44 .$body 45 .$this->endChunk(); 46 } 47 48 public function getVersion() 49 { 50 return $this->score->replayViewCount?->version ?? static::DEFAULT_VERSION; 51 } 52 53 /** 54 * Generates the header chunk for replay files. 55 * 56 * @return string Binary string of the chunk. 57 */ 58 public function headerChunk(): string 59 { 60 $score = $this->score; 61 $beatmap = $score->beatmap()->withTrashed()->first(); 62 63 if ($beatmap === null) { 64 throw new InvariantException('score is missing beatmap'); 65 } 66 67 $mode = Beatmap::MODES[$score->getMode()]; 68 $user = $score->user; 69 70 if ($user === null) { 71 throw new InvariantException('score is missing user'); 72 } 73 74 $md5 = md5("{$score->maxcombo}osu{$user->username}{$beatmap->checksum}{$score->score}{$score->rank}"); 75 $ticks = $score->date->timestamp * 10000000 + 621355968000000000; // Conversion to dotnet DateTime.Ticks. 76 77 // easier debugging with array and implode instead of plain string concatenation. 78 $components = [ 79 pack('c', $mode), 80 pack('i', $this->getVersion()), 81 pack_str($beatmap->checksum), 82 pack_str($user->username), 83 pack_str($md5), 84 pack('S', $score->count300), 85 pack('S', $score->count100), 86 pack('S', $score->count50), 87 pack('S', $score->countgeki), 88 pack('S', $score->countkatu), 89 pack('S', $score->countmiss), 90 pack('i', $score->score), 91 pack('S', $score->maxcombo), 92 pack('c', $score->perfect), 93 pack('i', $score->getAttributes()['enabled_mods']), 94 pack_str(''), // outputs 0b00 from site, 00 if exported from game client. 95 pack('q', $ticks), 96 ]; 97 98 return implode('', $components); 99 } 100 101 public function put(string $content): void 102 { 103 $this->storage()->put($this->path(), $content); 104 } 105 106 private function path(): string 107 { 108 return (string) $this->score->getKey(); 109 } 110 111 private function storage(): Filesystem 112 { 113 $disk = "replays.{$this->score->getMode()}.{$GLOBALS['cfg']['osu']['score_replays']['storage']}"; 114 115 return \Storage::disk($disk); 116 } 117}