I'm trying to add a jmp instruction at the end of text section in the calc.exe for windows XP, and I've added it and modified the entry point to start from that address and modified the virtual size of the text section so that it can handle the added instruction, but the result exe didn't work. so am I missing any thing here?
here is the C# code I've wrote to handle those things:
public static void inject()
{
StreamReader sr = new StreamReader("C:\\calc.EXE");
BinaryReader br = new BinaryReader(sr.BaseStream);
List<byte> bytesList = new List<byte>();
for (long i = 0; i < br.BaseStream.Length; i++)
{
bytesList.Add(br.ReadByte());
}
{
// updating the entry point
bytesList[280] = 176;
bytesList[281] = 42;
bytesList[282] = 1;
bytesList[283] = 0;
}
{
bytesList[496] = 192;
}
{
// second jmp
bytesList.RemoveRange(76464, 5);
byte[] injectedBytes = { 233, 255, 255, 249, 192 };
bytesList.InsertRange(76464, injectedBytes);
}
StreamWriter sw = new StreamWriter("C:\\calc2.EXE");
BinaryWriter bw = new BinaryWriter(sw.BaseStream);
bw.Write(bytesList.ToArray());
bw.Close();
}
and thanks in advance
When you change (in your case Add/Enlarge) a Section (in your case the Code Section, aka the so-called text Section), you MUST also tell the Windows Loader to map this additional part into Memory by modifying the Section's associated Header (descriptor). In your case, it looks like you did not make this and the Loader won't load the new code (and in your case, it won't even load/start the application). Modifying Image Files is cool, but takes time to master.
Portable Executable File Format – A Reverse Engineer View shows how it works.
Related
I am currently elaborating which content I should use in the different version numbers, so I read What are differences between AssemblyVersion, AssemblyFileVersion and AssemblyInformationalVersion? and many other posts and articles.
Based on SemVer, I would increase the minor version number if I make backwards compatible changes. I understand and like this concept, and I want to use it.
This answer on the above linked post has good explanations on the different version numbers, but it also says that changing AssemblyVersion would require recompiling all dependent assemblies and executables.
I did a quick test:
testVersionLib.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>abc.snk</AssemblyOriginatorKeyFile>
<AssemblyVersion>3.4.5.6</AssemblyVersion>
</PropertyGroup>
</Project>
project testVersionLib: class1.cs
using System;
namespace testVersionLib
{
public class Class1
{
public string m_versionText = "3.4.5.6";
}
}
project testVersionExe: program.cs
using System;
using System.Diagnostics;
namespace testVersionExe
{
class Program
{
static void Main (string[] args)
{
Console.WriteLine ("Hello World!");
PrintFileVersion ();
PrintAssemblyFullName ();
}
private static void PrintAssemblyFullName ()
{
Console.WriteLine ("m_versionText: " + new testVersionLib.Class1 ().m_versionText);
Console.WriteLine ("DLL Assembly FullName: " + System.Reflection.Assembly.GetAssembly (typeof (testVersionLib.Class1)).FullName);
}
private static void PrintFileVersion ()
{
Console.WriteLine ("DLL FileVersion: " + FileVersionInfo.GetVersionInfo ("testVersionLib.dll").FileVersion);
}
}
}
and found that this may apply to .Net Framework, but it obviously does not apply to .NET 5 (and most likely .NET 6 and above as well, and maybe previous versions of .NET Core): I created a .NET 5 C# console app with AssemblyVersion 1.2.3.4 and strong name. This EXE references a DLL with AssemblyVersion 3.4.5.6 and strong name. The DLL compiled with different versions and is placed in the EXE's folder without compiling that one again.
The results:
The EXE fails to start if the DLL version is below 3.4.5.6 (e.g. 3.4.5.5, 3.4.4.6, 3.3.5.6), which makes sense.
The EXE successfully runs if the DLL version is equal to or above the version that was used to created the app (equal: 3.4.5.6; above: 3.4.5.7, 3.4.6.6, 3.5.5.6, 4.4.5.6).
This answer only says that
[...] .Net 5+ does not (by default) require that the assembly version used at runtime match the assembly version used at build time.
but it does not explain why and how.
How are assemblies located, resolved and loaded in .NET 5?
If someone wants to repeat my test with the compiled files, here's the 7z archive, encoded as PNG:
To decode the image, save it as PNG and use this code:
static void Main (string[] args)
{
string dataPath = #"c:\temp\net5ver.7z";
string imagePath = #"c:\temp\net5ver.7z.png";
string decodedDataPath = #"c:\temp\net5ver.out.7z";
int imageWidth = 1024;
Encode (dataPath, imagePath, imageWidth);
Decode (imagePath, decodedDataPath);
}
public static void Decode (string i_imagePath, string i_dataPath)
{
var bitmap = new Bitmap (i_imagePath);
var bitmapData = bitmap.LockBits (new Rectangle (Point.Empty, bitmap.Size), System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);
byte[] dataLengthBytes = new byte[4];
Marshal.Copy (bitmapData.Scan0, dataLengthBytes, 0, 4);
int dataLength = BitConverter.ToInt32 (dataLengthBytes);
int imageWidth = bitmap.Width;
int dataLines = (int)Math.Ceiling (dataLength / (double)imageWidth);
if (bitmap.Height != dataLines + 1)
throw new Exception ();
byte[] row = new byte[imageWidth];
List<byte> data = new();
for (int copyIndex = 0; copyIndex < dataLines; copyIndex++)
{
int rowStartIndex = imageWidth * (copyIndex + 1);
Marshal.Copy (IntPtr.Add (bitmapData.Scan0, rowStartIndex), row, 0, row.Length);
data.AddRange (row.Take (dataLength - data.Count));
}
bitmap.UnlockBits (bitmapData);
System.IO.File.WriteAllBytes (i_dataPath, data.ToArray ());
}
public static void Encode (string i_dataPath,
string i_imagePath,
int i_imageWidth)
{
byte[] data = System.IO.File.ReadAllBytes (i_dataPath);
int dataLines = (int)Math.Ceiling (data.Length / (double)i_imageWidth);
int imageHeight = dataLines + 1;
var bitmap = new Bitmap (i_imageWidth, imageHeight, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
var palette = bitmap.Palette;
for (int index = 0; index < byte.MaxValue; index++)
palette.Entries[index] = Color.FromArgb (index, index, index);
bitmap.Palette = palette;
var bitmapData = bitmap.LockBits (new Rectangle (Point.Empty, bitmap.Size), System.Drawing.Imaging.ImageLockMode.WriteOnly, bitmap.PixelFormat);
Marshal.Copy (BitConverter.GetBytes (data.Length), 0, bitmapData.Scan0, 4);
for (int copyIndex = 0; copyIndex < dataLines; copyIndex++)
{
int dataStartIndex = i_imageWidth * copyIndex;
int rowStartIndex = i_imageWidth * (copyIndex + 1);
byte[] row = data.Skip (dataStartIndex).Take (i_imageWidth).ToArray ();
Marshal.Copy (row, 0, IntPtr.Add (bitmapData.Scan0, rowStartIndex), row.Length);
}
bitmap.UnlockBits (bitmapData);
bitmap.Save (i_imagePath);
}
I am currently trying to implement copy and paste for my application, the problem is that i can only plaintext or images to the clipboard according to the documentation of Gtk.Clipboard: https://valadoc.org/gtk+-3.0/Gtk.Clipboard.html set_text / set_image.
But then there is also this method https://valadoc.org/gtk+-3.0/Gtk.Clipboard.set_with_data.html set_with_data, which i think i can use for adding a uri or an array of uris. But i can't figure out how and didn't find any good examples either.
UPDATE
Using the given answer i can fill the clipboard with an array of uris, but i can read them, when i try it just calls the get_func again and refills it.
CTRL C pressed
clipboard get_func called
Received: file:///home/marcel/Downloads/.gitignore
CTRL V pressd
clipboard get_func called
Received: file:///home/marcel/Downloads
Try Pasting: file:///home/marcel/Downloads
This is the code i use for testing CTRL + V:
print ("\nCTRL V pressd\n");
clipboard.request_uris ((clipboard, uris) => {
foreach ( string content in uris ) {
print ("Try Pasting: ");
print (content);
print ("\n");
}
});
and this is the relevant part of the get_func for CTRL + C:
clipboard.set_with_owner (
clipboard_targets,
(clipboard, selection_data, info, user_data_or_owner) => {
print ("clipboard get_func called\n");
var w = user_data_or_owner as Window;
File[] files = { w.get_selected_file () };
switch ( info ) {
case ClipboardProtocol.TEXT_URI_LIST:
print ("Received: ");
string[] uris = {};
foreach ( var file in files ) {
print (file.get_uri ());
print ("\n");
uris += file.get_uri ();
}
selection_data.set_uris (uris);
break;
As you can see in the terminal output above, it just refills the clipboard, throwing away its previous values.
As requested I am providing both an example for writing URIs to clipboard and getting URIs from clipboard. These examples are basically command line programs that get / set the clipboard immediately. In an actual GUI application you would probably react to a button press or, to catch CtrlC / CtrlV events, use Gtk.Widget.add_events() and get / set the clipboard when handling the Gtk.Widget.event signal.
Getting the clipboard
You can request URIs from the X11 clipboard using Gtk.Clipboard.request_uris (). This function takes a callback that will be called once the URIs are available.
Example:
public void main (string[] args) {
Gtk.init (ref args);
Gdk.Display display = Gdk.Display.get_default ();
Gtk.Clipboard clipboard = Gtk.Clipboard.get_for_display (display, Gdk.SELECTION_CLIPBOARD);
clipboard.request_uris (recieved_func);
Gtk.main ();
}
/* Gtk.ClipboardURIRecievedFunc */
private void recieved_func (Gtk.Clipboard clipboard, string[] uris) {
foreach (var uri in uris) {
print (uri + "\n");
}
Gtk.main_quit ();
}
To be compiled with valac clipget.vala --pkg=gtk+-3.0
Setting the clipboard
Theory:
From the Qt4 documentation:
Since there is no standard way to copy and paste files between
applications on X11, various MIME types and conventions are currently
in use. For instance, Nautilus expects files to be supplied with a
x-special/gnome-copied-files MIME type with data beginning with the
cut/copy action, a newline character, and the URL of the file.
Gtk.Clipboard does not pre-implement setting the clipboard for copying / cutting files. As you said, there is no such Gtk.Clipboard.set_uris().
Instead, you should set the clipboard by providing a callback that X11 gets the clipboard contents from once requested.
These are the steps required:
Create a bunch of Gtk.TargetEntrys that specify what clipboard protocols your app can handle. You'll want to handle the protocolstext/uri-list, x-special/gnome-copied-files and UTF8_STRING. Each TargetEntry is identified by its info field, so that number should be unique (see enum ClipboardProtocol in the example below)
Implement a method of the type Gtk.ClipboardGetFunc. This method should fill the Gtk.SelectionData object that is passed with the file paths to copy / cut. Check for the info parameter to set the SelectionData argument according to the protocol specified.
Register the callback and the protocols implemented to X11 using Gtk.Clipboard.set_with_owner or Gtk.Clipboard.set_with_data
Example:
enum ClipboardProtocol {
TEXT_URI_LIST,
GNOME_COPIED_FILES,
UTF8_STRING
}
public void main (string[] args) {
Gtk.init (ref args);
Gdk.Display display = Gdk.Display.get_default ();
Gtk.Clipboard clipboard = Gtk.Clipboard.get_for_display (display, Gdk.SELECTION_CLIPBOARD);
var clipboard_targets = new Gtk.TargetEntry[3];
Gtk.TargetEntry target_entry = { "text/uri-list", 0, ClipboardProtocol.TEXT_URI_LIST };
clipboard_targets[0] = target_entry;
target_entry = { "x-special/gnome-copied-files", 0, ClipboardProtocol.GNOME_COPIED_FILES };
clipboard_targets[1] = target_entry;
target_entry = { "UTF8_STRING", 0, ClipboardProtocol.UTF8_STRING };
clipboard_targets[2] = target_entry;
var owner = new Object ();
var rc = clipboard.set_with_owner (
clipboard_targets,
get_func,
clear_func,
owner
);
assert (rc);
clipboard.store ();
Gtk.main ();
}
/* Gtk.ClipboardGetFunc */
private void get_func (
Gtk.Clipboard clipboard,
Gtk.SelectionData selection_data,
uint info,
void* user_data_or_owner
) {
print ("GET FUNC!\n");
File my_file = File.new_for_path ("/home/lukas/tmp/test.txt");
File my_2nd_file = File.new_for_path ("/home/lukas/tmp/test2.txt");
File[] files = { my_file, my_2nd_file };
switch (info) {
case ClipboardProtocol.TEXT_URI_LIST:
string[] uris = {};
foreach (var file in files) {
uris += file.get_uri ();
}
selection_data.set_uris (uris);
break;
case ClipboardProtocol.GNOME_COPIED_FILES:
var prefix = "copy\n";
//var prefix = "cut\n";
/* use one of the above */
var builder = new StringBuilder (prefix);
for (int i = 0; i < files.length; i++) {
builder.append (files[i].get_uri ());
/* dont put the newline if this is the last file */
if (i != files.length - 1)
builder.append_c ('\n');
}
selection_data.set (
selection_data.get_target (),
8,
builder.data
);
break;
case ClipboardProtocol.UTF8_STRING:
var builder = new StringBuilder ();
foreach (var file in files) {
builder.append (file.get_parse_name ());
}
builder.append_c ('\n');
selection_data.set_text (builder.str, -1);
break;
default:
assert_not_reached ();
}
Gtk.main_quit ();
}
/* Gtk.ClipboardClearFunc */
private void clear_func (Gtk.Clipboard clipboard, void* data) {
;
}
To be compiled with valac clipset.vala --pkg=gtk+-3.0
A couple of notes:
In my example, I could only test x-special/gnome-copied-files since I only have Nautilus installed at the moment. I adapted all of the protocols from the Thunar source code (see sources below) but they might still require troubleshooting*
If you do not want to go through the trouble of implementing this yourself, you could also use the xclip command line tool: https://askubuntu.com/a/210428/345569 However, IMHO implementing this yourself is a little more elegant.
Sources:
Article from the Ubuntu Forums: https://ubuntuforums.org/archive/index.php/t-2135919.html
Thunar source code (especially thunar/thunar/thunar-clipboard-manager.c): https://github.com/xfce-mirror/thunar/blob/3de231d2dec33ca48b73391386d442231baace3e/thunar/thunar-clipboard-manager.c
Qt4 documentation: http://doc.qt.io/archives/qt-4.8/qclipboard.html
I am running the Portable Device API to automatically get Photos from a connected Smart Phone. I have it all transferring correctly. The code that i use is that Standard DownloadFile() routine:
public PortableDownloadInfo DownloadFile(PortableDeviceFile file, string saveToPath)
{
IPortableDeviceContent content;
_device.Content(out content);
IPortableDeviceResources resources;
content.Transfer(out resources);
PortableDeviceApiLib.IStream wpdStream;
uint optimalTransferSize = 0;
var property = new _tagpropertykey
{
fmtid = new Guid(0xE81E79BE, 0x34F0, 0x41BF, 0xB5, 0x3F, 0xF1, 0xA0, 0x6A, 0xE8, 0x78, 0x42),
pid = 0
};
resources.GetStream(file.Id, ref property, 0, ref optimalTransferSize, out wpdStream);
System.Runtime.InteropServices.ComTypes.IStream sourceStream =
// ReSharper disable once SuspiciousTypeConversion.Global
(System.Runtime.InteropServices.ComTypes.IStream)wpdStream;
var filename = Path.GetFileName(file.Name);
if (string.IsNullOrEmpty(filename))
return null;
FileStream targetStream = new FileStream(Path.Combine(saveToPath, filename),
FileMode.Create, FileAccess.Write);
try
{
unsafe
{
var buffer = new byte[1024];
int bytesRead;
do
{
sourceStream.Read(buffer, 1024, new IntPtr(&bytesRead));
targetStream.Write(buffer, 0, 1024);
} while (bytesRead > 0);
targetStream.Close();
}
}
finally
{
Marshal.ReleaseComObject(sourceStream);
Marshal.ReleaseComObject(wpdStream);
}
return pdi;
}
}
There are two problems with this standard code:
1) - when the images are saves to the windows machine, there is no EXIF information. this information is what i need. how do i preserve it?
2) the saved files are very bloated. for example, the source jpeg is 1,045,807 bytes, whilst the downloaded file is 3,942,840 bytes!. it is similar to all of the other files. I would of thought that the some inside the unsafe{} section would output it byte for byte? Is there a better way to transfer the data? (a safe way?)
Sorry about this. it works fine.. it is something else that is causing these issues
I am currently making an "app launcher" which reads a text file line by line. Each line is a path to a useful program somewhere else on my pc. A link label is automatically made for each path (i.e. each line) in the text file.
I would like the .Text property of the link label to be an abbreviated form of the path (i.e. just the file name, not the whole path). I have found out how to shorten the string in this way (so far so good !)
However, I would also like to store the full path somewhere - as this is what my linklabel will need to link to. In Javascript I could pretty much just add this property to linklabel like so: mylinklabel.fullpath=line; (where line is the current line as we read through the text file, and fullpath is my "custom" property that I would like to try and add to the link label. I guess it needs declaring, but I am not sure how.
Below is the part of my code which creates the form, reads the text file line by line and creates a link label for the path found on each line:
private void Form1_Load(object sender, EventArgs e) //on form load
{
//System.Console.WriteLine("hello!");
int counter = 0;
string line;
string filenameNoExtension;
string myfile = #"c:\\users\matt\desktop\file.txt";
//string filenameNoExtension = Path.GetFileNameWithoutExtension(myfile);
// Read the file and display it line by line.
System.IO.StreamReader file = new System.IO.StreamReader(myfile);
while ((line = file.ReadLine()) != null)
{
//MessageBox.Show(line); //check whats on each line
LinkLabel mylinklabel = new LinkLabel();
filenameNoExtension = Path.GetFileNameWithoutExtension(line); //shortens the path to just the file name without extension
mylinklabel.Text = filenameNoExtension;
//string fullpath=line; //doesn't work
//mylinklabel.fullpath=line; //doesn't work
mylinklabel.Text = filenameNoExtension; //displays the shortened path
this.Controls.Add(mylinklabel);
mylinklabel.Location = new Point(0, 30 + counter * 30);
mylinklabel.AutoSize = true;
mylinklabel.VisitedLinkColor = System.Drawing.Color.White;
mylinklabel.LinkColor = System.Drawing.Color.White;
mylinklabel.Click += new System.EventHandler(LinkClick);
counter++;
}
file.Close();
}
So, how can I store a full path as a string inside the linklabel for use in my onclick function later on?
You could derive a new custom class or you could use a secondary data store for your additional info the easiest solution would be to use a dictionary.
dictonary<string,string> FilePaths = new dictonary<string,string>();
private void Form1_Load(object sender, EventArgs e) //on form load
{
...
FilePath[filenameNoExtension] = line;
}
You Can Access the Path
FilePath[mylinklabel.Tex]
One option you have is to have a method that truncates your string (and even adds "..."). You can then store the full path in the Tag property of the Linklabel. And here's an example of the first part (truncating the text).
public static string Truncate(this string s, int maxLength)
{
if (string.IsNullOrEmpty(s) || maxLength <= 0)
return string.Empty;
else if (s.Length > maxLength)
return s.Substring(0, maxLength) + "...";
else
return s;
}
Hope that helps some
Using C# 4 in a Windows console application that continually reports progress how can I make the "redraw" of the screen more fluid?
I'd like to do one of the following:
- Have it only "redraw" the part of the screen that's changing (the progress portion) and leave the rest as is.
- "Redraw" the whole screen but not have it flicker.
Currently I re-write all the text (application name, etc.). Like this:
Console.Clear();
WriteTitle();
Console.WriteLine();
Console.WriteLine("Deleting:\t{0} of {1} ({2})".FormatString(count.ToString("N0"), total.ToString("N0"), (count / (decimal)total).ToString("P2")));
Which causes a lot of flickering.
Try Console.SetCursorPosition. More details here: How can I update the current line in a C# Windows Console App?
static void Main(string[] args)
{
Console.SetCursorPosition(0, 0);
Console.Write("################################");
for (int row = 1; row < 10; row++)
{
Console.SetCursorPosition(0, row);
Console.Write("# #");
}
Console.SetCursorPosition(0, 10);
Console.Write("################################");
int data = 1;
System.Diagnostics.Stopwatch clock = new System.Diagnostics.Stopwatch();
clock.Start();
while (true)
{
data++;
Console.SetCursorPosition(1, 2);
Console.Write("Current Value: " + data.ToString());
Console.SetCursorPosition(1, 3);
Console.Write("Running Time: " + clock.Elapsed.TotalSeconds.ToString());
Thread.Sleep(1000);
}
Console.ReadKey();
}
I know this question is a bit old but I found if you set Console.CursorVisible = false then the flickering stops as well.
Here's a simple working demo that shows multi-line usage without flickering. It shows the current time and a random string every second.
private static void StatusUpdate()
{
var whiteSpace = new StringBuilder();
whiteSpace.Append(' ', 10);
var random = new Random();
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var randomWord = new string(Enumerable.Repeat(chars, random.Next(10)).Select(s => s[random.Next(s.Length)]).ToArray());
while (true)
{
Console.SetCursorPosition(0, 0);
var sb = new StringBuilder();
sb.AppendLine($"Program Status:{whiteSpace}");
sb.AppendLine("-------------------------------");
sb.AppendLine($"Last Updated: {DateTime.Now}{whiteSpace}");
sb.AppendLine($"Random Word: {randomWord}{whiteSpace}");
sb.AppendLine("-------------------------------");
Console.Write(sb);
Thread.Sleep(1000);
}
}
The above example assumes your console window is blank to start. If not, make sure to use Console.Clear() first.
Technical Note:
SetCursorPosition(0,0) places the cursor back to the top (0,0) so the next call to Console.Write will start from line 0, char 0. Note, it doesn't delete the previous content before writing. As an example, if you write "asdf" over a previous line such as "0123456", you'll end up with something like "asdf456" on that line. For that reason, we use a whiteSpace variable to ensure any lingering characters from the previous line are overwritten with blank spaces. Adjust the length of the whiteSpace variable to meet your needs. You only need the whiteSpace variable for lines that change.
Personal Note:
For my purposes, I wanted to show the applications current status (once a second) along with a bunch of other status information and I wanted to avoid any annoying flickering that can happen when you use Console.Clear(). In my application, I run my status updates behind a separate thread so it constantly provides updates even though I have numerous other threads and long running tasks going at the same time.
Credits:
Thanks to previous posters and dtb for the random string generator used in the demo.
How can I generate random alphanumeric strings in C#?
You could try to hack something together using the core libraries.
Rather than waste your time for sub-standard results, I would check out this C# port of the ncurses library (which is a library used for formatting console output):
Curses Sharp
I think you can use \r in Windows console to return the beginning of a line.
You could also use SetCursorPosition.
I would recommend the following extension methods. They allow you to use a StringBuilder to refresh the console view without any flicker, and also tidies up any residual characters on each line
The Problem: The following demo demonstrates using a standard StringBuilder, where updating lines that are shorter than the previously written line get jumbled up. It does this by writing a short string, then a long string on a loop:
public static void Main(string[] args)
{
var switchTextLength = false;
while(true)
{
var sb = new StringBuilder();
if (switchTextLength)
sb.AppendLine("Short msg");
else
sb.AppendLine("Longer message");
sb.UpdateConsole();
switchTextLength = !switchTextLength;
Thread.Sleep(500);
}
}
Result:
The Solution: By using the extension method provided below, the issue is resolved
public static void Main(string[] args)
{
var switchTextLength = false;
while(true)
{
var sb = new StringBuilder();
if (switchTextLength)
sb.AppendLineEx("Short msg");
else
sb.AppendLineEx("Longer message");
sb.UpdateConsole();
switchTextLength = !switchTextLength;
Thread.Sleep(500);
}
}
Result:
Extension Methods:
public static class StringBuilderExtensions
{
/// <summary>
/// Allows StrinbBuilder callers to append a line and blank out the remaining characters for the length of the console buffer width
/// </summary>
public static void AppendLineEx(this StringBuilder c, string msg)
{
// Append the actual line
c.Append(msg);
// Add blanking chars for the rest of the buffer
c.Append(' ', Console.BufferWidth - msg.Length - 1);
// Finish the line
c.Append(Environment.NewLine);
}
/// <summary>
/// Combines two StringBuilders using AppendLineEx
/// </summary>
public static void AppendEx(this StringBuilder c, StringBuilder toAdd)
{
foreach (var line in toAdd.ReadLines())
{
c.AppendLineEx(line);
}
}
/// <summary>
/// Hides the console cursor, resets its position and writes out the string builder
/// </summary>
public static void UpdateConsole(this StringBuilder c)
{
// Ensure the cursor is hidden
if (Console.CursorVisible) Console.CursorVisible = false;
// Reset the cursor position to the top of the console and write out the string builder
Console.SetCursorPosition(0, 0);
Console.WriteLine(c);
}
}
I actually had this issue so I made a quick simple method to try and eliminate this.
static void Clear(string text, int x, int y)
{
char[] textChars = text.ToCharArray();
string newText = "";
//Converts the string you just wrote into a blank string
foreach(char c in textChars)
{
text = text.Replace(c, ' ');
}
newText = text;
//Sets the cursor position
Console.SetCursorPosition(x, y);
//Writes the blank string over the old string
Console.WriteLine(newText);
//Resets cursor position
Console.SetCursorPosition(0, 0);
}
It actually worked surprisingly well and I hope it may work for you!
Naive approach but for simple applications is working:
protected string clearBuffer = null; // Clear this if window size changes
protected void ClearConsole()
{
if (clearBuffer == null)
{
var line = "".PadLeft(Console.WindowWidth, ' ');
var lines = new StringBuilder();
for (var i = 0; i < Console.WindowHeight; i++)
{
lines.AppendLine(line);
}
clearBuffer = lines.ToString();
}
Console.SetCursorPosition(0, 0);
Console.Write(clearBuffer);
Console.SetCursorPosition(0, 0);
}
Console.SetCursorPosition(0, 0); //Instead of Console.Clear();
WriteTitle();
Console.WriteLine();
Console.WriteLine("Deleting:\t{0} of {1} ({2})".FormatString(count.ToString("N0")