public static TaskFunc TransferHmod(string transferPath, string hmod) { return((Tasker tasker, Object syncObject) => { tasker.SetStatus(Resources.TransferringMods); var modName = hmod + ".hmod"; foreach (var dir in Shared.hmodDirectories) { string hmodHakchiPath = Shared.EscapeShellArgument($"{transferPath}/{modName}"); if (Directory.Exists(Path.Combine(dir, modName))) { hakchi.Shell.ExecuteSimple($"mkdir -p {hmodHakchiPath}"); using (var hmodTar = new TarStream(Path.Combine(dir, modName))) { if (hmodTar.Length > 0) { using (TrackableStream hmodStream = new TrackableStream(hmodTar)) { hmodStream.OnProgress += tasker.OnProgress; hakchi.Shell.Execute($"tar -xvC {hmodHakchiPath}", hmodStream, null, null, 0, true); } } } break; } if (File.Exists(Path.Combine(dir, modName))) { hakchi.Shell.ExecuteSimple($"mkdir -p {hmodHakchiPath}"); using (var hmodStream = new TrackableFileStream(Path.Combine(dir, modName), FileMode.Open)) { hmodStream.OnProgress += tasker.OnProgress; hakchi.Shell.Execute($"tar -xzvC {hmodHakchiPath}", hmodStream, null, null, 0, true); } break; } } return Conclusion.Success; }); }
public Tasker.Conclusion SyncLocalGames(Tasker tasker, Object syncObject = null) { // now transfer whatever games are remaining Trace.WriteLine("Exporting games: " + Shared.SizeSuffix(stats.TotalSize)); long max = transferGameSet.Sum(afi => afi.FileSize); long value = 0; DateTime startTime = DateTime.Now, lastTime = DateTime.Now; tasker.SetProgress(0, max, Tasker.State.Running, Resources.CopyingGames); foreach (var afi in transferGameSet) { string path = new Uri(exportDirectory + "/" + afi.FilePath).LocalPath; string dir = Path.GetDirectoryName(path); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } if (DateTime.Now.Subtract(lastTime).TotalMilliseconds > UpdateFreq) { transferForm.SetAdvancedProgress(value, max, afi.FilePath); lastTime = DateTime.Now; } // either read actual file, or file stream if (!string.IsNullOrEmpty(afi.LocalFilePath)) { if (afi.FileSize > NesApplication.MaxCompress) { using (var stream = new TrackableFileStream(afi.LocalFilePath, FileMode.Open)) { stream.OnProgress += ((long pos, long len) => { if (DateTime.Now.Subtract(lastTime).TotalMilliseconds > UpdateFreq) { transferForm.SetAdvancedProgress(value + pos, max, afi.FilePath); lastTime = DateTime.Now; } }); using (var f = File.Open(path, FileMode.Create)) stream.CopyTo(f); File.SetLastWriteTimeUtc(path, afi.ModifiedTime); } } else { File.Copy(afi.LocalFilePath, path, true); } } else { if (afi.FileStream == null || !afi.FileStream.CanRead) { Trace.WriteLine($"\"{afi.FilePath}\": no source data or stream or unreadable"); } else { afi.FileStream.Position = 0; using (var f = File.Open(path, FileMode.Create)) afi.FileStream.CopyTo(f); File.SetLastWriteTimeUtc(path, afi.ModifiedTime); } } value += afi.FileSize; } Trace.WriteLine("Uploaded " + (int)(max / 1024) + "kb in " + DateTime.Now.Subtract(startTime).TotalSeconds + " seconds"); // show resulting games directory tasker.SetStatus(Resources.PleaseWait); var process = new Process() { StartInfo = new ProcessStartInfo() { FileName = exportDirectory } }; process.Start(); return(Tasker.Conclusion.Success); }
public static TaskFunc ProcessNand(string nandDump, NandTasks task) { return((Tasker tasker, Object sync) => { NandTasks[] validTasks = { NandTasks.DumpNand, NandTasks.DumpNandB, NandTasks.DumpNandC, NandTasks.FlashNandB, NandTasks.FlashNandC, NandTasks.FormatNandC }; if (!validTasks.Contains(task)) { throw new ArgumentOutOfRangeException("task"); } if (task == NandTasks.FlashNandB && !Files.CheckFileType.IsSquashFs(nandDump)) { throw new Exception(Properties.Resources.InvalidHsqs); } bool isTar = false; if (task == NandTasks.FlashNandC) { isTar = Files.CheckFileType.IsTar(nandDump); if (!(isTar || Files.CheckFileType.IsExtFs(nandDump))) { throw new Exception(Properties.Resources.InvalidUserDataBackup); } } long partitionSize = 300 * 1024 * 1024; var splitStream = new SplitterStream(Program.debugStreams); switch (task) { case NandTasks.DumpNandB: case NandTasks.FlashNandB: hakchi.Shell.Execute("umount /newroot"); hakchi.Shell.Execute("cryptsetup close root-crypt"); hakchi.Shell.ExecuteSimple("cryptsetup open /dev/nandb root-crypt --type plain --cipher aes-xts-plain --key-file /key-file", 2000, true); if (task == NandTasks.DumpNandB) { partitionSize = long.Parse(hakchi.Shell.ExecuteSimple("echo $((($(hexdump -e '1/4 \"%u\"' -s $((0x28)) -n 4 /dev/mapper/root-crypt)+0xfff)/0x1000))", throwOnNonZero: true).Trim()) * 4 * 1024; } if (task == NandTasks.FlashNandB) { partitionSize = long.Parse(hakchi.Shell.ExecuteSimple("blockdev --getsize64 /dev/mapper/root-crypt", throwOnNonZero: true)); } break; case NandTasks.DumpNandC: hakchi.Shell.Execute("hakchi mount_base", null, null, null, 0, true); partitionSize = long.Parse(hakchi.Shell.ExecuteSimple("df -B 1 | grep /newroot/var/lib | head -n 1 | awk -e '{print $3 }'", throwOnNonZero: true).Trim()); break; case NandTasks.FlashNandC: partitionSize = long.Parse(hakchi.Shell.ExecuteSimple("blockdev --getsize64 /dev/nandc", throwOnNonZero: true)); break; case NandTasks.DumpNand: partitionSize = 536870912; break; case NandTasks.FormatNandC: hakchi.Shell.Execute("cat > /bin/mke2fs; chmod +x /bin/mke2fs", File.OpenRead(Shared.PathCombine(Program.BaseDirectoryInternal, "tools", "arm", "mke2fs.static")), null, null, 0, true); hakchi.Shell.Execute("hakchi umount_base", null, splitStream, splitStream); hakchi.Shell.Execute("yes | mke2fs -t ext4 -L data -b 4K -E stripe-width=32 -O ^huge_file,^metadata_csum /dev/nandc", null, splitStream, splitStream, 0, true); hakchi.Shell.Execute("rm /bin/mke2fs"); return Conclusion.Success; } FileMode mode = FileMode.Create; if (task == NandTasks.FlashNandC || task == NandTasks.FlashNandB) { mode = FileMode.Open; } tasker.SetStatus(mode == FileMode.Open ? Resources.FlashingNand : Resources.DumpingNand); using (var file = new TrackableFileStream(nandDump, mode)) { if (mode == FileMode.Open && file.Length > partitionSize) { throw new Exception(Resources.ImageTooLarge); } if (mode == FileMode.Create && task != NandTasks.DumpNandC) { file.SetLength(partitionSize); } if (task == NandTasks.DumpNandC) { file.OnProgress += (long position, long length) => { tasker.OnProgress(Math.Min(position, partitionSize), partitionSize); }; } else { file.OnProgress += tasker.OnProgress; } switch (task) { case NandTasks.DumpNandB: Shared.ShellPipe($"dd if=/dev/mapper/root-crypt bs=128K count={(partitionSize / 1024) / 4 }", null, file, throwOnNonZero: true); break; case NandTasks.FlashNandB: Shared.ShellPipe("dd of=/dev/mapper/root-crypt bs=128K", file, throwOnNonZero: true); hakchi.Shell.Execute("cryptsetup close root-crypt", throwOnNonZero: true); break; case NandTasks.DumpNandC: Shared.ShellPipe($"tar -cvC /newroot/var/lib/ .", null, file, null, throwOnNonZero: true); break; case NandTasks.FlashNandC: if (isTar) { hakchi.Shell.Execute("hakchi mount_base", null, null, null, 0, true); hakchi.Shell.Execute("rm -rf /newroot/var/lib/*", null, null, null, 0, true); hakchi.Shell.Execute("tar -xvC /newroot/var/lib/", file, throwOnNonZero: true); } else { Shared.ShellPipe("dd of=/dev/nandc bs=128K", file, throwOnNonZero: true); } break; case NandTasks.DumpNand: Shared.ShellPipe("sntool sunxi_flash phy_read 0 1000", null, file, throwOnNonZero: true); break; } file.Close(); } tasker.SetStatus(Resources.Done); tasker.SetProgress(1, 1); return Conclusion.Success; }); }
public static TaskFunc ProcessNand(string nandDump, NandTasks task) { return((Tasker tasker, Object sync) => { return TempHelpers.doWithTempFolder((tempFolder) => { NandTasks[] validTasks = { NandTasks.DumpNand, NandTasks.DumpSystemPartition, NandTasks.DumpUserPartition, NandTasks.FlashSystemPartition, NandTasks.FlashUserPartition }; if (!validTasks.Contains(task)) { throw new ArgumentOutOfRangeException(Enum.GetName(typeof(NandTasks), task)); } if (task == NandTasks.FlashSystemPartition || task == NandTasks.FlashUserPartition) { var extension = new FileInfo(nandDump).Extension.ToLower(); if (extension == ".gz" || extension == ".tgz" || extension == ".xz" || extension == ".bz2") { var tempFilename = Path.Combine(tempFolder, "dump.bin"); using (var extractor = ArchiveFactory.Open(nandDump)) using (var entryStream = extractor.Entries.First().OpenEntryStream()) using (var extractedFile = File.Create(tempFilename)) { tasker.SetStatus(Resources.ExtractingToTemporaryFolder); entryStream.CopyTo(extractedFile); nandDump = tempFilename; } } } bool isHsqs = false; bool isExtFs = false; bool isTar = false; if (task == NandTasks.FlashSystemPartition && !((isHsqs = Files.CheckFileType.IsSquashFs(nandDump)) || (isExtFs = Files.CheckFileType.IsExtFs(nandDump)) || (isTar = Files.CheckFileType.IsTar(nandDump)))) { throw new Exception(Properties.Resources.InvalidHsqs); } if (task == NandTasks.FlashUserPartition) { if (!((isTar = Files.CheckFileType.IsTar(nandDump)) || (isExtFs = Files.CheckFileType.IsExtFs(nandDump)))) { throw new Exception(Properties.Resources.InvalidUserDataBackup); } } var nandInfo = Sunxi.NandInfo.GetNandInfo(); long partitionSize = 300 * 1024 * 1024; var splitStream = new SplitterStream(Program.debugStreams); string rootfsDevice = $"/dev/{nandInfo.GetRootfsPartition().Device}"; string osDecryptedDevice = rootfsDevice; string userDataDevice = $"/dev/{nandInfo.GetDataPartition().Device}"; bool hasKeyfile = hakchi.Shell.Execute("[ -f /key-file ]") == 0; if (hasKeyfile) { osDecryptedDevice = "/dev/mapper/root-crypt"; } bool systemIsHsqs = hakchi.Shell.ExecuteSimple($"dd if={osDecryptedDevice} bs=1 count=4", 0) == "HSQS"; switch (task) { case NandTasks.DumpSystemPartition: if (systemIsHsqs) { partitionSize = long.Parse(hakchi.Shell.ExecuteSimple($"echo $((($(hexdump -e '1/4 \"%u\"' -s $((0x28)) -n 4 {osDecryptedDevice})+0xfff)/0x1000))", throwOnNonZero: true).Trim()) * 4 * 1024; } else { partitionSize = long.Parse(hakchi.Shell.ExecuteSimple($"blockdev --getsize64 {osDecryptedDevice}", throwOnNonZero: true)); } break; case NandTasks.FlashSystemPartition: hakchi.Shell.Execute("hakchi umount_base", null, splitStream, splitStream); hakchi.Shell.Execute("umount /newroot"); if (hasKeyfile) { hakchi.Shell.Execute("cryptsetup close root-crypt"); hakchi.Shell.ExecuteSimple($"cryptsetup open {rootfsDevice} root-crypt --type plain --cipher aes-xts-plain --key-file /key-file", 2000, true); } partitionSize = long.Parse(hakchi.Shell.ExecuteSimple($"blockdev --getsize64 {osDecryptedDevice}", throwOnNonZero: true)); break; case NandTasks.DumpUserPartition: hakchi.Shell.Execute("hakchi mount_base", null, null, null, 0, true); partitionSize = long.Parse(hakchi.Shell.ExecuteSimple("df -B 1 | grep /newroot/var/lib | head -n 1 | awk -e '{print $3 }'", throwOnNonZero: true).Trim()); break; case NandTasks.FlashUserPartition: partitionSize = long.Parse(hakchi.Shell.ExecuteSimple($"blockdev --getsize64 {userDataDevice}", throwOnNonZero: true)); break; case NandTasks.DumpNand: partitionSize = 536870912; break; } FileMode mode = FileMode.Create; if (task == NandTasks.FlashUserPartition || task == NandTasks.FlashSystemPartition) { mode = FileMode.Open; } tasker.SetStatus(mode == FileMode.Open ? Resources.FlashingNand : Resources.DumpingNand); using (var file = new TrackableFileStream(nandDump, mode, mode == FileMode.Open ? FileAccess.Read : FileAccess.ReadWrite)) { if (mode == FileMode.Open && file.Length > partitionSize) { throw new Exception(Resources.ImageTooLarge); } if (mode == FileMode.Create && task != NandTasks.DumpUserPartition && task != NandTasks.DumpSystemPartition) { file.SetLength(partitionSize); } if (task == NandTasks.DumpUserPartition) { file.OnProgress += (long position, long length) => { tasker.OnProgress(Math.Min(position, partitionSize), partitionSize); }; } else if (task == NandTasks.DumpSystemPartition && !systemIsHsqs) { file.OnProgress += (long position, long length) => { tasker.OnProgress(Math.Min(position, partitionSize) + partitionSize, partitionSize * 2); }; } else { file.OnProgress += tasker.OnProgress; } switch (task) { case NandTasks.DumpSystemPartition: if (systemIsHsqs) { Shared.ShellPipe($"dd if={osDecryptedDevice} bs=128K count={(partitionSize / 1024) / 4 }", null, file, throwOnNonZero: true); } else { Regex mksquashfsProgress = new Regex(@"(\d+)/(\d+)", RegexOptions.Compiled); using (var mksquashfs = File.OpenRead(Path.Combine(Program.BaseDirectoryInternal, "tools", "mksquashfs"))) { hakchi.Shell.Execute("cat > /mksquashfs", mksquashfs, throwOnNonZero: true); hakchi.Shell.Execute("chmod +x /mksquashfs", throwOnNonZero: true); } hakchi.Shell.ExecuteSimple("hakchi mount_base"); hakchi.Shell.ExecuteSimple("mkdir -p /tmp/"); using (EventStream mkSquashfsProgress = new EventStream()) { splitStream.AddStreams(mkSquashfsProgress); mkSquashfsProgress.OnData += (byte[] buffer) => { string data = Encoding.ASCII.GetString(buffer); MatchCollection matches = mksquashfsProgress.Matches(data); if (matches.Count > 0) { tasker.SetProgress(long.Parse(matches[matches.Count - 1].Groups[1].Value), long.Parse(matches[matches.Count - 1].Groups[2].Value) * 2); } }; hakchi.Shell.Execute("/mksquashfs /newroot/var/squashfs /tmp/rootfs.hsqs", null, splitStream, splitStream, throwOnNonZero: true); splitStream.RemoveStream(mkSquashfsProgress); } partitionSize = long.Parse(hakchi.Shell.ExecuteSimple("ls -la /tmp/rootfs.hsqs | awk -e '{ print $5 }'")); hakchi.Shell.ExecuteSimple("hakchi umount_base"); Shared.ShellPipe("cat /tmp/rootfs.hsqs", stdout: file); hakchi.Shell.ExecuteSimple("rm /tmp/rootfs.hsqs"); } break; case NandTasks.FlashSystemPartition: if (isTar || (hakchi.IsMdPartitioning && !isExtFs)) { using (var eventStream = new EventStream()) { eventStream.OnData += (byte[] data) => { var dataString = Encoding.UTF8.GetString(data).Trim(); tasker.SetStatus(dataString); Trace.WriteLine(dataString); }; hakchi.Shell.Execute("mkdir -p /tmp/rootfs /tmp/squashfs", throwOnNonZero: true); if (isHsqs) { Shared.ShellPipe($"dd of=/tmp/squashfs/squash.hsqs bs=128K", file, throwOnNonZero: true); } ShellTasks.FormatDevice(osDecryptedDevice)(tasker); hakchi.Shell.Execute($"mount {osDecryptedDevice} /tmp/rootfs", throwOnNonZero: true); if (isTar) { hakchi.Shell.Execute($"tar -xvf - -C /tmp/rootfs", file, eventStream, null, throwOnNonZero: true); } else if (isHsqs) { tasker.SetStatus(Resources.FlashingNand); hakchi.Shell.Execute($"mount /tmp/squashfs/squash.hsqs /tmp/squashfs/", throwOnNonZero: true); hakchi.Shell.Execute("cd /tmp/squashfs; rsync -av ./ ../rootfs", null, eventStream, eventStream, throwOnNonZero: true); hakchi.Shell.Execute("umount /tmp/squashfs", throwOnNonZero: true); hakchi.Shell.Execute("rm -rf /tmp/squashfs", throwOnNonZero: true); } hakchi.Shell.Execute("umount /tmp/rootfs", throwOnNonZero: true); hakchi.Shell.Execute("rmdir /tmp/rootfs", throwOnNonZero: true); } } else { Shared.ShellPipe($"dd of={osDecryptedDevice} bs=128K", file, throwOnNonZero: true); } if (hasKeyfile) { hakchi.Shell.Execute("cryptsetup close root-crypt", throwOnNonZero: true); } break; case NandTasks.DumpUserPartition: Shared.ShellPipe($"tar -cvC /newroot/var/lib/ .", null, file, null, throwOnNonZero: true); break; case NandTasks.FlashUserPartition: if (isTar) { hakchi.Shell.Execute("hakchi mount_base", null, null, null, 0, true); hakchi.Shell.Execute("rm -rf /newroot/var/lib/*", null, null, null, 0, true); hakchi.Shell.Execute("tar -xvC /newroot/var/lib/", file, throwOnNonZero: true); } else { Shared.ShellPipe($"dd of={userDataDevice} bs=128K", file, throwOnNonZero: true); } break; case NandTasks.DumpNand: hakchi.Shell.Execute("hakchi umount_base", null, splitStream, splitStream, 0, true); Shared.ShellPipe("sntool sunxi_flash phy_read 0", null, file, throwOnNonZero: true); break; } file.Close(); } tasker.SetStatus(Resources.Done); tasker.SetProgress(1, 1); return Conclusion.Success; }); }); }
static void Main(string[] args) { string source; string target; int result = -1; int top, left; #if DEBUG Debug.Listeners.Add(new TextWriterTraceListener(System.Console.Error)); #endif using (var nes = new ClovershellConnection()) { try { if (args.Length == 0) { ShowHelp(); Environment.Exit(-1); } if (Process.GetProcessesByName("hakchi").Count() > 0) { throw new Exception("Please close hakchi2 first"); } var command = args[0].ToLower(); nes.Connect(); var ping = nes.Ping(); if (ping < 0) { throw new Exception("connected to NES Mini but clovershell is not responding"); } switch (command) { case "shell": if (args.Length >= 2) { nes.ShellPort = ushort.Parse(args[1]); } nes.ShellEnabled = true; nes.AutoReconnect = true; Console.WriteLine("Started shell server on port {0}.", nes.ShellPort); Console.WriteLine("Connect to it using terminal client (raw mode, no local echo)."); Console.WriteLine("Press ENTER to stop."); Console.ReadLine(); result = 0; break; case "exec": if (args.Length < 2) { ShowHelp(); Environment.Exit(-1); } Stream stdin = null; if (args.Length >= 3) { if (args[2] == "null") { stdin = null; } else if (args[2] == "-") { stdin = Console.OpenStandardInput(); } else { stdin = new FileStream(args[2], FileMode.Open); } } Stream stdout; if (args.Length >= 4) { if (args[3] == "-") { stdout = Console.OpenStandardOutput(); } else if (args[3] == "null") { stdout = null; } else { stdout = new FileStream(args[3], FileMode.Create); } } else { stdout = Console.OpenStandardOutput(); } Stream stderr; if (args.Length >= 5) { if (args[4] == "-") { stderr = Console.OpenStandardError(); } else if (args[4] == "null") { stderr = null; } else { stderr = new FileStream(args[4], FileMode.Create); } } else { stderr = Console.OpenStandardError(); } var s = DateTime.Now; result = nes.Execute(args[1], stdin, stdout, stderr); Console.Error.WriteLine("Done in {0}ms. Exit code: {1}", (int)(DateTime.Now - s).TotalMilliseconds, result); break; case "pull": if (args.Length < 2) { ShowHelp(); Environment.Exit(-1); } source = args[1]; if (args.Length >= 3) { target = args[2]; } else { target = source; int pos; while ((pos = target.IndexOf("/")) >= 0) { target = target.Substring(pos + 1); } } source = source.Replace("'", "\\'"); var sizeMS = new MemoryStream(); nes.Execute(string.Format("stat -c %s \"{0}\"", source), null, sizeMS, null, 1000, true); sizeMS.Seek(0, SeekOrigin.Begin); var size = int.Parse(new StreamReader(sizeMS).ReadToEnd()); Console.Write("Reading {0}... ", source); top = Console.CursorTop; left = Console.CursorLeft; var outFile = new TrackableFileStream(target, FileMode.Create); outFile.OnProgress += delegate(long Position, long Length) { Console.CursorTop = top; Console.CursorLeft = left; Console.Write("{0} / {1} ({2}%) ", Position, size > 0 ? size.ToString() : "???", size > 0 ? (Position * 100 / size).ToString() : "???"); }; result = nes.Execute("cat '" + source + "'", null, outFile, Console.OpenStandardError(), 1000, true); Console.WriteLine("Done."); break; case "push": if (args.Length < 3) { ShowHelp(); Environment.Exit(-1); } source = args[1]; target = args[2]; target = target.Replace("'", "\\'"); top = Console.CursorTop; left = Console.CursorLeft; var inFile = new TrackableFileStream(source, FileMode.Open); inFile.OnProgress += delegate(long Position, long Length) { Console.CursorTop = top; Console.CursorLeft = left; Console.Write("{0} / {1} ({2}%) ", Position, Length, Position * 100 / Length); }; result = nes.Execute("cat > '" + target + "'", inFile, Console.OpenStandardOutput(), Console.OpenStandardError(), 1000, true); Console.WriteLine("Done."); break; default: ShowHelp(); Environment.Exit(-1); break; } } catch (Exception ex) { Console.Error.WriteLine("Error: " + ex.Message); } #if DEBUG //Debug.WriteLine("Done."); //Console.ReadLine(); #endif } Environment.Exit(result); }