├── hexed.gif ├── include ├── Error.h ├── Path.h ├── SaveRestoreConsole.h ├── Log.h ├── HelpWindow.h ├── MainWindow.h ├── Colours.h ├── Window.h ├── File.h ├── ConsoleBuffer.h ├── HexView.h └── KeyEvent.h ├── .gitignore ├── source ├── Path.cpp ├── Log.cpp ├── Error.cpp ├── HelpWindow.cpp ├── File.cpp ├── MainWindow.cpp ├── SaveRestoreConsole.cpp ├── Window.cpp ├── main.cpp ├── ConsoleBuffer.cpp └── HexView.cpp ├── README.md ├── hexed.sln ├── LICENSE ├── .github └── workflows │ ├── release.yml │ └── msbuild.yml ├── hexed.vcxproj.filters └── hexed.vcxproj /hexed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samizzo/hexed/HEAD/hexed.gif -------------------------------------------------------------------------------- /include/Error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void Error(const char* message); 4 | -------------------------------------------------------------------------------- /include/Path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class Path 4 | { 5 | public: 6 | static const char* FindFileName(const char* path); 7 | }; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/* 2 | .DS_Store 3 | x86/* 4 | x64/* 5 | *.vtcache 6 | *.user 7 | *.suo 8 | *.sdf 9 | *.opendb 10 | *.db 11 | Debug/* 12 | Release/* 13 | -------------------------------------------------------------------------------- /include/SaveRestoreConsole.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void SaveConsole(HANDLE stdoutHandle, HANDLE stdinHandle); 6 | void RestoreConsole(HANDLE stdoutHandle, HANDLE stdinHandle); 7 | -------------------------------------------------------------------------------- /include/Log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void Log(const char* prefix, const char* format, ...); 4 | 5 | #if _DEBUG 6 | #define LogDebug(...) Log("DEBUG: ", __VA_ARGS__) 7 | #else 8 | #define LogDebug(...) 9 | #endif 10 | -------------------------------------------------------------------------------- /source/Path.cpp: -------------------------------------------------------------------------------- 1 | #include "Path.h" 2 | #include 3 | 4 | const char* Path::FindFileName(const char* path) 5 | { 6 | int length = (int)strlen(path); 7 | const char* p = path + length - 1; 8 | for (int i = length - 1; i >= 0 && *p != '/' && *p != '\\'; i--, p--); 9 | return p + 1; 10 | } 11 | -------------------------------------------------------------------------------- /include/HelpWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Window.h" 4 | 5 | class HelpWindow : public Window 6 | { 7 | public: 8 | virtual void OnWindowRefreshed(); 9 | virtual void OnWindowResized(int width, int height); 10 | virtual void OnKeyEvent(KeyEvent& keyEvent); 11 | 12 | private: 13 | int m_x, m_y; 14 | }; 15 | -------------------------------------------------------------------------------- /include/MainWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Window.h" 4 | #include "HexView.h" 5 | #include "HelpWindow.h" 6 | #include "File.h" 7 | 8 | class MainWindow : public Window 9 | { 10 | public: 11 | MainWindow(File* file); 12 | virtual void OnWindowRefreshed(); 13 | virtual void OnKeyEvent(KeyEvent& keyEvent); 14 | 15 | private: 16 | char m_filename[MAX_PATH]; 17 | HexView m_hexView; 18 | HelpWindow m_helpWindow; 19 | File* m_file; 20 | }; 21 | -------------------------------------------------------------------------------- /source/Log.cpp: -------------------------------------------------------------------------------- 1 | #include "Log.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void Log(const char* prefix, const char* format, ...) 8 | { 9 | va_list args; 10 | printf("%s", prefix); 11 | OutputDebugString(prefix); 12 | va_start(args, format); 13 | int count = _vscprintf(format, args); 14 | char* buffer = (char*)_alloca(count + 1); 15 | vsprintf_s(buffer, count + 1, format, args); 16 | OutputDebugString(buffer); 17 | printf(buffer); 18 | va_end(args); 19 | } 20 | -------------------------------------------------------------------------------- /include/Colours.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define COLOUR(fg, bg) (fg | (bg << 4)) 4 | #define BG(c) (c >> 4) 5 | 6 | #define BACKGROUND COLOUR(0, 0) 7 | 8 | class Colours 9 | { 10 | public: 11 | static const unsigned short Background = BACKGROUND; 12 | static const unsigned short StatusBar = COLOUR(0, 7); 13 | static const unsigned short FunctionButton = COLOUR(0, 6); 14 | static const unsigned short Scrollbar = COLOUR(7, 0); 15 | static const unsigned short HexViewOffsetNormal = COLOUR(11, BG(BACKGROUND)); 16 | static const unsigned short HexViewByteNormal = COLOUR(2, BG(BACKGROUND)); 17 | static const unsigned short HexViewHighlight = COLOUR(0, 14); 18 | static const unsigned short HexViewCharNormal = COLOUR(10, BG(BACKGROUND)); 19 | static const unsigned short Shadow = COLOUR(1, 0); 20 | }; 21 | -------------------------------------------------------------------------------- /source/Error.cpp: -------------------------------------------------------------------------------- 1 | #include "Error.h" 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | extern bool s_running; 9 | 10 | string FormatErrorMessage(DWORD ErrorCode) 11 | { 12 | TCHAR* pMsgBuf = NULL; 13 | DWORD nMsgLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 14 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 15 | NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 16 | reinterpret_cast(&pMsgBuf), 0, NULL); 17 | if (!nMsgLen) 18 | return "FormatMessage failed"; 19 | string msg(pMsgBuf); 20 | LocalFree(pMsgBuf); 21 | return msg; 22 | } 23 | 24 | void Error(const char* message) 25 | { 26 | printf("Error: %s\n", message); 27 | DWORD lastError = GetLastError(); 28 | string errorString = FormatErrorMessage(lastError); 29 | printf("LastError: %s\n", errorString.c_str()); 30 | if (!s_running) 31 | ExitProcess(1); 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hexed - a Windows console-based hex editor 2 | 3 | ![msbuild](https://github.com/samizzo/hexed/actions/workflows/msbuild.yml/badge.svg) 4 | 5 | ## Keyboard shortcuts 6 | 7 | #### Navigation 8 | 9 | * **Left/Right/Up/Down** - Move cursor 10 | 11 | * **h/l/k/j** - Vi-style equivalent of the above 12 | 13 | * **Home/End** - Jump to start/end of current row 14 | 15 | * **Ctrl** + **Home/End** - Jump to start/end of entire file 16 | 17 | * **Page Up/Page Down** - Go back or forward a page at a time 18 | 19 | * **Ctrl** + **Page Up/Page Down** - Skip to first/last row of the currently displayed page while maintaining current column 20 | 21 | #### Editing 22 | 23 | * **Insert/Escape** - Enter/exit edit mode (goes into hex editing by default) 24 | 25 | * **Tab** - Switch between hex editing and ASCII editing 26 | 27 | #### Misc 28 | 29 | * **F5** - Redraw display 30 | 31 | * **Escape** - Quit (when not in edit mode) 32 | 33 | 34 | ## Sample session 35 | ![hexed.gif](/hexed.gif) 36 | -------------------------------------------------------------------------------- /source/HelpWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "HelpWindow.h" 2 | #include "Colours.h" 3 | 4 | void HelpWindow::OnWindowRefreshed() 5 | { 6 | WORD colour = COLOUR(7, 3); 7 | s_consoleBuffer->DrawWindow(m_x, m_y, m_width, m_height, colour); 8 | 9 | /*s_consoleBuffer->Write(m_x + 10, m_y + 8, COLOUR(15, 3), "Colours"); 10 | for (int i = 0; i < 16; i++) 11 | { 12 | int x = (m_x + 10) + ((i >> 3) * 10); 13 | int y = m_y + 10 + (i & 7); 14 | s_consoleBuffer->Write(x, y, i, "%c%c%c%c%c%c%c%c", 219, 219, 219, 219, 219, 219, 219, 219); 15 | }*/ 16 | } 17 | 18 | void HelpWindow::OnWindowResized(int width, int height) 19 | { 20 | int newWidth = (int)(width * 0.8f); 21 | int newHeight = (int)(height * 0.8f); 22 | Window::OnWindowResized(newWidth, newHeight); 23 | 24 | m_x = (width - newWidth) >> 1; 25 | m_y = (height - newHeight) >> 1; 26 | } 27 | 28 | void HelpWindow::OnKeyEvent(KeyEvent& keyEvent) 29 | { 30 | keyEvent.SetHandled(); 31 | if (!keyEvent.IsKeyDown() && keyEvent.GetVKKeyCode() == VK_ESCAPE) 32 | SetVisible(false); 33 | } 34 | -------------------------------------------------------------------------------- /include/Window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ConsoleBuffer.h" 4 | #include "KeyEvent.h" 5 | #include 6 | #include 7 | 8 | class Window 9 | { 10 | public: 11 | Window(); 12 | 13 | virtual void OnWindowRefreshed() = 0; 14 | virtual void OnWindowResized(int width, int height); 15 | virtual void OnKeyEvent(KeyEvent& keyEvent) = 0; 16 | 17 | void SetVisible(bool visible); 18 | bool IsVisible() const; 19 | 20 | static void SetConsoleBuffer(ConsoleBuffer* buffer); 21 | static void Add(Window* window); 22 | static void Resize(int width, int height); 23 | static void Refresh(bool fullDraw); 24 | static void ProcessKeyInput(KeyEvent& keyEvent); 25 | 26 | protected: 27 | int m_width; 28 | int m_height; 29 | 30 | static ConsoleBuffer* s_consoleBuffer; 31 | 32 | private: 33 | enum Flags 34 | { 35 | Flags_Visible = 1 << 0 36 | }; 37 | 38 | unsigned int m_flags; 39 | 40 | static std::vector s_visibleWindows; 41 | }; 42 | -------------------------------------------------------------------------------- /include/File.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class File 6 | { 7 | public: 8 | File(); 9 | ~File(); 10 | 11 | bool Open(const std::string& path); 12 | void Close(); 13 | unsigned int GetSize() const; 14 | void Seek(unsigned int position); 15 | void Read(void* buffer, unsigned int size); 16 | void Write(void* buffer, unsigned int size); 17 | bool IsOpen() const; 18 | bool IsReadOnly() const; 19 | const char* GetFullPath() const; 20 | const char* GetFileName() const; 21 | 22 | private: 23 | char* m_filename; 24 | char* m_fullPath; 25 | unsigned int m_filesize; 26 | void* m_handle; 27 | bool m_readOnly; 28 | }; 29 | 30 | inline unsigned int File::GetSize() const 31 | { 32 | return m_filesize; 33 | } 34 | 35 | inline bool File::IsReadOnly() const 36 | { 37 | return m_readOnly; 38 | } 39 | 40 | inline const char* File::GetFullPath() const 41 | { 42 | return m_fullPath; 43 | } 44 | 45 | inline const char* File::GetFileName() const 46 | { 47 | return m_filename; 48 | } 49 | -------------------------------------------------------------------------------- /hexed.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hexed", "hexed.vcxproj", "{7AB912DC-5910-4D78-9A99-252A60207B94}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {7AB912DC-5910-4D78-9A99-252A60207B94}.Debug|x64.ActiveCfg = Debug|x64 17 | {7AB912DC-5910-4D78-9A99-252A60207B94}.Debug|x64.Build.0 = Debug|x64 18 | {7AB912DC-5910-4D78-9A99-252A60207B94}.Debug|x86.ActiveCfg = Debug|Win32 19 | {7AB912DC-5910-4D78-9A99-252A60207B94}.Debug|x86.Build.0 = Debug|Win32 20 | {7AB912DC-5910-4D78-9A99-252A60207B94}.Release|x64.ActiveCfg = Release|x64 21 | {7AB912DC-5910-4D78-9A99-252A60207B94}.Release|x64.Build.0 = Release|x64 22 | {7AB912DC-5910-4D78-9A99-252A60207B94}.Release|x86.ActiveCfg = Release|Win32 23 | {7AB912DC-5910-4D78-9A99-252A60207B94}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017, Sam Izzo 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /include/ConsoleBuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class ConsoleBuffer 6 | { 7 | public: 8 | ConsoleBuffer(HANDLE stdoutHandle); 9 | ~ConsoleBuffer(); 10 | 11 | bool IsInitialised() const; 12 | void Write(int x, int y, WORD attributes, const char* format, ...); 13 | void SetAttributes(int x, int y, WORD attributes); 14 | void DrawWindow(int x, int y, int width, int height, WORD colour); 15 | void FillLine(int y, char c, WORD attributes); 16 | void FillRect(int x, int y, int width, int height, char c, WORD attributes); 17 | void Clear(WORD clearColour); 18 | void OnWindowResize(int width, int height); 19 | void Flush(bool fullDraw); 20 | 21 | void SetCursor(bool visible, unsigned int size); 22 | 23 | int GetWidth() const; 24 | int GetHeight() const; 25 | 26 | HANDLE GetStdoutHandle() const; 27 | 28 | private: 29 | int m_width; 30 | int m_height; 31 | CHAR_INFO* m_buffer; 32 | CHAR_INFO* m_backBuffer; 33 | HANDLE m_stdoutHandle; 34 | }; 35 | 36 | inline bool ConsoleBuffer::IsInitialised() const 37 | { 38 | return m_buffer != 0; 39 | } 40 | 41 | inline int ConsoleBuffer::GetWidth() const 42 | { 43 | return m_width; 44 | } 45 | 46 | inline int ConsoleBuffer::GetHeight() const 47 | { 48 | return m_height; 49 | } 50 | 51 | inline HANDLE ConsoleBuffer::GetStdoutHandle() const 52 | { 53 | return m_stdoutHandle; 54 | } 55 | -------------------------------------------------------------------------------- /include/HexView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Window.h" 4 | #include "File.h" 5 | 6 | class HexView : public Window 7 | { 8 | public: 9 | HexView(File* file); 10 | ~HexView(); 11 | 12 | virtual void OnWindowRefreshed(); 13 | virtual void OnWindowResized(int newWidth, int newHeight); 14 | virtual void OnKeyEvent(KeyEvent& keyEvent); 15 | 16 | int GetSelectedOffset(); 17 | int GetFileSize(); 18 | 19 | private: 20 | void UpdateCursor(); 21 | void CacheFile(bool resizeBuffer = false); 22 | void WriteBytes(unsigned char ascii); 23 | void WriteChar(unsigned char ascii); 24 | 25 | int GetSelectedLine(); 26 | int GetLastLine(); 27 | int GetBottomLine(); 28 | 29 | unsigned char* m_buffer; 30 | int m_bufferSize; 31 | int m_topLine; 32 | int m_selected; 33 | int m_fileSize; 34 | 35 | File* m_file; 36 | 37 | enum EditMode 38 | { 39 | EditMode_None, 40 | EditMode_Byte, 41 | EditMode_Char 42 | }; 43 | 44 | EditMode m_editMode; 45 | int m_nibbleIndex; 46 | }; 47 | 48 | inline int HexView::GetSelectedLine() 49 | { 50 | return m_selected >> 4; 51 | } 52 | 53 | inline int HexView::GetLastLine() 54 | { 55 | return m_fileSize >> 4; 56 | } 57 | 58 | inline int HexView::GetBottomLine() 59 | { 60 | return m_topLine + m_height - 1; 61 | } 62 | 63 | inline int HexView::GetSelectedOffset() 64 | { 65 | return m_selected; 66 | } 67 | 68 | inline int HexView::GetFileSize() 69 | { 70 | return m_fileSize; 71 | } 72 | -------------------------------------------------------------------------------- /include/KeyEvent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class KeyEvent 4 | { 5 | public: 6 | KeyEvent(bool keyDown, unsigned short vkKeyCode, unsigned short scanCode, unsigned char ascii, unsigned long controlKeyState); 7 | 8 | bool IsKeyDown() const; 9 | bool IsControlKeyDown(int controlKey) const; 10 | 11 | unsigned short GetVKKeyCode() const; 12 | unsigned short GetScanCode() const; 13 | unsigned char GetAscii() const; 14 | 15 | bool WasHandled() const; 16 | void SetHandled(); 17 | 18 | private: 19 | bool m_handled; 20 | bool m_keyDown; 21 | unsigned short m_vkKeyCode; 22 | unsigned short m_scanCode; 23 | unsigned char m_ascii; 24 | unsigned long m_controlKeyState; 25 | }; 26 | 27 | inline KeyEvent::KeyEvent(bool keyDown, unsigned short vkKeyCode, unsigned short scanCode, unsigned char ascii, unsigned long controlKeyState) 28 | { 29 | m_handled = false; 30 | m_keyDown = keyDown; 31 | m_vkKeyCode = vkKeyCode; 32 | m_scanCode = scanCode; 33 | m_ascii = ascii; 34 | m_controlKeyState = controlKeyState; 35 | } 36 | 37 | inline bool KeyEvent::IsKeyDown() const 38 | { 39 | return m_keyDown; 40 | } 41 | 42 | inline unsigned short KeyEvent::GetVKKeyCode() const 43 | { 44 | return m_vkKeyCode; 45 | } 46 | 47 | inline unsigned short KeyEvent::GetScanCode() const 48 | { 49 | return m_scanCode; 50 | } 51 | 52 | inline unsigned char KeyEvent::GetAscii() const 53 | { 54 | return m_ascii; 55 | } 56 | 57 | inline bool KeyEvent::IsControlKeyDown(int controlKey) const 58 | { 59 | return (m_controlKeyState & controlKey) != 0; 60 | } 61 | 62 | inline bool KeyEvent::WasHandled() const 63 | { 64 | return m_handled; 65 | } 66 | 67 | inline void KeyEvent::SetHandled() 68 | { 69 | m_handled = true; 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | name: Upload Release Asset 8 | 9 | jobs: 10 | build: 11 | name: Upload Release Asset 12 | runs-on: windows-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | 17 | - name: Add MSBuild to PATH 18 | uses: microsoft/setup-msbuild@v1.3.1 19 | with: 20 | vs-version: '[17.3,)' 21 | 22 | - name: Build Release x64 23 | working-directory: ${{env.GITHUB_WORKSPACE}} 24 | # Add additional options to the MSBuild command line here (like platform or verbosity level). 25 | # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference 26 | run: msbuild /m /p:Configuration=Release /p:Platform=x64 ${{env.SOLUTION_FILE_PATH}} 27 | 28 | - name: Create Release 29 | id: create_release 30 | uses: actions/create-release@v1 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | with: 34 | tag_name: ${{ github.ref }} 35 | release_name: Release ${{ github.ref }} 36 | draft: false 37 | prerelease: false 38 | 39 | - name: Upload Release Asset 40 | id: upload-release-asset 41 | uses: actions/upload-release-asset@v1 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | with: 45 | upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps 46 | asset_path: x64/Release/hexed.exe 47 | asset_name: hexed.exe 48 | asset_content_type: application/exe -------------------------------------------------------------------------------- /.github/workflows/msbuild.yml: -------------------------------------------------------------------------------- 1 | name: MSBuild 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | # Path to the solution file relative to the root of the project. 11 | SOLUTION_FILE_PATH: . 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | build: 18 | runs-on: windows-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - name: Add MSBuild to PATH 24 | uses: microsoft/setup-msbuild@v1.3.1 25 | with: 26 | vs-version: '[17.3,)' 27 | 28 | - name: Build Debug x86 29 | working-directory: ${{env.GITHUB_WORKSPACE}} 30 | # Add additional options to the MSBuild command line here (like platform or verbosity level). 31 | # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference 32 | run: msbuild /m /p:Configuration=Debug /p:Platform=x86 ${{env.SOLUTION_FILE_PATH}} 33 | 34 | - name: Build Debug x64 35 | working-directory: ${{env.GITHUB_WORKSPACE}} 36 | # Add additional options to the MSBuild command line here (like platform or verbosity level). 37 | # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference 38 | run: msbuild /m /p:Configuration=Debug /p:Platform=x64 ${{env.SOLUTION_FILE_PATH}} 39 | 40 | - name: Build Release x86 41 | working-directory: ${{env.GITHUB_WORKSPACE}} 42 | # Add additional options to the MSBuild command line here (like platform or verbosity level). 43 | # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference 44 | run: msbuild /m /p:Configuration=Release /p:Platform=x86 ${{env.SOLUTION_FILE_PATH}} 45 | 46 | - name: Build Release x64 47 | working-directory: ${{env.GITHUB_WORKSPACE}} 48 | # Add additional options to the MSBuild command line here (like platform or verbosity level). 49 | # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference 50 | run: msbuild /m /p:Configuration=Release /p:Platform=x64 ${{env.SOLUTION_FILE_PATH}} -------------------------------------------------------------------------------- /source/File.cpp: -------------------------------------------------------------------------------- 1 | #include "File.h" 2 | #include "Error.h" 3 | #include 4 | 5 | File::File() 6 | { 7 | m_filename = 0; 8 | m_fullPath = 0; 9 | m_filesize = 0; 10 | m_handle = 0; 11 | } 12 | 13 | File::~File() 14 | { 15 | delete[] m_fullPath; 16 | Close(); 17 | } 18 | 19 | bool File::Open(const std::string& path) 20 | { 21 | m_readOnly = false; 22 | 23 | m_fullPath = new char[MAX_PATH]; 24 | GetFullPathName(path.c_str(), MAX_PATH, m_fullPath, &m_filename); 25 | 26 | DWORD fileAttr = GetFileAttributes(path.c_str()); 27 | if (fileAttr != INVALID_FILE_ATTRIBUTES) 28 | m_readOnly = (fileAttr & FILE_ATTRIBUTE_READONLY) != 0; 29 | 30 | DWORD access = GENERIC_READ; 31 | DWORD shareMode = FILE_SHARE_READ; 32 | if (!m_readOnly) 33 | { 34 | access |= GENERIC_WRITE; 35 | shareMode |= FILE_SHARE_DELETE | FILE_SHARE_WRITE; 36 | } 37 | 38 | m_handle = CreateFile(path.c_str(), access, shareMode, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 39 | if (m_handle != INVALID_HANDLE_VALUE) 40 | m_filesize = GetFileSize(m_handle, 0); 41 | 42 | return m_handle != INVALID_HANDLE_VALUE; 43 | } 44 | 45 | bool File::IsOpen() const 46 | { 47 | return m_handle != INVALID_HANDLE_VALUE; 48 | } 49 | 50 | void File::Close() 51 | { 52 | if (!IsOpen()) 53 | return; 54 | 55 | CloseHandle(m_handle); 56 | m_handle = INVALID_HANDLE_VALUE; 57 | } 58 | 59 | void File::Seek(unsigned int position) 60 | { 61 | if (!m_handle) 62 | return; 63 | 64 | LARGE_INTEGER distance; 65 | distance.QuadPart = position; 66 | if (!SetFilePointerEx(m_handle, distance, 0, FILE_BEGIN)) 67 | Error("SetFilePointerEx failed"); 68 | } 69 | 70 | void File::Read(void* buffer, unsigned int size) 71 | { 72 | if (!m_handle) 73 | return; 74 | 75 | DWORD bytesRead; 76 | ReadFile(m_handle, buffer, size, &bytesRead, 0); 77 | } 78 | 79 | void File::Write(void* buffer, unsigned int size) 80 | { 81 | if (!m_handle) 82 | return; 83 | 84 | DWORD bytesWritten; 85 | if (!WriteFile(m_handle, buffer, size, &bytesWritten, 0)) 86 | Error("WriteFile failed"); 87 | } 88 | -------------------------------------------------------------------------------- /source/MainWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "MainWindow.h" 2 | #include "Path.h" 3 | #include "Colours.h" 4 | #include 5 | 6 | MainWindow::MainWindow(File* file) : 7 | m_hexView(file) 8 | { 9 | m_file = file; 10 | m_helpWindow.SetVisible(false); 11 | } 12 | 13 | void MainWindow::OnWindowRefreshed() 14 | { 15 | // If full path is too long, just show the filename. 16 | unsigned int maxFilename = m_width - 1 - 8 - 3 - 8 - 1 - 4 - 1 - 1; // Header looks like: " [RO] xxxxxxxx / yyyyyyyy " 17 | const char* fullPath = m_file->GetFullPath(); 18 | if (strlen(fullPath) > maxFilename) 19 | { 20 | // If filename is too long, truncate it. 21 | const char* fileName = m_file->GetFileName(); 22 | size_t len = strlen(fileName); 23 | if (len <= maxFilename) 24 | { 25 | strncpy_s(m_filename, MAX_PATH, fileName, len); 26 | } 27 | else 28 | { 29 | len = maxFilename - 2; 30 | strncpy_s(m_filename, MAX_PATH, fileName, len); 31 | strcat_s(m_filename, MAX_PATH, ".."); 32 | } 33 | } 34 | else 35 | { 36 | strncpy_s(m_filename, MAX_PATH, fullPath, _TRUNCATE); 37 | } 38 | 39 | s_consoleBuffer->Clear(Colours::Background); 40 | s_consoleBuffer->FillLine(0, ' ', Colours::StatusBar); 41 | s_consoleBuffer->Write(1, 0, Colours::StatusBar, m_filename); 42 | if (m_file->IsReadOnly()) 43 | { 44 | size_t readOnlyOffset = strlen(m_filename) + 2; 45 | s_consoleBuffer->Write((int)readOnlyOffset, 0, Colours::StatusBar, "[RO]"); 46 | } 47 | 48 | s_consoleBuffer->FillLine(m_height - 1, ' ', Colours::StatusBar); 49 | 50 | int selectedOffset = m_hexView.GetSelectedOffset(); 51 | int fileSize = max(m_hexView.GetFileSize() - 1, 0); 52 | s_consoleBuffer->Write(m_width - 20, 0, Colours::StatusBar, "%08X / %08X", selectedOffset, fileSize); 53 | 54 | //s_consoleBuffer->Write(0, m_height - 1, Colours::FunctionButton, " F1 Help "); 55 | } 56 | 57 | void MainWindow::OnKeyEvent(KeyEvent& keyEvent) 58 | { 59 | unsigned short vkKeyCode = keyEvent.GetVKKeyCode(); 60 | switch (vkKeyCode) 61 | { 62 | case VK_F1: 63 | { 64 | /*if (!keyEvent.IsKeyDown()) 65 | { 66 | m_helpWindow.SetVisible(true); 67 | }*/ 68 | 69 | break; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /source/SaveRestoreConsole.cpp: -------------------------------------------------------------------------------- 1 | #include "SaveRestoreConsole.h" 2 | #include "Error.h" 3 | #include 4 | #include 5 | 6 | static CONSOLE_SCREEN_BUFFER_INFOEX s_prevInfo; 7 | static DWORD s_prevMode; 8 | static CHAR_INFO* s_prevConsoleOutput; 9 | static COORD s_prevSize; 10 | static CONSOLE_CURSOR_INFO s_prevCursor; 11 | 12 | void SaveConsole(HANDLE stdoutHandle, HANDLE stdinHandle) 13 | { 14 | s_prevInfo.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); 15 | if (!GetConsoleScreenBufferInfoEx(stdoutHandle, &s_prevInfo)) 16 | Error("Failed to get console info"); 17 | 18 | if (!GetConsoleMode(stdinHandle, &s_prevMode)) 19 | Error("Couldn't get console mode"); 20 | 21 | // Resize the buffer to the same size as the window. 22 | s_prevSize.X = s_prevInfo.srWindow.Right - s_prevInfo.srWindow.Left + 1; 23 | s_prevSize.Y = s_prevInfo.srWindow.Bottom - s_prevInfo.srWindow.Top + 1; 24 | SetConsoleScreenBufferSize(stdoutHandle, s_prevSize); 25 | 26 | // Save the current output in the newly resized window. 27 | s_prevConsoleOutput = new CHAR_INFO[s_prevSize.X * s_prevSize.Y]; 28 | 29 | COORD coord; 30 | coord.X = 0; 31 | coord.Y = 0; 32 | 33 | SMALL_RECT rect; 34 | rect.Left = 0; 35 | rect.Top = 0; 36 | rect.Right = s_prevSize.X; 37 | rect.Bottom = s_prevSize.Y; 38 | ReadConsoleOutput(stdoutHandle, s_prevConsoleOutput, s_prevSize, coord, &rect); 39 | 40 | GetConsoleCursorInfo(stdoutHandle, &s_prevCursor); 41 | 42 | CONSOLE_CURSOR_INFO info = s_prevCursor; 43 | info.bVisible = FALSE; 44 | SetConsoleCursorInfo(stdoutHandle, &info); 45 | } 46 | 47 | void RestoreConsole(HANDLE stdoutHandle, HANDLE stdinHandle) 48 | { 49 | { 50 | // Restore the old output in the window. 51 | assert(s_prevConsoleOutput != 0); 52 | COORD coord; 53 | coord.X = 0; 54 | coord.Y = 0; 55 | 56 | SMALL_RECT rect; 57 | rect.Left = 0; 58 | rect.Right = s_prevSize.X; 59 | rect.Top = 0; 60 | rect.Bottom = s_prevSize.Y; 61 | WriteConsoleOutput(stdoutHandle, s_prevConsoleOutput, s_prevSize, coord, &rect); 62 | 63 | delete[] s_prevConsoleOutput; 64 | s_prevConsoleOutput = 0; 65 | } 66 | 67 | SetConsoleCursorInfo(stdoutHandle, &s_prevCursor); 68 | 69 | { 70 | SMALL_RECT& rect = s_prevInfo.srWindow; 71 | int width = rect.Right - rect.Left + 1; 72 | int height = rect.Bottom - rect.Top + 1; 73 | 74 | // HACK: Add 1 to work around a bug where the window shrinks otherwise. 75 | rect.Left = 0; 76 | rect.Right = width; 77 | rect.Top = 0; 78 | rect.Bottom = height; 79 | SetConsoleScreenBufferInfoEx(stdoutHandle, &s_prevInfo); 80 | SetConsoleMode(stdinHandle, s_prevMode); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /source/Window.cpp: -------------------------------------------------------------------------------- 1 | #include "Window.h" 2 | 3 | std::vector Window::s_visibleWindows; 4 | ConsoleBuffer* Window::s_consoleBuffer; 5 | 6 | Window::Window() 7 | { 8 | m_width = 0; 9 | m_height = 0; 10 | m_flags = 0; 11 | SetVisible(true); 12 | } 13 | 14 | void Window::SetConsoleBuffer(ConsoleBuffer* buffer) 15 | { 16 | s_consoleBuffer = buffer; 17 | } 18 | 19 | void Window::Add(Window* window) 20 | { 21 | s_visibleWindows.push_back(window); 22 | } 23 | 24 | void Window::Resize(int width, int height) 25 | { 26 | s_consoleBuffer->OnWindowResize(width, height); 27 | for (size_t i = 0; i < s_visibleWindows.size(); i++) 28 | { 29 | Window* window = s_visibleWindows[i]; 30 | if (window->IsVisible()) 31 | window->OnWindowResized(width, height); 32 | } 33 | } 34 | 35 | void Window::Refresh(bool fullDraw) 36 | { 37 | if (!s_consoleBuffer || !s_consoleBuffer->IsInitialised()) 38 | return; 39 | 40 | for (size_t i = 0; i < s_visibleWindows.size(); i++) 41 | { 42 | Window* window = s_visibleWindows[i]; 43 | if (window->IsVisible()) 44 | window->OnWindowRefreshed(); 45 | } 46 | assert(s_consoleBuffer); 47 | s_consoleBuffer->Flush(fullDraw); 48 | } 49 | 50 | void Window::ProcessKeyInput(KeyEvent& keyEvent) 51 | { 52 | // Input is processed in visiblity order. 53 | for (int i = (int)s_visibleWindows.size() - 1; i >= 0; i--) 54 | { 55 | Window* window = s_visibleWindows[i]; 56 | window->OnKeyEvent(keyEvent); 57 | 58 | // If a window handled the event, no other windows will handled it. 59 | if (keyEvent.WasHandled()) 60 | break; 61 | } 62 | } 63 | 64 | void Window::OnWindowResized(int width, int height) 65 | { 66 | m_width = width; 67 | m_height = height; 68 | } 69 | 70 | void Window::SetVisible(bool visible) 71 | { 72 | m_flags = visible ? m_flags | Flags_Visible : m_flags & ~Flags_Visible; 73 | if (IsVisible()) 74 | { 75 | if (s_consoleBuffer && s_consoleBuffer->IsInitialised()) 76 | OnWindowResized(s_consoleBuffer->GetWidth(), s_consoleBuffer->GetHeight()); 77 | 78 | // Remove from the visible list first. 79 | for (size_t i = 0; i < s_visibleWindows.size(); i++) 80 | { 81 | if (s_visibleWindows[i] == this) 82 | { 83 | s_visibleWindows.erase(s_visibleWindows.begin() + i); 84 | break; 85 | } 86 | } 87 | 88 | // Now add to the end. 89 | s_visibleWindows.push_back(this); 90 | } 91 | else 92 | { 93 | // Remove from the visible list. 94 | for (size_t i = 0; i < s_visibleWindows.size(); i++) 95 | { 96 | if (s_visibleWindows[i] == this) 97 | { 98 | s_visibleWindows.erase(s_visibleWindows.begin() + i); 99 | break; 100 | } 101 | } 102 | } 103 | 104 | Window::Refresh(true); 105 | } 106 | 107 | bool Window::IsVisible() const 108 | { 109 | return (m_flags & Flags_Visible) != 0; 110 | } 111 | -------------------------------------------------------------------------------- /hexed.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files 47 | 48 | 49 | Source Files 50 | 51 | 52 | 53 | 54 | Header Files 55 | 56 | 57 | Header Files 58 | 59 | 60 | Header Files 61 | 62 | 63 | Header Files 64 | 65 | 66 | Header Files 67 | 68 | 69 | Header Files 70 | 71 | 72 | Header Files 73 | 74 | 75 | Header Files 76 | 77 | 78 | Header Files 79 | 80 | 81 | Header Files 82 | 83 | 84 | Header Files 85 | 86 | 87 | Header Files 88 | 89 | 90 | -------------------------------------------------------------------------------- /source/main.cpp: -------------------------------------------------------------------------------- 1 | #if defined(_DEBUG) 2 | #define _CRTDBG_MAP_ALLOC 3 | #include 4 | #include 5 | #endif 6 | 7 | #include "MainWindow.h" 8 | #include "ConsoleBuffer.h" 9 | #include 10 | #include 11 | #include "Error.h" 12 | #include "SaveRestoreConsole.h" 13 | #include "Log.h" 14 | 15 | using namespace std; 16 | 17 | bool s_running = false; 18 | 19 | void ProcessInput(const INPUT_RECORD& inputRecord); 20 | void RemapColours(HANDLE stdoutHandle); 21 | 22 | static void DisplayHelp() 23 | { 24 | printf("usage: hexed \n"); 25 | } 26 | 27 | int main(int argc, char** argv) 28 | { 29 | LogDebug("main\n"); 30 | 31 | #if defined(_DEBUG) 32 | _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); 33 | #endif 34 | 35 | std::string fname; 36 | 37 | if (argc != 2) 38 | { 39 | char sshClient[1024] = { 0 }; 40 | char sshConnection[1024] = { 0 }; 41 | GetEnvironmentVariable("SSH_CLIENT", sshClient, sizeof(sshClient)); 42 | GetEnvironmentVariable("SSH_CONNECTION", sshConnection, sizeof(sshConnection)); 43 | if (strlen(sshClient) == 0 && strlen(sshConnection) == 0) 44 | { 45 | OPENFILENAME ofn; 46 | ZeroMemory(&ofn, sizeof ofn); 47 | ofn.lStructSize = sizeof ofn; 48 | ofn.lpstrFilter = "All files\0*.*\0\0"; 49 | ofn.nFilterIndex = 1; 50 | fname.resize(MAX_PATH); 51 | ofn.lpstrFile = fname.data(); 52 | ofn.nMaxFile = MAX_PATH; 53 | ofn.Flags = OFN_FILEMUSTEXIST | OFN_FORCESHOWHIDDEN; 54 | if (GetOpenFileName(&ofn) == FALSE) 55 | { 56 | DisplayHelp(); 57 | return 0; 58 | } 59 | } 60 | else 61 | { 62 | // Running under an ssh connection so don't try to pop up an open file 63 | // dialog. Just display the help text. 64 | DisplayHelp(); 65 | return 0; 66 | } 67 | } 68 | else 69 | { 70 | fname = argv[1]; 71 | } 72 | 73 | File file; 74 | if (!file.Open(fname)) 75 | { 76 | printf("hexed: couldn't open '%s'\n", fname.c_str()); 77 | return 0; 78 | } 79 | 80 | HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); 81 | if (stdinHandle == INVALID_HANDLE_VALUE) 82 | Error("Couldn't get input handle"); 83 | 84 | HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); 85 | if (stdoutHandle == INVALID_HANDLE_VALUE) 86 | Error("Couldn't get output handle"); 87 | 88 | SaveConsole(stdoutHandle, stdinHandle); 89 | 90 | if (!SetConsoleMode(stdinHandle, ENABLE_WINDOW_INPUT)) 91 | Error("Couldn't set console mode"); 92 | 93 | static const int INPUT_BUFFER_SIZE = 128; 94 | INPUT_RECORD inputBuffer[INPUT_BUFFER_SIZE]; 95 | 96 | RemapColours(stdoutHandle); 97 | 98 | int width = 0; 99 | int height = 0; 100 | 101 | ConsoleBuffer buffer(stdoutHandle); 102 | Window::SetConsoleBuffer(&buffer); 103 | MainWindow mainWindow(&file); 104 | 105 | s_running = true; 106 | 107 | while (s_running) 108 | { 109 | CONSOLE_SCREEN_BUFFER_INFO info; 110 | if (GetConsoleScreenBufferInfo(stdoutHandle, &info)) 111 | { 112 | int newWidth = info.dwSize.X; 113 | int newHeight = info.dwSize.Y; 114 | if (newWidth != width || newHeight != height) 115 | { 116 | width = newWidth; 117 | height = newHeight; 118 | Window::Resize(width, height); 119 | Window::Refresh(true); 120 | } 121 | } 122 | 123 | DWORD numEventsAvailable; 124 | GetNumberOfConsoleInputEvents(stdinHandle, &numEventsAvailable); 125 | if (numEventsAvailable > 0) 126 | { 127 | DWORD numEventsRead; 128 | ReadConsoleInput(stdinHandle, inputBuffer, INPUT_BUFFER_SIZE, &numEventsRead); 129 | 130 | for (DWORD i = 0; i < numEventsRead; i++) 131 | { 132 | INPUT_RECORD& e = inputBuffer[i]; 133 | ProcessInput(e); 134 | } 135 | } 136 | } 137 | 138 | RestoreConsole(stdoutHandle, stdoutHandle); 139 | } 140 | 141 | void ProcessInput(const INPUT_RECORD& inputRecord) 142 | { 143 | switch (inputRecord.EventType) 144 | { 145 | case KEY_EVENT: 146 | { 147 | const KEY_EVENT_RECORD& ker = inputRecord.Event.KeyEvent; 148 | KeyEvent keyEvent(ker.bKeyDown == TRUE, ker.wVirtualKeyCode, ker.wVirtualScanCode, ker.uChar.AsciiChar, ker.dwControlKeyState); 149 | Window::ProcessKeyInput(keyEvent); 150 | if (!keyEvent.WasHandled() && ker.wVirtualKeyCode == VK_ESCAPE && !ker.bKeyDown) 151 | s_running = false; 152 | break; 153 | } 154 | } 155 | } 156 | 157 | void RemapColours(HANDLE stdoutHandle) 158 | { 159 | CONSOLE_SCREEN_BUFFER_INFOEX info; 160 | info.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); 161 | if (!GetConsoleScreenBufferInfoEx(stdoutHandle, &info)) 162 | Error("Failed to get console info"); 163 | 164 | // TODO: Configurable colours. 165 | 166 | info.ColorTable[0] = 0x00362b00; 167 | info.ColorTable[1] = 0x00423607; 168 | info.ColorTable[2] = 0x00808000; 169 | info.ColorTable[3] = 0x00a48231; 170 | info.ColorTable[4] = 0x00164bcb; 171 | info.ColorTable[5] = 0x00b6369c; 172 | info.ColorTable[6] = 0x00009985; 173 | info.ColorTable[7] = 0x00d5e8ee; 174 | info.ColorTable[8] = 0x00a1a193; 175 | info.ColorTable[9] = 0x00d28b26; 176 | info.ColorTable[10] = 0x0036b64f; 177 | info.ColorTable[11] = 0x0098a12a; 178 | info.ColorTable[12] = 0x002f32dc; 179 | info.ColorTable[13] = 0x008236d3; 180 | info.ColorTable[14] = 0x000089b5; 181 | info.ColorTable[15] = 0x00e3f6fd; 182 | 183 | // HACK: Workaround for a bug where SetConsoleScreenBufferInfoEx seems to interpret 184 | // the right and bottom as exclusive, so the window size shrinks. 185 | info.srWindow.Right += 1; 186 | info.srWindow.Bottom += 1; 187 | 188 | if (!SetConsoleScreenBufferInfoEx(stdoutHandle, &info)) 189 | Error("Failed to set console info"); 190 | } 191 | -------------------------------------------------------------------------------- /source/ConsoleBuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "ConsoleBuffer.h" 2 | #include "Colours.h" 3 | #include 4 | #include 5 | #include 6 | 7 | ConsoleBuffer::ConsoleBuffer(HANDLE stdoutHandle) 8 | { 9 | m_stdoutHandle = stdoutHandle; 10 | m_width = 0; 11 | m_height = 0; 12 | m_buffer = 0; 13 | m_backBuffer = 0; 14 | } 15 | 16 | ConsoleBuffer::~ConsoleBuffer() 17 | { 18 | delete[] m_buffer; 19 | delete[] m_backBuffer; 20 | } 21 | 22 | void ConsoleBuffer::Write(int x, int y, WORD attributes, const char* format, ...) 23 | { 24 | assert(m_buffer != 0); 25 | assert(m_width > 0); 26 | assert(m_height > 0); 27 | 28 | va_list args; 29 | va_start(args, format); 30 | int count = _vscprintf(format, args); 31 | char* buffer = (char*)_alloca(count + 1); 32 | vsprintf_s(buffer, count + 1, format, args); 33 | 34 | int offset = x + (y * m_width); 35 | for (int i = 0; i < count; i++, offset++) 36 | { 37 | m_buffer[offset].Char.AsciiChar = buffer[i]; 38 | m_buffer[offset].Attributes = attributes; 39 | } 40 | 41 | va_end(args); 42 | } 43 | 44 | void ConsoleBuffer::SetAttributes(int x, int y, WORD attributes) 45 | { 46 | assert(m_buffer != 0); 47 | assert(m_width > 0); 48 | assert(m_height > 0); 49 | 50 | int offset = x + (y * m_width); 51 | m_buffer[offset].Attributes = attributes; 52 | } 53 | 54 | void ConsoleBuffer::DrawWindow(int x, int y, int width, int height, WORD colour) 55 | { 56 | WORD shadowColour = Colours::Shadow; 57 | 58 | for (int j = 0; j < height; j++) 59 | { 60 | for (int i = 0; i < width; i++) 61 | { 62 | unsigned char c = ' '; 63 | 64 | // Horizontal border. 65 | if (j == 0 || j == height - 1) 66 | c = 205; 67 | 68 | Write(i + x, j + y, colour, "%c", c); 69 | 70 | if (j == 0) 71 | { 72 | // Bottom shadow. 73 | SetAttributes(i + x + 2, y + height, shadowColour); 74 | } 75 | } 76 | 77 | // Vertical border. 78 | if (j > 0 && j < height - 1) 79 | { 80 | Write(x, j + y, colour, "%c", 186); 81 | Write(x + width - 1, j + y, colour, "%c", 186); 82 | } 83 | 84 | // Right shadow. 85 | SetAttributes(x + width, j + y + 1, shadowColour); 86 | SetAttributes(x + width + 1, j + y + 1, shadowColour); 87 | } 88 | 89 | // Corners of box. 90 | Write(x, y, colour, "%c", 201); 91 | Write(x + width - 1, y, colour, "%c", 187); 92 | Write(x, y + height - 1, colour, "%c", 200); 93 | Write(x + width - 1, y + height - 1, colour, "%c", 188); 94 | } 95 | 96 | void ConsoleBuffer::FillLine(int y, char c, WORD attributes) 97 | { 98 | assert(m_buffer != 0); 99 | assert(m_width > 0); 100 | assert(m_height > 0); 101 | 102 | int offset = y * m_width; 103 | for (int i = 0; i < m_width; i++, offset++) 104 | { 105 | m_buffer[offset].Char.AsciiChar = c; 106 | m_buffer[offset].Attributes = attributes; 107 | } 108 | } 109 | 110 | void ConsoleBuffer::FillRect(int x, int y, int width, int height, char c, WORD attributes) 111 | { 112 | assert(m_buffer != 0); 113 | assert(m_width > 0); 114 | assert(m_height > 0); 115 | 116 | for (int j = y; j < y + height; j++) 117 | { 118 | for (int i = x; i < x + width; i++) 119 | { 120 | CHAR_INFO& charInfo = m_buffer[i + (j * m_width)]; 121 | charInfo.Char.AsciiChar = c; 122 | charInfo.Attributes = attributes; 123 | } 124 | } 125 | } 126 | 127 | void ConsoleBuffer::Clear(WORD clearColour) 128 | { 129 | assert(m_buffer != 0); 130 | assert(m_width > 0); 131 | assert(m_height > 0); 132 | 133 | for (int i = 0; i < m_width * m_height; i++) 134 | { 135 | CHAR_INFO& info = m_buffer[i]; 136 | info.Char.AsciiChar = ' '; 137 | info.Attributes = clearColour; 138 | } 139 | } 140 | 141 | void ConsoleBuffer::OnWindowResize(int width, int height) 142 | { 143 | delete[] m_buffer; 144 | m_width = width; 145 | m_height = height; 146 | m_buffer = new CHAR_INFO[width * height]; 147 | 148 | delete[] m_backBuffer; 149 | m_width = width; 150 | m_height = height; 151 | m_backBuffer = new CHAR_INFO[width * height]; 152 | } 153 | 154 | void ConsoleBuffer::Flush(bool fullDraw) 155 | { 156 | assert(m_stdoutHandle != 0); 157 | assert(m_buffer != 0); 158 | assert(m_backBuffer); 159 | assert(m_width > 0); 160 | assert(m_height > 0); 161 | 162 | COORD bufferSize; 163 | bufferSize.X = m_width; 164 | bufferSize.Y = m_height; 165 | 166 | COORD bufferCoord; 167 | bufferCoord.X = 0; 168 | bufferCoord.Y = 0; 169 | 170 | SMALL_RECT rect; 171 | rect.Top = 0; 172 | rect.Left = 0; 173 | rect.Bottom = m_height - 1; 174 | rect.Right = m_width - 1; 175 | 176 | if (fullDraw) 177 | { 178 | memcpy(m_backBuffer, m_buffer, m_width * m_height * sizeof(CHAR_INFO)); 179 | WriteConsoleOutput(m_stdoutHandle, m_buffer, bufferSize, bufferCoord, &rect); 180 | } 181 | else 182 | { 183 | int index = 0; 184 | for (int y = 0; y < m_height; y++) 185 | { 186 | for (int x = 0; x < m_width; x++, index++) 187 | { 188 | CHAR_INFO& c = m_buffer[index]; 189 | CHAR_INFO& bc = m_backBuffer[index]; 190 | COORD coord; 191 | coord.X = x; 192 | coord.Y = y; 193 | 194 | //if (c.Attributes != bc.Attributes || c.Char.AsciiChar != bc.Char.AsciiChar) 195 | //{ 196 | // bufferSize.X = 1; 197 | // bufferSize.Y = 1; 198 | // coord.X = 0; 199 | // coord.Y = 0; 200 | // rect.Top = y; 201 | // rect.Left = x; 202 | // rect.Bottom = y; 203 | // rect.Right = x; 204 | // bc.Attributes = c.Attributes; 205 | // bc.Char.AsciiChar = c.Char.AsciiChar; 206 | // WriteConsoleOutput(m_stdoutHandle, &c, bufferSize, coord, &rect); 207 | //} 208 | 209 | if (c.Attributes != bc.Attributes) 210 | { 211 | bc.Attributes = c.Attributes; 212 | DWORD numWritten; 213 | WriteConsoleOutputAttribute(m_stdoutHandle, &bc.Attributes, 1, coord, &numWritten); 214 | } 215 | 216 | if (c.Char.AsciiChar != bc.Char.AsciiChar) 217 | { 218 | bc.Char.AsciiChar = c.Char.AsciiChar; 219 | DWORD numWritten; 220 | WriteConsoleOutputCharacter(m_stdoutHandle, &bc.Char.AsciiChar, 1, coord, &numWritten); 221 | } 222 | } 223 | } 224 | } 225 | } 226 | 227 | void ConsoleBuffer::SetCursor(bool visible, unsigned int size) 228 | { 229 | CONSOLE_CURSOR_INFO info; 230 | info.bVisible = visible; 231 | info.dwSize = size; 232 | SetConsoleCursorInfo(m_stdoutHandle, &info); 233 | } 234 | -------------------------------------------------------------------------------- /hexed.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {7AB912DC-5910-4D78-9A99-252A60207B94} 50 | Win32Proj 51 | hexed 52 | 10.0 53 | 54 | 55 | 56 | Application 57 | true 58 | v143 59 | NotSet 60 | 61 | 62 | Application 63 | false 64 | v143 65 | true 66 | NotSet 67 | 68 | 69 | Application 70 | true 71 | v143 72 | NotSet 73 | 74 | 75 | Application 76 | false 77 | v143 78 | true 79 | NotSet 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | true 101 | $(PlatformShortName)\$(Configuration)\ 102 | $(SolutionDir)$(PlatformShortName)\$(Configuration)\ 103 | 104 | 105 | true 106 | $(SolutionDir)$(PlatformShortName)\$(Configuration)\ 107 | $(PlatformShortName)\$(Configuration)\ 108 | 109 | 110 | false 111 | $(PlatformShortName)\$(Configuration)\ 112 | $(SolutionDir)$(PlatformShortName)\$(Configuration)\ 113 | 114 | 115 | false 116 | $(SolutionDir)$(PlatformShortName)\$(Configuration)\ 117 | $(PlatformShortName)\$(Configuration)\ 118 | 119 | 120 | 121 | 122 | 123 | Level3 124 | Disabled 125 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 126 | $(SolutionDir)include;%(AdditionalIncludeDirectories) 127 | stdcpp17 128 | 129 | 130 | Console 131 | true 132 | 133 | 134 | 135 | 136 | 137 | 138 | Level3 139 | Disabled 140 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 141 | $(SolutionDir)include;%(AdditionalIncludeDirectories) 142 | stdcpp17 143 | 144 | 145 | Console 146 | true 147 | 148 | 149 | 150 | 151 | Level3 152 | 153 | 154 | MaxSpeed 155 | true 156 | true 157 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 158 | $(SolutionDir)include;%(AdditionalIncludeDirectories) 159 | stdcpp17 160 | 161 | 162 | Console 163 | true 164 | true 165 | true 166 | 167 | 168 | 169 | 170 | Level3 171 | 172 | 173 | MaxSpeed 174 | true 175 | true 176 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 177 | $(SolutionDir)include;%(AdditionalIncludeDirectories) 178 | stdcpp17 179 | 180 | 181 | Console 182 | true 183 | true 184 | true 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /source/HexView.cpp: -------------------------------------------------------------------------------- 1 | #include "HexView.h" 2 | #include "Log.h" 3 | #include "Colours.h" 4 | #include 5 | 6 | static const int BYTES_OFFSET = 11; 7 | static const int CHARS_OFFSET = BYTES_OFFSET + (16 * 3); 8 | 9 | HexView::HexView(File* file) 10 | { 11 | m_editMode = EditMode_None; 12 | m_buffer = 0; 13 | m_topLine = 0; 14 | m_selected = 0; 15 | m_fileSize = 0; 16 | m_file = file; 17 | assert(file); 18 | if (m_file->IsOpen()) 19 | m_fileSize = m_file->GetSize(); 20 | 21 | s_consoleBuffer->SetCursor(false, 1); 22 | } 23 | 24 | HexView::~HexView() 25 | { 26 | m_file->Close(); 27 | delete[] m_buffer; 28 | } 29 | 30 | void HexView::OnWindowRefreshed() 31 | { 32 | if (!m_file->IsOpen()) 33 | return; 34 | 35 | assert(m_buffer); 36 | int offset = m_topLine << 4; 37 | int selectedLine = GetSelectedLine(); 38 | bool done = false; 39 | 40 | for (int j = 0; j < m_height; j++) 41 | { 42 | int y = 1 + j; 43 | WORD colour = 0; 44 | 45 | int lastLine = (m_fileSize - 1) >> 4; 46 | int curr = lastLine == 0 ? 0 : GetSelectedLine() * (m_height - 1) / lastLine; 47 | char c = j == curr ? 178 : 176; 48 | s_consoleBuffer->Write(0, y, Colours::Scrollbar, "%c", c); 49 | 50 | if (done) 51 | continue; 52 | 53 | if ((offset >> 4) == selectedLine) 54 | { 55 | // Highlight the selected line's offset text. 56 | colour = Colours::HexViewHighlight; 57 | } 58 | else 59 | { 60 | colour = Colours::HexViewOffsetNormal; 61 | } 62 | 63 | s_consoleBuffer->Write(2, y, colour, "%08X", offset); 64 | 65 | int x = BYTES_OFFSET; 66 | 67 | for (int i = 0; i < 16; i++, offset++) 68 | { 69 | int bufferIndex = offset - (m_topLine << 4); 70 | assert(bufferIndex >= 0 && bufferIndex < m_fileSize); 71 | unsigned char c = m_buffer[bufferIndex]; 72 | 73 | if (offset == m_selected && (m_editMode == EditMode_None || m_editMode == EditMode_Char)) 74 | { 75 | // Highlight the selected byte but only if not in edit mode, 76 | // or if we are in edit mode, then only if we are not editing 77 | // the bytes. 78 | colour = Colours::HexViewHighlight; 79 | } 80 | else 81 | { 82 | colour = Colours::HexViewByteNormal; 83 | } 84 | 85 | int xx = x + (i * 3); 86 | s_consoleBuffer->Write(xx, y, colour, "%02X", c); 87 | 88 | xx = CHARS_OFFSET + i; 89 | if (c < ' ') 90 | c = '.'; 91 | 92 | if (offset == m_selected && (m_editMode == EditMode_None || m_editMode == EditMode_Byte)) 93 | { 94 | // Highlight the selected character but only if not in edit mode, 95 | // or if we are in edit mode, then only if we are not editing the 96 | // bytes. 97 | colour = Colours::HexViewHighlight; 98 | } 99 | else 100 | { 101 | colour = Colours::HexViewCharNormal; 102 | } 103 | s_consoleBuffer->Write(xx, y, colour, "%c", c); 104 | 105 | if ((offset + 1) >= m_fileSize) 106 | { 107 | done = true; 108 | break; 109 | } 110 | } 111 | } 112 | } 113 | 114 | void HexView::OnWindowResized(int width, int height) 115 | { 116 | // Our height is actually smaller. 117 | height -= 2; 118 | Window::OnWindowResized(width, height); 119 | CacheFile(true); 120 | } 121 | 122 | void HexView::CacheFile(bool resizeBuffer) 123 | { 124 | if (!m_file->IsOpen()) 125 | return; 126 | 127 | assert(m_topLine >= 0); 128 | int offset = m_topLine << 4; 129 | assert(offset >= 0 && offset < m_fileSize); 130 | 131 | if (resizeBuffer) 132 | { 133 | assert(m_height >= 0); 134 | int screenSize = m_height << 4; 135 | 136 | delete[] m_buffer; 137 | m_bufferSize = offset + screenSize >= m_fileSize ? m_fileSize - offset : screenSize; 138 | m_buffer = new unsigned char[m_bufferSize]; 139 | memset(m_buffer, 0, m_bufferSize); 140 | } 141 | 142 | m_file->Seek(offset); 143 | m_file->Read(m_buffer, m_bufferSize); 144 | } 145 | 146 | void HexView::OnKeyEvent(KeyEvent& keyEvent) 147 | { 148 | if (!keyEvent.IsKeyDown()) 149 | { 150 | switch (keyEvent.GetVKKeyCode()) 151 | { 152 | case VK_ESCAPE: 153 | { 154 | if (m_editMode == EditMode_None) 155 | break; 156 | 157 | // Escape when in edit mode cancels the edit mode. 158 | keyEvent.SetHandled(); 159 | m_editMode = EditMode_None; 160 | s_consoleBuffer->SetCursor(false, 1); 161 | Window::Refresh(true); 162 | break; 163 | } 164 | } 165 | 166 | return; 167 | } 168 | 169 | bool refresh = true; 170 | bool fullDraw = false; 171 | 172 | unsigned short vkCode = keyEvent.GetVKKeyCode(); 173 | 174 | if (m_editMode == EditMode_Byte) 175 | { 176 | unsigned char ascii = toupper(keyEvent.GetAscii()); 177 | if ((ascii >= 'A' && ascii <= 'F') || (ascii >= '0' && ascii <= '9')) 178 | { 179 | WriteBytes(ascii); 180 | vkCode = VK_RIGHT; 181 | } 182 | } 183 | else if (m_editMode == EditMode_Char) 184 | { 185 | unsigned char ascii = keyEvent.GetAscii(); 186 | if (ascii >= 32 && ascii < 127) 187 | { 188 | WriteChar((unsigned char)ascii); 189 | vkCode = VK_RIGHT; 190 | } 191 | } 192 | 193 | switch (vkCode) 194 | { 195 | case VK_LEFT: 196 | case 'H': 197 | { 198 | if (m_editMode == EditMode_Byte && m_nibbleIndex == 0) 199 | { 200 | // In byte edit mode 201 | m_nibbleIndex = 1; 202 | } 203 | else 204 | { 205 | m_nibbleIndex = 0; 206 | m_selected = max(m_selected - 1, 0); 207 | int selectedLine = GetSelectedLine(); 208 | if (selectedLine < m_topLine) 209 | { 210 | m_topLine--; 211 | assert(m_topLine >= 0); 212 | fullDraw = true; 213 | CacheFile(); 214 | } 215 | } 216 | break; 217 | } 218 | 219 | case VK_RIGHT: 220 | case 'L': 221 | { 222 | if (m_editMode == EditMode_Byte && m_nibbleIndex == 1) 223 | { 224 | m_nibbleIndex = 0; 225 | } 226 | else 227 | { 228 | m_nibbleIndex = 1; 229 | m_selected = max(min(m_selected + 1, m_fileSize - 1), 0); 230 | int selectedLine = GetSelectedLine(); 231 | int bottomLine = GetBottomLine(); 232 | if (selectedLine > bottomLine) 233 | { 234 | m_topLine++; 235 | assert((m_topLine << 4) < m_fileSize); 236 | fullDraw = true; 237 | CacheFile(); 238 | } 239 | } 240 | break; 241 | } 242 | 243 | case VK_UP: 244 | case 'K': 245 | { 246 | refresh = false; 247 | int selectedLine = GetSelectedLine(); 248 | if (selectedLine == 0) 249 | break; 250 | 251 | refresh = true; 252 | m_selected = max(m_selected - 16, 0); 253 | selectedLine = GetSelectedLine(); 254 | if (selectedLine < m_topLine) 255 | { 256 | m_topLine--; 257 | assert(m_topLine >= 0); 258 | fullDraw = true; 259 | CacheFile(); 260 | } 261 | break; 262 | } 263 | 264 | case VK_DOWN: 265 | case 'J': 266 | { 267 | refresh = false; 268 | int selectedLine = GetSelectedLine(); 269 | int lastLine = GetLastLine(); 270 | 271 | // If on the last line, don't move anywhere. 272 | if (selectedLine == lastLine) 273 | break; 274 | 275 | refresh = true; 276 | m_selected = max(min(m_selected + 16, m_fileSize - 1), 0); 277 | selectedLine = GetSelectedLine(); 278 | 279 | int bottomLine = GetBottomLine(); 280 | if (selectedLine > bottomLine) 281 | { 282 | m_topLine++; 283 | assert((m_topLine << 4) < m_fileSize); 284 | fullDraw = true; 285 | CacheFile(); 286 | } 287 | break; 288 | } 289 | 290 | case VK_HOME: 291 | { 292 | if (keyEvent.IsControlKeyDown(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) 293 | { 294 | m_selected = 0; 295 | m_topLine = 0; 296 | fullDraw = true; 297 | CacheFile(); 298 | } 299 | else 300 | { 301 | m_selected &= ~15; 302 | } 303 | break; 304 | } 305 | 306 | case VK_END: 307 | { 308 | if (keyEvent.IsControlKeyDown(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) 309 | { 310 | m_selected = max(m_fileSize - 1, 0); 311 | int selectedLine = GetSelectedLine(); 312 | m_topLine = max(selectedLine - m_height + 1, 0); 313 | fullDraw = true; 314 | CacheFile(); 315 | } 316 | else 317 | { 318 | m_selected = max(min(m_selected | 15, m_fileSize - 1), 0); 319 | } 320 | break; 321 | } 322 | 323 | // Page down. 324 | case VK_NEXT: 325 | { 326 | // Current selection column in the last line. 327 | int lastLineSelected = max(min(((m_fileSize - 1) & ~0xf) | (m_selected & 0xf), m_fileSize - 1), 0); 328 | 329 | if (keyEvent.IsControlKeyDown(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) 330 | { 331 | // Control is down. Go to the bottom of the current page. 332 | int bottomLine = GetBottomLine(); 333 | 334 | // Select the offset at the bottom of the current page. 335 | m_selected = min((bottomLine << 4) | (m_selected & 0xf), lastLineSelected); 336 | } 337 | else 338 | { 339 | int selectedLine = GetSelectedLine(); 340 | 341 | // Get the current distance from the selection to the top line. 342 | int delta = selectedLine - m_topLine; 343 | 344 | // Select the offset at one page down from the current. 345 | m_selected = min(m_selected + (m_height << 4), lastLineSelected); 346 | 347 | // Determine if we need to update the top line. 348 | selectedLine = GetSelectedLine(); 349 | int bottomLine = GetBottomLine(); 350 | if (selectedLine > bottomLine) 351 | { 352 | // Update the top line, but maintain the current selection distance so the cursor 353 | // never moves. 354 | m_topLine = max(selectedLine - delta, 0); 355 | fullDraw = true; 356 | CacheFile(); 357 | } 358 | } 359 | break; 360 | } 361 | 362 | // Page up. 363 | case VK_PRIOR: 364 | { 365 | if (keyEvent.IsControlKeyDown(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) 366 | { 367 | // Control is down. Go to the top of the current page. 368 | // Select the offset at the top of the current page. 369 | m_selected = (m_topLine << 4) | m_selected & 0xf; 370 | } 371 | else 372 | { 373 | int selectedLine = GetSelectedLine(); 374 | 375 | // Get the current distance from the selection to the top line. 376 | int delta = selectedLine - m_topLine; 377 | 378 | // Select the offset at one page up from the current. 379 | m_selected = max(m_selected - (m_height << 4), (m_selected & 0xf)); 380 | 381 | // Determine if we need to update the top line. 382 | selectedLine = GetSelectedLine(); 383 | if (selectedLine < m_topLine) 384 | { 385 | // Update the top line, but maintain the current selection distance so the cursor 386 | // never moves. 387 | m_topLine = max(selectedLine - delta, 0); 388 | fullDraw = true; 389 | CacheFile(); 390 | } 391 | } 392 | break; 393 | } 394 | 395 | case VK_F5: 396 | { 397 | fullDraw = true; 398 | break; 399 | } 400 | 401 | case VK_INSERT: 402 | { 403 | if (m_editMode != EditMode_None || !m_file->IsOpen()) 404 | break; 405 | 406 | m_editMode = EditMode_Byte; 407 | m_nibbleIndex = 1; 408 | s_consoleBuffer->SetCursor(true, 100); 409 | break; 410 | } 411 | 412 | case VK_TAB: 413 | { 414 | if (m_editMode == EditMode_None) 415 | break; 416 | 417 | m_editMode = m_editMode == EditMode_Byte ? EditMode_Char : EditMode_Byte; 418 | break; 419 | } 420 | } 421 | 422 | if (refresh) 423 | { 424 | Window::Refresh(fullDraw); 425 | UpdateCursor(); 426 | } 427 | } 428 | 429 | void HexView::UpdateCursor() 430 | { 431 | if (m_editMode == EditMode_None) 432 | return; 433 | 434 | HANDLE stdoutHandle = s_consoleBuffer->GetStdoutHandle(); 435 | COORD cursorPos; 436 | 437 | if (m_editMode == EditMode_Byte) 438 | { 439 | cursorPos.X = BYTES_OFFSET + ((m_selected & 0xf) * 3) + (m_nibbleIndex ^ 1); 440 | } 441 | else 442 | { 443 | cursorPos.X = CHARS_OFFSET + (m_selected & 0xf); 444 | } 445 | 446 | cursorPos.Y = (GetSelectedLine() - m_topLine) + 1; 447 | SetConsoleCursorPosition(stdoutHandle, cursorPos); 448 | } 449 | 450 | void HexView::WriteBytes(unsigned char ascii) 451 | { 452 | // Write bytes to the buffer. 453 | int bufferIndex = m_selected - (m_topLine << 4); 454 | assert(bufferIndex >= 0 && bufferIndex < m_bufferSize); 455 | 456 | char value = (ascii >= 'A' && ascii <= 'F') ? ascii - 'A' + 10 : ascii - '0'; 457 | unsigned char b = m_buffer[bufferIndex]; 458 | unsigned char mask = 0xf << (4 * m_nibbleIndex); 459 | b = (b & ~mask) | (value << (4 * m_nibbleIndex)); 460 | m_buffer[bufferIndex] = b; 461 | 462 | m_file->Seek(m_selected); 463 | m_file->Write(m_buffer + bufferIndex, 1); 464 | } 465 | 466 | void HexView::WriteChar(unsigned char ascii) 467 | { 468 | // Write chars to the buffer. 469 | int bufferIndex = m_selected - (m_topLine << 4); 470 | assert(bufferIndex >= 0 && bufferIndex < m_bufferSize); 471 | m_buffer[bufferIndex] = ascii; 472 | m_file->Seek(m_selected); 473 | m_file->Write(m_buffer + bufferIndex, 1); 474 | } 475 | --------------------------------------------------------------------------------