From 316ef5807c00cc8b8332f137d408e752baf409c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BE=D0=BD=D1=8C=D0=BA=D0=B8=D0=BD=20=D0=90=D0=BB?= =?UTF-8?q?=D0=B5=D0=BA=D1=81=D0=B5=D0=B9?= Date: Mon, 19 Sep 2022 23:00:00 +0300 Subject: [PATCH] RomFs builder (not tested on external programs) --- NyaFs/Filesystem/RomFs/Builder/Node.cs | 48 ++++ NyaFs/Filesystem/RomFs/Builder/Nodes/Block.cs | 20 ++ NyaFs/Filesystem/RomFs/Builder/Nodes/Char.cs | 20 ++ NyaFs/Filesystem/RomFs/Builder/Nodes/Dir.cs | 25 ++ NyaFs/Filesystem/RomFs/Builder/Nodes/Fifo.cs | 14 + NyaFs/Filesystem/RomFs/Builder/Nodes/File.cs | 18 ++ .../Filesystem/RomFs/Builder/Nodes/Parent.cs | 18 ++ .../Filesystem/RomFs/Builder/Nodes/Socket.cs | 15 + .../Filesystem/RomFs/Builder/Nodes/SymLink.cs | 18 ++ NyaFs/Filesystem/RomFs/RomFsBuilder.cs | 257 ++++++++++++++++++ NyaFs/Filesystem/RomFs/RomFsReader.cs | 19 ++ NyaFs/Filesystem/RomFs/Types/RmNode.cs | 36 ++- .../Elements/Fs/Writer/RomFsWriter.cs | 13 + NyaFs/Processor/Scripting/Commands/Store.cs | 3 +- todo.md | 21 +- 15 files changed, 539 insertions(+), 6 deletions(-) create mode 100644 NyaFs/Filesystem/RomFs/Builder/Node.cs create mode 100644 NyaFs/Filesystem/RomFs/Builder/Nodes/Block.cs create mode 100644 NyaFs/Filesystem/RomFs/Builder/Nodes/Char.cs create mode 100644 NyaFs/Filesystem/RomFs/Builder/Nodes/Dir.cs create mode 100644 NyaFs/Filesystem/RomFs/Builder/Nodes/Fifo.cs create mode 100644 NyaFs/Filesystem/RomFs/Builder/Nodes/File.cs create mode 100644 NyaFs/Filesystem/RomFs/Builder/Nodes/Parent.cs create mode 100644 NyaFs/Filesystem/RomFs/Builder/Nodes/Socket.cs create mode 100644 NyaFs/Filesystem/RomFs/Builder/Nodes/SymLink.cs create mode 100644 NyaFs/Filesystem/RomFs/RomFsBuilder.cs create mode 100644 NyaFs/ImageFormat/Elements/Fs/Writer/RomFsWriter.cs diff --git a/NyaFs/Filesystem/RomFs/Builder/Node.cs b/NyaFs/Filesystem/RomFs/Builder/Node.cs new file mode 100644 index 0000000..f825997 --- /dev/null +++ b/NyaFs/Filesystem/RomFs/Builder/Node.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Extension.Array; + +namespace NyaFs.Filesystem.RomFs.Builder +{ + class Node + { + public Universal.Types.FilesystemItemType Type; + public string Path; + public uint Mode; + + public long FileOffset = 0; + public long NextOffset = 0; + + public bool Executable => (Mode & 0x100) != 0; + + public Node(Universal.Types.FilesystemItemType Type, string Path, uint Mode) + { + this.Type = Type; + this.Path = Path; + this.Mode = Mode; + } + + public string Filename => (Path == "/") ? "." : Universal.Helper.FsHelper.GetName(Path); + + protected virtual byte[] Content => null; + + protected virtual uint SpecInfo => 0; + + private uint ContentSize => Convert.ToUInt32(Content?.Length ?? 0); + + public Types.RmNode GetHeader() => new Types.RmNode(Filename, Type, Executable, ContentSize, SpecInfo); + + public byte[] GetContent() + { + if (ContentSize > 0) + { + var Res = new byte[ContentSize.GetAligned(0x10)]; + Res.WriteArray(0, Content, Content.Length); + return Res; + } + else + return new byte[] { }; + } + } +} diff --git a/NyaFs/Filesystem/RomFs/Builder/Nodes/Block.cs b/NyaFs/Filesystem/RomFs/Builder/Nodes/Block.cs new file mode 100644 index 0000000..4982e60 --- /dev/null +++ b/NyaFs/Filesystem/RomFs/Builder/Nodes/Block.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NyaFs.Filesystem.RomFs.Builder.Nodes +{ + class Block : Node + { + uint Major; + uint Minor; + + public Block(string Path, uint Mode, uint Major, uint Minor) : base(Universal.Types.FilesystemItemType.Block, Path, Mode) + { + this.Major = Major; + this.Minor = Minor; + } + + protected override uint SpecInfo => ((Major & 0xFFFF) << 16) | (Minor & 0xFFFF); + } +} diff --git a/NyaFs/Filesystem/RomFs/Builder/Nodes/Char.cs b/NyaFs/Filesystem/RomFs/Builder/Nodes/Char.cs new file mode 100644 index 0000000..5a6d5a0 --- /dev/null +++ b/NyaFs/Filesystem/RomFs/Builder/Nodes/Char.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NyaFs.Filesystem.RomFs.Builder.Nodes +{ + class Char : Node + { + uint Major; + uint Minor; + + public Char(string Path, uint Mode, uint Major, uint Minor) : base(Universal.Types.FilesystemItemType.Character, Path, Mode) + { + this.Major = Major; + this.Minor = Minor; + } + + protected override uint SpecInfo => ((Major & 0xFFFF) << 16) | (Minor & 0xFFFF); + } +} diff --git a/NyaFs/Filesystem/RomFs/Builder/Nodes/Dir.cs b/NyaFs/Filesystem/RomFs/Builder/Nodes/Dir.cs new file mode 100644 index 0000000..01b5039 --- /dev/null +++ b/NyaFs/Filesystem/RomFs/Builder/Nodes/Dir.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NyaFs.Filesystem.RomFs.Builder.Nodes +{ + class Dir : Node + { + private List Nodes = new List(); + public Node[] NestedNodes => Nodes.OrderBy(E => E.Filename, StringComparer.Ordinal).ToArray(); + + public long DirLink = 0; + + public Dir(string Path, uint Mode) : base(Universal.Types.FilesystemItemType.Directory, Path, Mode) + { + + } + + public void AddNestedNode(Node N) + { + Nodes.Add(N); + } + } +} diff --git a/NyaFs/Filesystem/RomFs/Builder/Nodes/Fifo.cs b/NyaFs/Filesystem/RomFs/Builder/Nodes/Fifo.cs new file mode 100644 index 0000000..13f9cbf --- /dev/null +++ b/NyaFs/Filesystem/RomFs/Builder/Nodes/Fifo.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NyaFs.Filesystem.RomFs.Builder.Nodes +{ + class Fifo : Node + { + public Fifo(string Path, uint Mode) : base(Universal.Types.FilesystemItemType.Fifo, Path, Mode) + { + + } + } +} diff --git a/NyaFs/Filesystem/RomFs/Builder/Nodes/File.cs b/NyaFs/Filesystem/RomFs/Builder/Nodes/File.cs new file mode 100644 index 0000000..fa11297 --- /dev/null +++ b/NyaFs/Filesystem/RomFs/Builder/Nodes/File.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NyaFs.Filesystem.RomFs.Builder.Nodes +{ + class File : Node + { + private byte[] Data; + + public File(string Path, uint Mode, byte[] Data) : base(Universal.Types.FilesystemItemType.File, Path, Mode) + { + this.Data = Data; + } + + protected override byte[] Content => Data; + } +} diff --git a/NyaFs/Filesystem/RomFs/Builder/Nodes/Parent.cs b/NyaFs/Filesystem/RomFs/Builder/Nodes/Parent.cs new file mode 100644 index 0000000..17d9558 --- /dev/null +++ b/NyaFs/Filesystem/RomFs/Builder/Nodes/Parent.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NyaFs.Filesystem.RomFs.Builder.Nodes +{ + class Parent : Node + { + private long Offset; + + public Parent(long Offset) : base(Universal.Types.FilesystemItemType.HardLink, "..", 0) + { + this.Offset = Offset; + } + + protected override uint SpecInfo => Convert.ToUInt32(Offset); + } +} diff --git a/NyaFs/Filesystem/RomFs/Builder/Nodes/Socket.cs b/NyaFs/Filesystem/RomFs/Builder/Nodes/Socket.cs new file mode 100644 index 0000000..e212caa --- /dev/null +++ b/NyaFs/Filesystem/RomFs/Builder/Nodes/Socket.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NyaFs.Filesystem.RomFs.Builder.Nodes +{ + class Socket : Node + { + public Socket(string Path, uint Mode) : base(Universal.Types.FilesystemItemType.Socket, Path, Mode) + { + + } + + } +} diff --git a/NyaFs/Filesystem/RomFs/Builder/Nodes/SymLink.cs b/NyaFs/Filesystem/RomFs/Builder/Nodes/SymLink.cs new file mode 100644 index 0000000..d6ef88e --- /dev/null +++ b/NyaFs/Filesystem/RomFs/Builder/Nodes/SymLink.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NyaFs.Filesystem.RomFs.Builder.Nodes +{ + class SymLink : Node + { + private byte[] Data; + + public SymLink(string Path, uint Mode, string Target) : base(Universal.Types.FilesystemItemType.SymLink, Path, Mode) + { + this.Data = UTF8Encoding.UTF8.GetBytes(Target); + } + + protected override byte[] Content => Data; + } +} diff --git a/NyaFs/Filesystem/RomFs/RomFsBuilder.cs b/NyaFs/Filesystem/RomFs/RomFsBuilder.cs new file mode 100644 index 0000000..6d83821 --- /dev/null +++ b/NyaFs/Filesystem/RomFs/RomFsBuilder.cs @@ -0,0 +1,257 @@ +using Extension.Array; +using NyaFs.Filesystem.Universal; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NyaFs.Filesystem.RomFs +{ + class RomFsBuilder : IFilesystemBuilder + { + List InvalidGid = new List(); + List InvalidUid = new List(); + + private List Nodes = new List(); + private Builder.Nodes.Dir Root = null; + + private Builder.Nodes.Dir GetParentDirectory(string Path) + { + var Parent = Universal.Helper.FsHelper.GetParentDirPath(Path); + + foreach (var D in Nodes) + { + if (D.Path == Parent) + return D as Builder.Nodes.Dir; + } + + return null; + } + + private void CheckId(uint Uid, uint Gid) + { + if (Gid != 0) + { + if (!InvalidGid.Contains(Gid)) InvalidGid.Add(Gid); + } + if (Gid != 0) + { + if (!InvalidUid.Contains(Gid)) InvalidUid.Add(Gid); + } + } + + private void AddNestedNode(string Path, Func NodeGetter) + { + var Parent = GetParentDirectory(Path); + if ((Parent != null) || (Path == "/")) + { + var N = NodeGetter(); + + if (Parent != null) + Parent.AddNestedNode(N); + else + Root = N as Builder.Nodes.Dir; + + Nodes.Add(N); + } + else + throw new InvalidOperationException($"Cannot add entry with path {Path}: no parent dir."); + } + + + /// + /// Create block device + /// + /// Path to block device + /// Major number + /// Minor number + /// Owner user + /// Owner group + /// Access mode + public void Block(string Path, uint Major, uint Minor, uint User, uint Group, uint Mode) => + AddNestedNode(Path, () => new Builder.Nodes.Block(Path, Mode, Major, Minor)); + + /// + /// Create char device + /// + /// Path to char device + /// Major number + /// Minor number + /// Owner user + /// Owner group + /// Access mode + public void Char(string Path, uint Major, uint Minor, uint User, uint Group, uint Mode) => + AddNestedNode(Path, () => new Builder.Nodes.Char(Path, Mode, Major, Minor)); + + /// + /// Create directory + /// + /// Path to directory (parent dir must exists) + /// Owner user + /// Owner group + /// Access mode + public void Directory(string Path, uint User, uint Group, uint Mode) => + AddNestedNode(Path, () => new Builder.Nodes.Dir(Path, Mode)); + + /// + /// Create fifo + /// + /// Path to fifo + /// Owner user + /// Owner group + /// Access mode + public void Fifo(string Path, uint User, uint Group, uint Mode) => + AddNestedNode(Path, () => new Builder.Nodes.Fifo(Path, Mode)); + + /// + /// Create file + /// + /// Path to file + /// File content + /// Owner user + /// Owner group + /// Access mode + public void File(string Path, byte[] Content, uint User, uint Group, uint Mode) => + AddNestedNode(Path, () => new Builder.Nodes.File(Path, Mode, Content)); + + /// + /// Create socket + /// + /// Path to socket + /// Owner user + /// Owner group + /// Access mode + public void Socket(string Path, uint User, uint Group, uint Mode) => + AddNestedNode(Path, () => new Builder.Nodes.Socket(Path, Mode)); + + /// + /// Create symlink + /// + /// Path to symlink + /// Target path + /// Owner user + /// Owner group + /// Access mode + public void SymLink(string Path, string Target, uint User, uint Group, uint Mode) => + AddNestedNode(Path, () => new Builder.Nodes.SymLink(Path, Mode, Target)); + + public byte[] GetFilesystemImage() + { + if (InvalidGid.Count > 0) + { + Log.Warning(0, $"Filesystem containg group id which cannot be saved in romfs image: {String.Join(", ", Array.ConvertAll(InvalidGid.ToArray(), G => $"{G}"))}"); + } + if (InvalidUid.Count > 0) + { + Log.Warning(0, $"Filesystem containg user id which cannot be saved in romfs image: {String.Join(", ", Array.ConvertAll(InvalidUid.ToArray(), U => $"{U}"))}"); + } + + var Res = new List(); + AppendSuperblock(Res); + AppendNodes(Res); + + var PreparedImage = Res.ToArray(); + UpdateNodes(PreparedImage); + UpdateSuperblock(PreparedImage); + + return PreparedImage; + } + + private void AppendSuperblock(List Res) + { + var SB = new Types.RmSuperblock(); + + Res.AddRange(SB.getPacket()); + } + + private void AppendNodes(List Res) + { + Root.FileOffset = Res.Count; + AddDirectoryNodes(Res, Root); + } + + private void AddNodeWithContent(List Res, List LocalNodes, Builder.Node Node) + { + Node.FileOffset = Res.Count; + + var NodeData = Node.GetHeader(); + Res.AddRange(NodeData.getPacket()); + Res.AddRange(Node.GetContent()); + + LocalNodes.Add(Node); + } + + private void AddParentNode(List Res, List LocalNodes, long Offset) + { + var N = new Builder.Nodes.Parent(Offset); + AddNodeWithContent(Res, LocalNodes, N); + + Nodes.Add(N); + } + + private void AddDirectoryNodes(List Res, Builder.Nodes.Dir Dir) + { + List LocalNodes = new List(); + + var StartOffset = Res.Count; + // dir + AddNodeWithContent(Res, LocalNodes, Dir); + var NestedOffset = Res.Count; + // .. + AddParentNode(Res, LocalNodes, StartOffset); + + // + foreach (var N in Dir.NestedNodes) + { + if (N.Type == Universal.Types.FilesystemItemType.Directory) + { + N.FileOffset = Res.Count; + LocalNodes.Add(N); + + AddDirectoryNodes(Res, N as Builder.Nodes.Dir); + } + else + AddNodeWithContent(Res, LocalNodes, N); + } + + // Update offsets + for(int i = LocalNodes.Count - 1; i > 0; i--) + LocalNodes[i - 1].NextOffset = LocalNodes[i].FileOffset; + + Dir.DirLink = (Dir.Path == "/") ? StartOffset : NestedOffset; + + } + + private uint CalcChecksum(byte[] preparedImage) + { + int Count = Math.Min(512, preparedImage.Length) / 4; + uint Res = 0; + + for(int i = 0; i < Count; i++) + Res += preparedImage.ReadUInt32BE(i * 4); + + Res = Convert.ToUInt32((-Res) & 0xFFFFFFFF); + return Res; + } + + private void UpdateSuperblock(byte[] preparedImage) + { + var SB = new Types.RmSuperblock(preparedImage, 0); + + SB.FullSize = Convert.ToUInt32(preparedImage.Length); + SB.Checksum = CalcChecksum(preparedImage); + } + + private void UpdateNodes(byte[] preparedImage) + { + foreach(var N in Nodes) + { + var Node = new Types.RmNode(preparedImage, N.FileOffset); + Node.NextHeader = Convert.ToUInt32(N.NextOffset); + if(N.Type == Universal.Types.FilesystemItemType.Directory) + Node.SpecInfo = Convert.ToUInt32((N as Builder.Nodes.Dir).DirLink); + + Node.CalcChecksum(); + } + } + } +} \ No newline at end of file diff --git a/NyaFs/Filesystem/RomFs/RomFsReader.cs b/NyaFs/Filesystem/RomFs/RomFsReader.cs index 973c624..05c1ade 100644 --- a/NyaFs/Filesystem/RomFs/RomFsReader.cs +++ b/NyaFs/Filesystem/RomFs/RomFsReader.cs @@ -15,6 +15,11 @@ class RomFsReader : RawPacket, IFilesystemReader public RomFsReader(byte[] Data) : base(Data) { Superblock = new Types.RmSuperblock(Data, 0); + + if(!CheckChecksum()) + { + Log.Error(0, "RomFS: invalid header checksum"); + } } public RomFsReader(string Filename) : this(System.IO.File.ReadAllBytes(Filename)) @@ -22,6 +27,20 @@ public RomFsReader(string Filename) : this(System.IO.File.ReadAllBytes(Filename) } + private bool CheckChecksum() + { + var Part = ReadArray(0, 512); + var SB = new Types.RmSuperblock(Part, 0); + SB.Checksum = 0; + + long Res = 0; + for (int i = 0; i < Part.Length / 4; i++) + Res += Part.ReadUInt32BE(i*4); + + Res = Convert.ToUInt32((-Res) & 0xFFFFFFFF); + return (Res == Superblock.Checksum); + } + private Types.RmNode GetRootNode() => new Types.RmNode(Raw, Superblock.SuperblockSize); internal Types.RmNode[] GetDirEntries(Types.RmNode Dir) diff --git a/NyaFs/Filesystem/RomFs/Types/RmNode.cs b/NyaFs/Filesystem/RomFs/Types/RmNode.cs index 20aba49..514bff7 100644 --- a/NyaFs/Filesystem/RomFs/Types/RmNode.cs +++ b/NyaFs/Filesystem/RomFs/Types/RmNode.cs @@ -1,4 +1,5 @@ using Extension.Packet; +using Extension.Array; using System; using System.Collections.Generic; using System.Text; @@ -7,11 +8,44 @@ namespace NyaFs.Filesystem.RomFs.Types { class RmNode : ArrayWrapper { + private static uint CalcNodeSize(string FileName) => Convert.ToUInt32(0x10 + (FileName.Length + 1).GetAligned(0x10)); + + public RmNode(string Filename, Universal.Types.FilesystemItemType Type, bool Executable, uint Size, uint Spec) : base(CalcNodeSize(Filename)) + { + FsNodeType = Type; + IsExecutable = Executable; + WriteString(0x10, Filename, Filename.Length); + SpecInfo = Spec; + this.Size = Size; + + CalcChecksum(); + } + public RmNode(byte[] Data, long Offset) : base(Data, Offset, 0xC) { } + public uint CalculatedChecksum + { + get { + uint Res = 0; + for (int i = 0; i < HeaderSize; i += 4) + Res += ReadUInt32BE(i); + + Res -= Checksum; + Res = Convert.ToUInt32((-Res) & 0xFFFFFFFF); + return Res; + } + } + + public bool CorrectChecksum => (Checksum == CalculatedChecksum); + + public void CalcChecksum() + { + Checksum = CalculatedChecksum; + } + /// /// The offset of the next file header (zero if no more files) /// @@ -75,7 +109,7 @@ public Universal.Types.FilesystemItemType FsNodeType public bool IsExecutable { get { return (ReadUInt32BE(0x00) & 0x8u) != 0; } - set { WriteUInt32BE(0x00, (ReadUInt32BE(0x00) & 0xFFFFFFF7u) | (value ? 0x80u : 0x00u)); } + set { WriteUInt32BE(0x00, (ReadUInt32BE(0x00) & 0xFFFFFFF7u) | (value ? 0x8u : 0x0u)); } } /// diff --git a/NyaFs/ImageFormat/Elements/Fs/Writer/RomFsWriter.cs b/NyaFs/ImageFormat/Elements/Fs/Writer/RomFsWriter.cs new file mode 100644 index 0000000..e83e41d --- /dev/null +++ b/NyaFs/ImageFormat/Elements/Fs/Writer/RomFsWriter.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NyaFs.ImageFormat.Elements.Fs.Writer +{ + public class RomFsWriter : BaseFsWriter + { + public RomFsWriter() : base(new Filesystem.RomFs.RomFsBuilder()) { } + + public RomFsWriter(string Filename) : base(new Filesystem.RomFs.RomFsBuilder(), Filename) { } + } +} diff --git a/NyaFs/Processor/Scripting/Commands/Store.cs b/NyaFs/Processor/Scripting/Commands/Store.cs index 02504d4..f16da7b 100644 --- a/NyaFs/Processor/Scripting/Commands/Store.cs +++ b/NyaFs/Processor/Scripting/Commands/Store.cs @@ -12,7 +12,7 @@ public Store() : base("store") new string[] { "raw", "gz", "gzip", "lzma", "lz4", "bz2", "bzip2", "legacy" })); AddConfig(new Configs.ImageScriptArgsConfig(1, "ramfs", - new string[] { "cpio", "gz", "gzip", "lzma", "lz4", "bz2", "bzip2", "legacy", "ext2", "squashfs", "cramfs" })); + new string[] { "cpio", "gz", "gzip", "lzma", "lz4", "bz2", "bzip2", "legacy", "ext2", "squashfs", "cramfs", "romfs" })); AddConfig(new Configs.ImageScriptArgsConfig(2, "devtree", new string[] { "dtb" })); @@ -185,6 +185,7 @@ private ImageFormat.Elements.Fs.Writer.Writer GetFsWriter(ImageFormat.Elements.F case "ext2": return new ImageFormat.Elements.Fs.Writer.Ext2FsWriter(ImageFormat.Elements.Fs.Writer.Writer.DetectFixDiskSize(Fs, 0x800000), Path); case "squashfs": return new ImageFormat.Elements.Fs.Writer.SquashFsWriter(Fs.SquashFsCompression, Path); case "cramfs": return new ImageFormat.Elements.Fs.Writer.CramFsWriter(Path); + case "romfs": return new ImageFormat.Elements.Fs.Writer.RomFsWriter(Path); case "lz4": case "lzma": case "gz": diff --git a/todo.md b/todo.md index abaa7b1..0fedd74 100644 --- a/todo.md +++ b/todo.md @@ -1,13 +1,15 @@ # TODO ## Filesystems -CramFS -RomFS +CramFS Big Endian (select+detection) TAR VFAT EXT3 BASE EXT4 BASE +Detection of filesystem limitations before writing! Uid/Gid, mode. +Scan filesystem: detect elf arch, etc... + ## Bugfix Root node mode!.. @@ -17,22 +19,33 @@ TVIP firmware OpenWRT Legacy multiimage +## Encryption +FIT encryption (AES) + ## Plugins Plugin system Allow add readers/writers as plugin Allow add commands from plugin Editing filesystem: change users, groups, rights +Lua scripts as commands: open file, edit, save -## Additional images +## Additional images processing Uboot (TVIP) Secondary bootloader (Android) Recovery DTB (Android) Different resources (TVIP splash) +## Commands +User/Group id change for all system: from -> to + + ## Remote access -Access, adding and editing files from exist commanders like mc, WinSCP and other +Access, adding and editing files from exist commanders like mc, WinSCP and other: SCP SFTP +FTP +NFS + TFTP: return different images based on requested filename ## Links