diff --git a/TEKSteamClient.csproj b/TEKSteamClient.csproj index 68aea13..d149921 100644 --- a/TEKSteamClient.csproj +++ b/TEKSteamClient.csproj @@ -1,7 +1,7 @@ net8.0 - 1.1.1 + 1.2.0 $(BaseVersion) $(BaseVersion)-alpha.$(GITHUB_RUN_NUMBER) Nuclearist diff --git a/protos/manifest/payload.proto b/protos/manifest/payload.proto index 70e6f2f..8ea1379 100644 --- a/protos/manifest/payload.proto +++ b/protos/manifest/payload.proto @@ -10,6 +10,9 @@ message Payload enum FileFlag { NONE = 0; + READONLY = 8; + HIDDEN = 16; + EXECUTABLE = 32; DIRECTORY = 64; } message Chunk diff --git a/src/AppManager.cs b/src/AppManager.cs index 5a8fdee..87c73fa 100644 --- a/src/AppManager.cs +++ b/src/AppManager.cs @@ -48,7 +48,7 @@ public AppManager(uint appId, string installationPath, [Optional]string? worksho if (!Directory.Exists(WorkshopContentPath)) Directory.CreateDirectory(WorkshopContentPath); var attributes = File.GetAttributes(_scDataPath); - if ((attributes & FileAttributes.ReadOnly) is not FileAttributes.None) + if (attributes.HasFlag(FileAttributes.ReadOnly)) File.SetAttributes(_scDataPath, attributes & ~FileAttributes.ReadOnly); } /// Path to SCData directory. @@ -117,7 +117,7 @@ void processDir(in DirectoryEntry dir, in DirectoryEntry.AuxiliaryEntry auxiliar var file = dir.Files[auxiliaryFile.Index]; string filePath = Path.Combine(path, file.Name); var attributes = File.GetAttributes(filePath); - if ((attributes & FileAttributes.ReadOnly) is not FileAttributes.None) + if (attributes.HasFlag(FileAttributes.ReadOnly)) File.SetAttributes(filePath, attributes & ~FileAttributes.ReadOnly); using var fileHandle = File.OpenHandle(filePath, access: FileAccess.ReadWrite, options: FileOptions.RandomAccess); long fileSize = RandomAccess.GetLength(fileHandle); @@ -395,7 +395,6 @@ static long countTotalDirSize(in DirectoryEntry dir) foreach (var subdir in dir.Subdirectories) result += countTotalDirSize(in subdir); return result; - } if (acquisitionDir.IsNew) { @@ -423,7 +422,22 @@ static long countTotalDirSize(in DirectoryEntry dir) var file = dir.Files[acquisitonFile.Index]; if (acquisitonFile.Chunks.Count is 0) { - File.Move(Path.Combine(downloadPath, file.Name), Path.Combine(localPath, file.Name), true); + string destinationFile = Path.Combine(localPath, file.Name); + if (File.Exists(destinationFile)) + File.Delete(destinationFile); + File.Move(Path.Combine(downloadPath, file.Name), destinationFile); + if (file.Flags is not 0) + { + var attributes = (FileAttributes)0; + if (file.Flags.HasFlag(FileEntry.Flag.ReadOnly)) + attributes = FileAttributes.ReadOnly; + if (file.Flags.HasFlag(FileEntry.Flag.Hidden)) + attributes |= FileAttributes.Hidden; + if (attributes is not 0) + File.SetAttributes(destinationFile, attributes); + if (file.Flags.HasFlag(FileEntry.Flag.Executable) && !OperatingSystem.IsWindows()) + File.SetUnixFileMode(destinationFile, File.GetUnixFileMode(destinationFile) | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute); + } state.DisplayProgress += file.Size; ProgressUpdated?.Invoke(state.DisplayProgress); } @@ -440,7 +454,7 @@ static long countTotalDirSize(in DirectoryEntry dir) } string filePath = Path.Combine(localPath, file.Name); var attributes = File.GetAttributes(filePath); - if ((attributes & FileAttributes.ReadOnly) is not FileAttributes.None) + if (attributes.HasFlag(FileAttributes.ReadOnly)) File.SetAttributes(filePath, attributes & ~FileAttributes.ReadOnly); using var fileHandle = File.OpenHandle(filePath, access: FileAccess.Write, options: FileOptions.RandomAccess); for (; chunkIndex < acquisitonFile.Chunks.Count; chunkIndex++) diff --git a/src/Manifest/DepotManifest.cs b/src/Manifest/DepotManifest.cs index 473293b..368cec8 100644 --- a/src/Manifest/DepotManifest.cs +++ b/src/Manifest/DepotManifest.cs @@ -71,7 +71,7 @@ internal DepotManifest(byte[] compressedData, ItemIdentifier item) foreach (var file in files) { _nameBufferSize += Encoding.UTF8.GetByteCount(Path.GetFileName(file.Name)); - if ((file.Flags & FileFlag.Directory) is not 0) + if (file.Flags.HasFlag(FileFlag.Directory)) numDirs++; else { @@ -111,7 +111,7 @@ void loadDirectory(int index, int payloadEntryIndex) int numSepChars = file.Name.AsSpan().Count(Path.DirectorySeparatorChar); if (numSepChars - dirNumSepChars is not 1 || numSepChars > 0 && file.Name[path.Length] != Path.DirectorySeparatorChar) continue; - if ((file.Flags & FileFlag.Directory) is not 0) + if (file.Flags.HasFlag(FileFlag.Directory)) { dirOffset++; continue; @@ -128,10 +128,17 @@ void loadDirectory(int index, int payloadEntryIndex) Checksum = chunk.Checksum }; new Span(ChunkBuffer, chunkStartOffset, numChunks).Sort(); + FileEntry.Flag flags = 0; + if (file.Flags.HasFlag(FileFlag.Readonly)) + flags = FileEntry.Flag.ReadOnly; + if (file.Flags.HasFlag(FileFlag.Hidden)) + flags |= FileEntry.Flag.Hidden; + if (file.Flags.HasFlag(FileFlag.Executable)) + flags |= FileEntry.Flag.Executable; FileBuffer[fileOffset++] = new() { Name = numSepChars > 0 ? Path.GetFileName(file.Name) : file.Name, - Size = file.Size, + SizeAndFlags = file.Size | ((long)flags << 56), Chunks = new(ChunkBuffer, chunkStartOffset, numChunks) }; } @@ -142,7 +149,7 @@ void loadDirectory(int index, int payloadEntryIndex) for (int i = startIndex; i < endIndex; i++) { var file = files[i]; - if ((file.Flags & FileFlag.Directory) is not 0) + if (file.Flags.HasFlag(FileFlag.Directory)) { int numSepChars = file.Name.AsSpan().Count(Path.DirectorySeparatorChar); if (numSepChars - dirNumSepChars is 1 && (numSepChars < 1 || file.Name[path.Length] == Path.DirectorySeparatorChar)) @@ -209,7 +216,7 @@ public DepotManifest(string filePath, ItemIdentifier item, ulong id) FileBuffer[i] = new() { Name = Encoding.UTF8.GetString(buffer.Slice(nameOffset, nameLength)), - Size = Unsafe.As(ref Unsafe.Add(ref spanRef, offset)), + SizeAndFlags = Unsafe.As(ref Unsafe.Add(ref spanRef, offset)), Chunks = new(ChunkBuffer, chunkOffset, numChunks) }; offset += 4; @@ -289,7 +296,7 @@ public void WriteToFile(string filePath) { int nameLength = Encoding.UTF8.GetBytes(file.Name, buffer[nameOffset..]); nameOffset += nameLength; - Unsafe.As(ref Unsafe.Add(ref spanRef, offset)) = file.Size; + Unsafe.As(ref Unsafe.Add(ref spanRef, offset)) = file.SizeAndFlags; offset += 2; entriesSpan[offset++] = nameLength; entriesSpan[offset++] = file.Chunks.Count; diff --git a/src/Manifest/FileEntry.cs b/src/Manifest/FileEntry.cs index 074341d..8c6b225 100644 --- a/src/Manifest/FileEntry.cs +++ b/src/Manifest/FileEntry.cs @@ -7,10 +7,25 @@ public readonly struct FileEntry { /// Name of the file. public required string Name { get; init; } + /// Total size of the file OR'ed with its flags. Used only during initialization. + public required long SizeAndFlags { get; init; } /// Total size of the file. - public required long Size { get; init; } + public long Size => SizeAndFlags & 0x00FFFFFFFFFFFFFF; + /// Extra flags of the file. + public Flag Flags => (Flag)((SizeAndFlags & 0x7F00000000000000) >> 56); /// Chunks that compose the file. public required ArraySegment Chunks { get; init; } + /// Extra flags describing file's attributes or permissions. + [Flags] + public enum Flag + { + /// The file has attribute. + ReadOnly = 1 << 0, + /// The file has attribute. + Hidden = 1 << 1, + /// The file has , and permissions. + Executable = 1 << 2 + } /// Structure used in to store indexes of files and chunks that must be acquired. internal readonly struct AcquisitionEntry { @@ -18,7 +33,8 @@ internal readonly struct AcquisitionEntry public required int Index { get; init; } /// File's chunks that must be acquired. If empty, the whole file is acquired. public required ArraySegment Chunks { get; init; } - //struct int long + /// Structure storing chunk's index along with its optional offset in chunk buffer file. + /// Index of the chunk in its file. public readonly struct ChunkEntry(int index) { /// Index of the chunk entry in its file.