mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Make IFile and IDirectory implement IFileSystemDirectory
Fix SwitchFs implementation Fix NandPartition implementation
This commit is contained in:
parent
0ba490b2dc
commit
d61088b72f
5 changed files with 152 additions and 70 deletions
|
@ -1,4 +1,5 @@
|
||||||
using System.IO;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using DiscUtils.Fat;
|
using DiscUtils.Fat;
|
||||||
|
|
||||||
|
@ -8,46 +9,88 @@ namespace LibHac.Nand
|
||||||
{
|
{
|
||||||
public FatFileSystem Fs { get; }
|
public FatFileSystem Fs { get; }
|
||||||
|
|
||||||
|
public override string PathSeperator => "/";
|
||||||
|
public override IDirectory RootDirectory => new IDirectory(this, "");
|
||||||
|
|
||||||
public NandPartition(FatFileSystem fileSystem)
|
public NandPartition(FatFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
Fs = fileSystem;
|
Fs = fileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool FileExists(string path)
|
public override bool FileExists(IFile file)
|
||||||
{
|
{
|
||||||
return Fs.FileExists(path);
|
return Fs.FileExists(file.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DirectoryExists(string path)
|
public override bool DirectoryExists(IDirectory directory)
|
||||||
{
|
{
|
||||||
return Fs.DirectoryExists(path);
|
return Fs.DirectoryExists(directory.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream OpenFile(string path, FileMode mode)
|
public new virtual Stream OpenFile(IFile file, FileMode mode)
|
||||||
{
|
{
|
||||||
return Fs.OpenFile(path, mode);
|
return Fs.OpenFile(file.Path, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream OpenFile(string path, FileMode mode, FileAccess access)
|
public override Stream OpenFile(IFile file, FileMode mode, FileAccess access)
|
||||||
{
|
{
|
||||||
return Fs.OpenFile(path, mode, access);
|
return Fs.OpenFile(file.Path, mode, access);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetFileSystemEntries(string path, string searchPattern)
|
public new virtual IFileSytemEntry[] GetFileSystemEntries(IDirectory directory, string searchPattern)
|
||||||
{
|
{
|
||||||
return Fs.GetFileSystemEntries(path, searchPattern);
|
return GetFileSystemEntries(directory, searchPattern, SearchOption.TopDirectoryOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetFileSystemEntries(string path, string searchPattern, SearchOption searchOption)
|
public override IFileSytemEntry[] GetFileSystemEntries(IDirectory directory, string searchPattern, SearchOption searchOption)
|
||||||
{
|
{
|
||||||
string[] files = Fs.GetFiles(path, searchPattern, searchOption);
|
string[] files = Fs.GetFiles(directory.Path, searchPattern, searchOption);
|
||||||
string[] dirs = Fs.GetDirectories(path, searchPattern, searchOption);
|
string[] dirs = Fs.GetDirectories(directory.Path, searchPattern, searchOption);
|
||||||
return files.Concat(dirs).ToArray();
|
IFileSytemEntry[] entries = new IFileSytemEntry[files.Length + dirs.Length];
|
||||||
|
for (int i = 0; i < files.Length; i++)
|
||||||
|
entries[i] = new IFile(this, files[i]);
|
||||||
|
for (int i = 0; i < dirs.Length; i++)
|
||||||
|
entries[i] = new IDirectory(this, files[i]);
|
||||||
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetFullPath(string path)
|
public string GetFullPath(string path)
|
||||||
{
|
{
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IDirectory GetPath(string path)
|
||||||
|
{
|
||||||
|
return new IDirectory(this, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IFile GetFileImpl(string path)
|
||||||
|
{
|
||||||
|
return new IFile(this, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IFile[] GetFiles(IDirectory directory)
|
||||||
|
{
|
||||||
|
List<IFile> files = new List<IFile>();
|
||||||
|
foreach (string file in Fs.GetFiles(directory.Path))
|
||||||
|
files.Add(GetFile(file));
|
||||||
|
return files.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IDirectory[] GetDirectories(IDirectory directory)
|
||||||
|
{
|
||||||
|
List<IDirectory> directories = new List<IDirectory>();
|
||||||
|
foreach (string dir in Fs.GetDirectories(directory.Path))
|
||||||
|
directories.Add(GetDirectory(dir));
|
||||||
|
return directories.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IFileSytemEntry[] GetEntries(IDirectory directory)
|
||||||
|
{
|
||||||
|
List<IFileSytemEntry> list = new List<IFileSytemEntry>();
|
||||||
|
list.AddRange(GetDirectories(directory));
|
||||||
|
list.AddRange(GetFiles(directory));
|
||||||
|
return list.ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,11 @@ namespace LibHac
|
||||||
}
|
}
|
||||||
public abstract Stream OpenFile(IFile file, FileMode mode, FileAccess access);
|
public abstract Stream OpenFile(IFile file, FileMode mode, FileAccess access);
|
||||||
|
|
||||||
public IFile[] GetFileSystemEntries(IDirectory directory, string searchPattern)
|
public IFileSytemEntry[] GetFileSystemEntries(IDirectory directory, string searchPattern)
|
||||||
{
|
{
|
||||||
return GetFileSystemEntries(directory, searchPattern, SearchOption.TopDirectoryOnly);
|
return GetFileSystemEntries(directory, searchPattern, SearchOption.TopDirectoryOnly);
|
||||||
}
|
}
|
||||||
public abstract IFile[] GetFileSystemEntries(IDirectory path, string searchPattern, SearchOption searchOption);
|
public abstract IFileSytemEntry[] GetFileSystemEntries(IDirectory path, string searchPattern, SearchOption searchOption);
|
||||||
|
|
||||||
public IDirectory GetDirectory(string path)
|
public IDirectory GetDirectory(string path)
|
||||||
{
|
{
|
||||||
|
@ -35,20 +35,28 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
if (path.StartsWith(PathSeperator))
|
if (path.StartsWith(PathSeperator))
|
||||||
path = path.Substring(PathSeperator.Length);
|
path = path.Substring(PathSeperator.Length);
|
||||||
return GetFile(path);
|
return GetFileImpl(path);
|
||||||
}
|
}
|
||||||
protected abstract IFile GetFileImpl(string path);
|
protected abstract IFile GetFileImpl(string path);
|
||||||
|
|
||||||
|
|
||||||
public abstract IFile[] GetFiles(IDirectory directory);
|
public abstract IFile[] GetFiles(IDirectory directory);
|
||||||
public abstract IDirectory[] GetDirectories(IDirectory directory);
|
public abstract IDirectory[] GetDirectories(IDirectory directory);
|
||||||
|
public abstract IFileSytemEntry[] GetEntries(IDirectory directory);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class IDirectory
|
public interface IFileSytemEntry
|
||||||
{
|
{
|
||||||
public IFileSystem FileSystem;
|
IFileSystem FileSystem { get; }
|
||||||
public string Path;
|
string Path { get; }
|
||||||
|
bool Exists { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IDirectory : IFileSytemEntry
|
||||||
|
{
|
||||||
|
public IFileSystem FileSystem { get; }
|
||||||
|
public string Path { get; }
|
||||||
|
|
||||||
public IDirectory Parent
|
public IDirectory Parent
|
||||||
{
|
{
|
||||||
|
@ -79,17 +87,28 @@ namespace LibHac
|
||||||
return FileSystem.GetDirectory(Path + FileSystem.PathSeperator + path);
|
return FileSystem.GetDirectory(Path + FileSystem.PathSeperator + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IFileSytemEntry[] GetFileSystemEntries(string searchOption)
|
||||||
|
{
|
||||||
|
return FileSystem.GetFileSystemEntries(this, searchOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFileSytemEntry[] GetFileSystemEntries(string searchPattern, SearchOption searchOption)
|
||||||
|
{
|
||||||
|
return FileSystem.GetFileSystemEntries(this, searchPattern, searchOption);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class IFile
|
public class IFile : IFileSytemEntry
|
||||||
{
|
{
|
||||||
public IFileSystem FileSystem;
|
public IFileSystem FileSystem { get; }
|
||||||
public string Path;
|
public string Path { get; }
|
||||||
|
|
||||||
public string Name => System.IO.Path.GetFileNameWithoutExtension(Path);
|
public string Name => System.IO.Path.GetFileNameWithoutExtension(Path);
|
||||||
public string Extension => System.IO.Path.GetExtension(Path);
|
public string Extension => System.IO.Path.GetExtension(Path);
|
||||||
public string FileName => System.IO.Path.GetFileName(Path);
|
public string FileName => System.IO.Path.GetFileName(Path);
|
||||||
|
|
||||||
|
public bool Exists => FileSystem.FileExists(this);
|
||||||
|
|
||||||
public IDirectory Parent {
|
public IDirectory Parent {
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
@ -98,7 +117,6 @@ namespace LibHac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Exists => FileSystem.FileExists(this);
|
|
||||||
|
|
||||||
public IFile(IFileSystem filesystem, string path)
|
public IFile(IFileSystem filesystem, string path)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace LibHac
|
namespace LibHac
|
||||||
{
|
{
|
||||||
|
@ -30,28 +31,18 @@ namespace LibHac
|
||||||
return new FileStream(GetFullPath(file), mode, access);
|
return new FileStream(GetFullPath(file), mode, access);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IFile[] GetFileSystemEntries(IDirectory path, string searchPattern, SearchOption searchOption)
|
public override IFileSytemEntry[] GetFileSystemEntries(IDirectory path, string searchPattern, SearchOption searchOption)
|
||||||
{
|
{
|
||||||
//return Directory.GetFileSystemEntries(Path.Combine(Root, path), searchPattern, searchOption);
|
var result = new List<IFileSytemEntry>();
|
||||||
var result = new List<IFile>();
|
|
||||||
|
|
||||||
try
|
DirectoryInfo root = new DirectoryInfo(GetFullPath(path));
|
||||||
|
foreach(FileSystemInfo info in root.EnumerateFileSystemInfos(searchPattern, searchOption))
|
||||||
{
|
{
|
||||||
result.AddRange(GetFileSystemEntries(path, searchPattern));
|
string relativePath = Util.GetRelativePath(info.FullName, Root);
|
||||||
}
|
if (info.Attributes.HasFlag(FileAttributes.Directory))
|
||||||
catch (UnauthorizedAccessException) { /* Skip this directory */ }
|
result.Add(new IDirectory(this, relativePath));
|
||||||
|
else
|
||||||
if (searchOption == SearchOption.TopDirectoryOnly)
|
result.Add(new LocalFile(this, relativePath));
|
||||||
return result.ToArray();
|
|
||||||
|
|
||||||
string[] searchDirectories = Directory.GetDirectories(GetFullPath(path));
|
|
||||||
foreach (string search in searchDirectories)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result.AddRange(GetFileSystemEntries(FullPathToDirectory(search), searchPattern, searchOption));
|
|
||||||
}
|
|
||||||
catch (UnauthorizedAccessException) { /* Skip this result */ }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.ToArray();
|
return result.ToArray();
|
||||||
|
@ -76,6 +67,14 @@ namespace LibHac
|
||||||
return directories;
|
return directories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override IFileSytemEntry[] GetEntries(IDirectory directory)
|
||||||
|
{
|
||||||
|
List<IFileSytemEntry> list = new List<IFileSytemEntry>();
|
||||||
|
list.AddRange(GetDirectories(directory));
|
||||||
|
list.AddRange(GetFiles(directory));
|
||||||
|
return list.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
protected override IDirectory GetPath(string path)
|
protected override IDirectory GetPath(string path)
|
||||||
{
|
{
|
||||||
return new IDirectory(this, path);
|
return new IDirectory(this, path);
|
||||||
|
|
|
@ -58,20 +58,16 @@ namespace LibHac
|
||||||
|
|
||||||
private void OpenAllNcas()
|
private void OpenAllNcas()
|
||||||
{
|
{
|
||||||
IFile[] files = ContentsDir.Files;
|
|
||||||
IDirectory[] directories = ContentsDir.Directories;
|
|
||||||
|
|
||||||
Dictionary<string, IStorage> storages = new Dictionary<string, IStorage>();
|
Dictionary<IFileSytemEntry, IStorage> storages = new Dictionary<IFileSytemEntry, IStorage>();
|
||||||
|
|
||||||
foreach (IFile file in files)
|
foreach (IFileSytemEntry nca in ContentsDir.GetFileSystemEntries("*.nca", SearchOption.AllDirectories))
|
||||||
storages[file.Path] = file.Open(FileMode.Open, FileAccess.Read).AsStorage();
|
storages[nca] = OpenSplitNcaStream(nca);
|
||||||
foreach(IDirectory directory in directories)
|
|
||||||
storages[directory.Path] = OpenSplitNcaStream(Fs, directory);
|
|
||||||
|
|
||||||
foreach (KeyValuePair<string, IStorage> kv in storages)
|
foreach (KeyValuePair<IFileSytemEntry, IStorage> kv in storages)
|
||||||
{
|
{
|
||||||
Nca nca = null;
|
Nca nca = null;
|
||||||
string path = kv.Key;
|
IFileSytemEntry file = kv.Key;
|
||||||
IStorage storage = kv.Value;
|
IStorage storage = kv.Value;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -87,7 +83,7 @@ namespace LibHac
|
||||||
|
|
||||||
if (isNax0)
|
if (isNax0)
|
||||||
{
|
{
|
||||||
string sdPath = "/" + path.Replace('\\', '/');
|
string sdPath = Util.GetRelativePath(file.Path, ContentsDir.Path).Replace("\\", "/");
|
||||||
var nax0 = new Nax0(Keyset, storage, sdPath, false);
|
var nax0 = new Nax0(Keyset, storage, sdPath, false);
|
||||||
nca = new Nca(Keyset, nax0.BaseStorage, false);
|
nca = new Nca(Keyset, nax0.BaseStorage, false);
|
||||||
}
|
}
|
||||||
|
@ -96,23 +92,23 @@ namespace LibHac
|
||||||
nca = new Nca(Keyset, storage, false);
|
nca = new Nca(Keyset, storage, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
nca.NcaId = Path.GetFileNameWithoutExtension(path);
|
nca.NcaId = Path.GetFileNameWithoutExtension(file.Path);
|
||||||
string extension = nca.Header.ContentType == ContentType.Meta ? ".cnmt.nca" : ".nca";
|
string extension = nca.Header.ContentType == ContentType.Meta ? ".cnmt.nca" : ".nca";
|
||||||
nca.Filename = nca.NcaId + extension;
|
nca.Filename = nca.NcaId + extension;
|
||||||
}
|
}
|
||||||
catch (MissingKeyException ex)
|
catch (MissingKeyException ex)
|
||||||
{
|
{
|
||||||
if (ex.Name == null)
|
if (ex.Name == null)
|
||||||
{ Console.WriteLine($"{ex.Message} File:\n{path}"); }
|
{ Console.WriteLine($"{ex.Message} File:\n{file.Path}"); }
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string name = ex.Type == KeyType.Title ? $"Title key for rights ID {ex.Name}" : ex.Name;
|
string name = ex.Type == KeyType.Title ? $"Title key for rights ID {ex.Name}" : ex.Name;
|
||||||
Console.WriteLine($"{ex.Message}\nKey: {name}\nFile: {path}");
|
Console.WriteLine($"{ex.Message}\nKey: {name}\nFile: {file.Path}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"{ex.Message} File: {path}");
|
Console.WriteLine($"{ex.Message} File: {file.Path}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nca?.NcaId != null) Ncas.Add(nca.NcaId, nca);
|
if (nca?.NcaId != null) Ncas.Add(nca.NcaId, nca);
|
||||||
|
@ -123,7 +119,7 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
if (SaveDir == null) return;
|
if (SaveDir == null) return;
|
||||||
|
|
||||||
IFile[] files = Fs.GetFileSystemEntries(SaveDir, "*");
|
IFileSytemEntry[] files = Fs.GetFileSystemEntries(SaveDir, "*");
|
||||||
|
|
||||||
foreach (IFile file in files)
|
foreach (IFile file in files)
|
||||||
{
|
{
|
||||||
|
@ -134,7 +130,7 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
IStorage storage = Fs.OpenFile(file, FileMode.Open).AsStorage();
|
IStorage storage = Fs.OpenFile(file, FileMode.Open).AsStorage();
|
||||||
|
|
||||||
string sdPath = file.Path.Replace('\\', '/');
|
string sdPath = Util.GetRelativePath(file.Path, SaveDir.Path).Replace("\\", "/");
|
||||||
var nax0 = new Nax0(Keyset, storage, sdPath, false);
|
var nax0 = new Nax0(Keyset, storage, sdPath, false);
|
||||||
save = new Savefile(Keyset, nax0.BaseStorage, IntegrityCheckLevel.None, true);
|
save = new Savefile(Keyset, nax0.BaseStorage, IntegrityCheckLevel.None, true);
|
||||||
}
|
}
|
||||||
|
@ -157,7 +153,15 @@ namespace LibHac
|
||||||
var title = new Title();
|
var title = new Title();
|
||||||
|
|
||||||
// Meta contents always have 1 Partition FS section with 1 file in it
|
// Meta contents always have 1 Partition FS section with 1 file in it
|
||||||
IStorage sect = nca.OpenSection(0, false, IntegrityCheckLevel.ErrorOnInvalid, true);
|
IStorage sect;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sect = nca.OpenSection(0, false, IntegrityCheckLevel.ErrorOnInvalid, true);
|
||||||
|
} catch(Exception e)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var pfs0 = new Pfs(sect);
|
var pfs0 = new Pfs(sect);
|
||||||
IStorage file = pfs0.OpenFile(pfs0.Files[0]);
|
IStorage file = pfs0.OpenFile(pfs0.Files[0]);
|
||||||
|
|
||||||
|
@ -241,19 +245,32 @@ namespace LibHac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IStorage OpenSplitNcaStream(IFileSystem fs, IDirectory directory)
|
internal static IStorage OpenSplitNcaStream(IFileSytemEntry nca)
|
||||||
{
|
{
|
||||||
var files = new List<IFile>();
|
var files = new List<IFile>();
|
||||||
var storages = new List<IStorage>();
|
var storages = new List<IStorage>();
|
||||||
|
|
||||||
if (directory.Exists)
|
if (nca.Exists)
|
||||||
{
|
{
|
||||||
while (true)
|
if (typeof(IFile).IsAssignableFrom(nca.GetType())) // if entry is a IFile
|
||||||
{
|
{
|
||||||
IFile partFile = directory.GetFile($"{files.Count:D2}");
|
IFile file = (IFile)nca;
|
||||||
if (!partFile.Exists) break;
|
if (file.Name != "00")
|
||||||
|
{
|
||||||
|
return file.Open(FileMode.Open, FileAccess.Read).AsStorage();
|
||||||
|
}
|
||||||
|
files.Add(file);
|
||||||
|
}
|
||||||
|
else if (typeof(IDirectory).IsAssignableFrom(nca.GetType()))
|
||||||
|
{
|
||||||
|
IDirectory directory = (IDirectory)nca;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
IFile partFile = directory.GetFile($"{files.Count:D2}");
|
||||||
|
if (!partFile.Exists) break;
|
||||||
|
|
||||||
files.Add(partFile);
|
files.Add(partFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -261,7 +278,6 @@ namespace LibHac
|
||||||
|
|
||||||
if (files.Count == 1)
|
if (files.Count == 1)
|
||||||
return files[0].Open(FileMode.Open, FileAccess.Read).AsStorage();
|
return files[0].Open(FileMode.Open, FileAccess.Read).AsStorage();
|
||||||
|
|
||||||
|
|
||||||
foreach (IFile file in files)
|
foreach (IFile file in files)
|
||||||
storages.Add(file.Open( FileMode.Open, FileAccess.Read).AsStorage());
|
storages.Add(file.Open( FileMode.Open, FileAccess.Read).AsStorage());
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace LibHac.Tests
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
LocalFileSystem fs = new LocalFileSystem("C:\\\\");
|
LocalFileSystem fs = new LocalFileSystem("G:\\\\");
|
||||||
foreach(IFile file in fs.RootDirectory.Files)
|
foreach(IFile file in fs.RootDirectory.Files)
|
||||||
{
|
{
|
||||||
Console.WriteLine(file.Path);
|
Console.WriteLine(file.Path);
|
||||||
|
@ -21,6 +21,12 @@ namespace LibHac.Tests
|
||||||
Console.WriteLine(directory.Path);
|
Console.WriteLine(directory.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string baseDir = "C:\\Users\\Somebody Whoisbored\\.switch\\";
|
||||||
|
Keyset keyset = ExternalKeys.ReadKeyFile(baseDir + "prod.keys", baseDir + "title.keys", baseDir + "console.keys");
|
||||||
|
|
||||||
|
SwitchFs sw = new SwitchFs(keyset, fs);
|
||||||
|
;
|
||||||
|
|
||||||
Console.ReadKey();
|
Console.ReadKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue