2024-01-21 18:39:51 +01:00
# include <imgui.h>
# include <imgui_internal.h>
# include <fonts/codicons_font.h>
# include <hex/ui/imgui_imhex_extensions.h>
# include <hex/api/content_registry.hpp>
# include <hex/api/event_manager.hpp>
# include <hex/api/task_manager.hpp>
# include <hex/api/theme_manager.hpp>
# include <hex/api/tutorial_manager.hpp>
# include <hex/helpers/utils.hpp>
# include <romfs/romfs.hpp>
# include <wolv/hash/uuid.hpp>
# include <wolv/utils/guards.hpp>
# include <list>
2024-01-30 21:20:53 +01:00
# include <numbers>
2024-01-21 18:39:51 +01:00
# include <ranges>
# include <string>
namespace hex : : plugin : : builtin {
namespace {
ImGuiExt : : Texture s_imhexBanner ;
2024-01-30 14:57:36 +01:00
ImGuiExt : : Texture s_compassTexture , s_globeTexture ;
2024-01-21 18:39:51 +01:00
std : : list < std : : pair < std : : fs : : path , ImGuiExt : : Texture > > s_screenshots ;
nlohmann : : json s_screenshotDescriptions ;
std : : string s_uuid ;
class Blend {
public :
Blend ( float start , float end ) : m_time ( 0 ) , m_start ( start ) , m_end ( end ) { }
[[nodiscard]] operator float ( ) {
m_time + = ImGui : : GetIO ( ) . DeltaTime ;
float t = m_time ;
t - = m_start ;
t / = ( m_end - m_start ) ;
t = std : : clamp ( t , 0.0F , 1.0F ) ;
float square = t * t ;
return square / ( 2.0F * ( square - t ) + 1.0F ) ;
}
2024-02-11 00:18:20 +01:00
void reset ( ) {
m_time = 0 ;
}
2024-01-21 18:39:51 +01:00
private :
float m_time ;
float m_start , m_end ;
} ;
EventManager : : EventList : : iterator s_drawEvent ;
void drawOutOfBoxExperience ( ) {
static float windowAlpha = 1.0F ;
static bool oobeDone = false ;
static bool tutorialEnabled = false ;
ImGui : : SetNextWindowPos ( ImHexApi : : System : : getMainWindowPosition ( ) ) ;
ImGui : : SetNextWindowSize ( ImHexApi : : System : : getMainWindowSize ( ) ) ;
ImGui : : PushStyleVar ( ImGuiStyleVar_Alpha , windowAlpha ) ;
ON_SCOPE_EXIT { ImGui : : PopStyleVar ( ) ; } ;
if ( ImGui : : Begin ( " ##oobe " , nullptr , ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize ) ) {
ImGui : : BringWindowToFocusFront ( ImGui : : GetCurrentWindowRead ( ) ) ;
static Blend bannerSlideIn ( - 0.2F , 1.5F ) ;
static Blend bannerFadeIn ( - 0.2F , 1.5F ) ;
// Draw banner
ImGui : : SetCursorPos ( scaled ( { 25 * bannerSlideIn , 25 } ) ) ;
const auto bannerSize = s_imhexBanner . getSize ( ) / ( 1.5F * ( 1.0F / ImHexApi : : System : : getGlobalScale ( ) ) ) ;
ImGui : : Image (
s_imhexBanner ,
bannerSize ,
{ 0 , 0 } , { 1 , 1 } ,
{ 1 , 1 , 1 , ( bannerFadeIn - 0.5F ) * 2.0F }
) ;
static u32 page = 0 ;
switch ( page ) {
// Landing page
case 0 : {
static Blend textFadeIn ( 2.0F , 2.5F ) ;
static Blend buttonFadeIn ( 2.5F , 3.0F ) ;
// Draw welcome text
ImGui : : PushStyleVar ( ImGuiStyleVar_Alpha , textFadeIn ) ;
ImGui : : SameLine ( ) ;
if ( ImGui : : BeginChild ( " Text " , ImVec2 ( ImGui : : GetContentRegionAvail ( ) . x , bannerSize . y ) ) ) {
ImGuiExt : : TextFormattedCentered ( " Welcome to ImHex! \n \n A powerful data analysis and visualization suite for Reverse Engineers, Hackers and Security Researchers. " ) ;
}
ImGui : : EndChild ( ) ;
if ( ! s_screenshots . empty ( ) ) {
const auto imageSize = s_screenshots . front ( ) . second . getSize ( ) * ImHexApi : : System : : getGlobalScale ( ) ;
const auto padding = ImGui : : GetStyle ( ) . CellPadding . x ;
const auto stride = imageSize . x + padding * 2 ;
static bool imageHovered = false ;
static std : : string clickedImage ;
// Move last screenshot to the front of the list when the last screenshot is out of view
static float position = 0 ;
if ( position > = stride ) {
position = 0 ;
s_screenshots . splice ( s_screenshots . begin ( ) , s_screenshots , std : : prev ( s_screenshots . end ( ) ) , s_screenshots . end ( ) ) ;
}
if ( ! imageHovered & & clickedImage . empty ( ) )
position + = ( ImGui : : GetIO ( ) . DeltaTime ) * 40 ;
imageHovered = false ;
auto drawList = ImGui : : GetWindowDrawList ( ) ;
const auto drawImage = [ & ] ( const std : : fs : : path & fileName , const ImGuiExt : : Texture & screenshot ) {
auto pos = ImGui : : GetCursorScreenPos ( ) ;
// Draw image
ImGui : : Image ( screenshot , imageSize ) ;
imageHovered = imageHovered | | ImGui : : IsItemHovered ( ) ;
auto currentHovered = ImGui : : IsItemHovered ( ) ;
if ( ImGui : : IsItemClicked ( ) ) {
clickedImage = fileName . string ( ) ;
ImGui : : OpenPopup ( " FeatureDescription " ) ;
}
// Draw shadow
auto & style = ImGui : : GetStyle ( ) ;
float shadowSize = style . WindowShadowSize * ( currentHovered ? 3.0F : 1.0F ) ;
ImU32 shadowCol = ImGui : : GetColorU32 ( ImGuiCol_WindowShadow , currentHovered ? 2.0F : 1.0F ) ;
ImVec2 shadowOffset = ImVec2 ( ImCos ( style . WindowShadowOffsetAngle ) , ImSin ( style . WindowShadowOffsetAngle ) ) * style . WindowShadowOffsetDist ;
drawList - > AddShadowRect ( pos , pos + imageSize , shadowCol , shadowSize , shadowOffset , ImDrawFlags_ShadowCutOutShapeBackground ) ;
ImGui : : SameLine ( ) ;
} ;
ImGui : : NewLine ( ) ;
2024-01-22 08:25:26 +01:00
u32 repeatCount = std : : ceil ( std : : ceil ( ImHexApi : : System : : getMainWindowSize ( ) . x / stride ) / s_screenshots . size ( ) ) ;
if ( repeatCount = = 0 )
repeatCount = 1 ;
2024-01-21 18:39:51 +01:00
// Draw top screenshot row
ImGui : : SetCursorPosX ( - position ) ;
2024-01-22 08:25:26 +01:00
for ( u32 i = 0 ; i < repeatCount ; i + = 1 ) {
for ( const auto & [ fileName , screenshot ] : s_screenshots | std : : views : : reverse ) {
drawImage ( fileName , screenshot ) ;
}
2024-01-21 18:39:51 +01:00
}
ImGui : : NewLine ( ) ;
// Draw bottom screenshot row
ImGui : : SetCursorPosX ( - stride + position ) ;
2024-01-22 08:25:26 +01:00
for ( u32 i = 0 ; i < repeatCount ; i + = 1 ) {
for ( const auto & [ fileName , screenshot ] : s_screenshots ) {
drawImage ( fileName , screenshot ) ;
}
2024-01-21 18:39:51 +01:00
}
ImGui : : SetNextWindowPos ( ImGui : : GetWindowPos ( ) + ( ImGui : : GetWindowSize ( ) / 2 ) , ImGuiCond_Always , ImVec2 ( 0.5F , 0.5F ) ) ;
ImGui : : SetNextWindowSize ( ImVec2 ( 400 _scaled , 0 ) , ImGuiCond_Always ) ;
if ( ImGui : : BeginPopup ( " FeatureDescription " ) ) {
const auto & description = s_screenshotDescriptions [ clickedImage ] ;
ImGuiExt : : Header ( description [ " title " ] . get < std : : string > ( ) . c_str ( ) , true ) ;
ImGuiExt : : TextFormattedWrapped ( " {} " , description [ " description " ] . get < std : : string > ( ) . c_str ( ) ) ;
ImGui : : EndPopup ( ) ;
} else {
clickedImage . clear ( ) ;
}
// Continue button
const auto buttonSize = scaled ( { 100 , 50 } ) ;
ImGui : : SetCursorPos ( ImHexApi : : System : : getMainWindowSize ( ) - buttonSize - scaled ( { 10 , 10 } ) ) ;
ImGui : : PushStyleVar ( ImGuiStyleVar_Alpha , buttonFadeIn ) ;
if ( ImGuiExt : : DimmedButton ( hex : : format ( " {} {} " , " hex.ui.common.continue " _lang , ICON_VS_ARROW_RIGHT ) . c_str ( ) , buttonSize ) )
page + = 1 ;
ImGui : : PopStyleVar ( ) ;
}
ImGui : : PopStyleVar ( ) ;
break ;
}
2024-01-30 14:57:36 +01:00
// Language selection page
2024-01-21 18:39:51 +01:00
case 1 : {
2024-01-30 14:57:36 +01:00
static const auto & languages = LocalizationManager : : getSupportedLanguages ( ) ;
static auto currLanguage = languages . begin ( ) ;
static float prevTime = 0 ;
ImGui : : NewLine ( ) ;
ImGui : : NewLine ( ) ;
ImGui : : NewLine ( ) ;
ImGui : : NewLine ( ) ;
2024-02-11 00:18:20 +01:00
static Blend textFadeOut ( 2.5F , 2.9F ) ;
static Blend textFadeIn ( 0.1F , 0.5F ) ;
2024-01-30 14:57:36 +01:00
auto currTime = ImGui : : GetTime ( ) ;
if ( ( currTime - prevTime ) > 3 ) {
prevTime = currTime ;
+ + currLanguage ;
2024-02-11 00:18:20 +01:00
textFadeIn . reset ( ) ;
textFadeOut . reset ( ) ;
2024-01-30 14:57:36 +01:00
}
if ( currLanguage = = languages . end ( ) )
currLanguage = languages . begin ( ) ;
// Draw globe image
const auto imageSize = s_compassTexture . getSize ( ) / ( 1.5F * ( 1.0F / ImHexApi : : System : : getGlobalScale ( ) ) ) ;
ImGui : : SetCursorPos ( ( ImGui : : GetWindowSize ( ) / 2 - imageSize / 2 ) - ImVec2 ( 0 , 50 _scaled ) ) ;
ImGui : : Image ( s_globeTexture , imageSize ) ;
ImGui : : NewLine ( ) ;
ImGui : : NewLine ( ) ;
// Draw information text
ImGui : : SetCursorPosX ( 0 ) ;
const auto availableWidth = ImGui : : GetContentRegionAvail ( ) . x ;
if ( ImGui : : BeginChild ( " ##language_text " , ImVec2 ( availableWidth , 30 _scaled ) ) ) {
2024-02-11 00:18:20 +01:00
ImGui : : PushStyleColor ( ImGuiCol_Text , ImGui : : GetColorU32 ( ImGuiCol_Text , textFadeIn - textFadeOut ) ) ;
2024-01-30 14:57:36 +01:00
ImGuiExt : : TextFormattedCentered ( " {} " , LocalizationManager : : getLocalizedString ( " hex.builtin.setting.interface.language " , currLanguage - > first ) ) ;
ImGui : : PopStyleColor ( ) ;
}
ImGui : : EndChild ( ) ;
ImGui : : NewLine ( ) ;
// Draw language selection list
ImGui : : SetCursorPosX ( availableWidth / 3 ) ;
if ( ImGui : : BeginListBox ( " ##language " , ImVec2 ( availableWidth / 3 , 0 ) ) ) {
for ( const auto & [ langId , language ] : LocalizationManager : : getSupportedLanguages ( ) ) {
if ( ImGui : : Selectable ( language . c_str ( ) , langId = = LocalizationManager : : getSelectedLanguage ( ) ) ) {
LocalizationManager : : loadLanguage ( langId ) ;
}
}
ImGui : : EndListBox ( ) ;
}
// Continue button
const auto buttonSize = scaled ( { 100 , 50 } ) ;
ImGui : : SetCursorPos ( ImHexApi : : System : : getMainWindowSize ( ) - buttonSize - scaled ( { 10 , 10 } ) ) ;
if ( ImGuiExt : : DimmedButton ( hex : : format ( " {} {} " , " hex.ui.common.continue " _lang , ICON_VS_ARROW_RIGHT ) . c_str ( ) , buttonSize ) )
page + = 1 ;
break ;
}
// Server contact page
case 2 : {
2024-01-21 18:39:51 +01:00
static ImVec2 subWindowSize = { 0 , 0 } ;
const auto windowSize = ImHexApi : : System : : getMainWindowSize ( ) ;
// Draw telemetry subwindow
ImGui : : SetCursorPos ( ( windowSize - subWindowSize ) / 2 ) ;
ImGuiExt : : BeginSubWindow ( " hex.builtin.oobe.server_contact " _lang , subWindowSize , ImGuiChildFlags_AutoResizeY ) ;
{
// Draw telemetry information
auto yBegin = ImGui : : GetCursorPosY ( ) ;
std : : string message = " hex.builtin.oobe.server_contact.text " _lang ;
ImGuiExt : : TextFormattedWrapped ( " {} " , message . c_str ( ) ) ;
ImGui : : NewLine ( ) ;
// Draw table containing everything that's being reported
if ( ImGui : : CollapsingHeader ( " hex.builtin.oobe.server_contact.data_collected_title " _lang ) ) {
2024-01-25 21:23:03 +01:00
if ( ImGui : : BeginTable ( " hex.builtin.oobe.server_contact.data_collected_table " , 2 ,
2024-01-21 18:39:51 +01:00
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY | ImGuiTableFlags_NoHostExtendY ,
ImVec2 ( ImGui : : GetContentRegionAvail ( ) . x , 150 _scaled ) ) ) {
ImGui : : TableSetupColumn ( " hex.builtin.oobe.server_contact.data_collected_table.key " _lang ) ;
ImGui : : TableSetupColumn ( " hex.builtin.oobe.server_contact.data_collected_table.value " _lang , ImGuiTableColumnFlags_WidthStretch ) ;
ImGui : : TableSetupScrollFreeze ( 0 , 1 ) ;
ImGui : : TableHeadersRow ( ) ;
ImGui : : TableNextRow ( ) ;
ImGui : : TableNextColumn ( ) ;
ImGui : : TextUnformatted ( " hex.builtin.oobe.server_contact.data_collected.uuid " _lang ) ;
ImGui : : TableNextColumn ( ) ;
ImGui : : TextWrapped ( " %s " , s_uuid . c_str ( ) ) ;
ImGui : : TableNextRow ( ) ;
ImGui : : TableNextColumn ( ) ;
ImGui : : TextUnformatted ( " hex.builtin.oobe.server_contact.data_collected.version " _lang ) ;
ImGui : : TableNextColumn ( ) ;
ImGuiExt : : TextFormattedWrapped ( " {} \n {}@{} \n {} " ,
ImHexApi : : System : : getImHexVersion ( ) ,
ImHexApi : : System : : getCommitHash ( true ) ,
ImHexApi : : System : : getCommitBranch ( ) ,
ImHexApi : : System : : isPortableVersion ( ) ? " Portable " : " Installed "
) ;
ImGui : : TableNextRow ( ) ;
ImGui : : TableNextColumn ( ) ;
ImGui : : TextUnformatted ( " hex.builtin.oobe.server_contact.data_collected.os " _lang ) ;
ImGui : : TableNextColumn ( ) ;
ImGuiExt : : TextFormattedWrapped ( " {} \n {} \n {} \n {} " ,
ImHexApi : : System : : getOSName ( ) ,
ImHexApi : : System : : getOSVersion ( ) ,
ImHexApi : : System : : getArchitecture ( ) ,
ImHexApi : : System : : getGPUVendor ( ) ) ;
ImGui : : EndTable ( ) ;
}
}
ImGui : : NewLine ( ) ;
const auto width = ImGui : : GetWindowWidth ( ) ;
const auto buttonSize = ImVec2 ( width / 3 - ImGui : : GetStyle ( ) . FramePadding . x * 3 , 0 ) ;
const auto buttonPos = [ & ] ( u8 index ) { return ImGui : : GetStyle ( ) . FramePadding . x + ( buttonSize . x + ImGui : : GetStyle ( ) . FramePadding . x * 3 ) * index ; } ;
// Draw allow button
ImGui : : SetCursorPosX ( buttonPos ( 0 ) ) ;
if ( ImGui : : Button ( " hex.ui.common.allow " _lang , buttonSize ) ) {
2024-02-03 12:16:36 +01:00
ContentRegistry : : Settings : : write < int > ( " hex.builtin.setting.general " , " hex.builtin.setting.general.server_contact " , 1 ) ;
ContentRegistry : : Settings : : write < int > ( " hex.builtin.setting.general " , " hex.builtin.setting.general.upload_crash_logs " , 1 ) ;
2024-01-21 18:39:51 +01:00
page + = 1 ;
}
ImGui : : SameLine ( ) ;
// Draw crash logs only button
ImGui : : SetCursorPosX ( buttonPos ( 1 ) ) ;
if ( ImGui : : Button ( " hex.builtin.oobe.server_contact.crash_logs_only " _lang , buttonSize ) ) {
2024-02-03 12:16:36 +01:00
ContentRegistry : : Settings : : write < int > ( " hex.builtin.setting.general " , " hex.builtin.setting.general.server_contact " , 0 ) ;
ContentRegistry : : Settings : : write < int > ( " hex.builtin.setting.general " , " hex.builtin.setting.general.upload_crash_logs " , 1 ) ;
2024-01-21 18:39:51 +01:00
page + = 1 ;
}
ImGui : : SameLine ( ) ;
// Draw deny button
ImGui : : SetCursorPosX ( buttonPos ( 2 ) ) ;
if ( ImGui : : Button ( " hex.ui.common.deny " _lang , buttonSize ) ) {
2024-03-13 09:41:04 +01:00
ContentRegistry : : Settings : : write < int > ( " hex.builtin.setting.general " , " hex.builtin.setting.general.server_contact " , 0 ) ;
ContentRegistry : : Settings : : write < int > ( " hex.builtin.setting.general " , " hex.builtin.setting.general.upload_crash_logs " , 0 ) ;
2024-01-21 18:39:51 +01:00
page + = 1 ;
}
auto yEnd = ImGui : : GetCursorPosY ( ) ;
subWindowSize = ImGui : : GetWindowSize ( ) ;
subWindowSize . y = ( yEnd - yBegin ) + 35 _scaled ;
}
ImGuiExt : : EndSubWindow ( ) ;
break ;
}
// Tutorial page
2024-01-30 14:57:36 +01:00
case 3 : {
2024-01-21 18:39:51 +01:00
ImGui : : NewLine ( ) ;
ImGui : : NewLine ( ) ;
ImGui : : NewLine ( ) ;
ImGui : : NewLine ( ) ;
// Draw compass image
const auto imageSize = s_compassTexture . getSize ( ) / ( 1.5F * ( 1.0F / ImHexApi : : System : : getGlobalScale ( ) ) ) ;
ImGui : : SetCursorPos ( ( ImGui : : GetWindowSize ( ) / 2 - imageSize / 2 ) - ImVec2 ( 0 , 50 _scaled ) ) ;
ImGui : : Image ( s_compassTexture , imageSize ) ;
// Draw information text about playing the tutorial
ImGui : : SetCursorPosX ( 0 ) ;
ImGuiExt : : TextFormattedCentered ( " hex.builtin.oobe.tutorial_question " _lang ) ;
// Draw no button
const auto buttonSize = scaled ( { 100 , 50 } ) ;
ImGui : : SetCursorPos ( ImHexApi : : System : : getMainWindowSize ( ) - ImVec2 ( buttonSize . x * 2 + 20 , buttonSize . y + 10 ) ) ;
if ( ImGuiExt : : DimmedButton ( " hex.ui.common.no " _lang , buttonSize ) ) {
oobeDone = true ;
}
// Draw yes button
ImGui : : SetCursorPos ( ImHexApi : : System : : getMainWindowSize ( ) - ImVec2 ( buttonSize . x + 10 , buttonSize . y + 10 ) ) ;
if ( ImGuiExt : : DimmedButton ( " hex.ui.common.yes " _lang , buttonSize ) ) {
tutorialEnabled = true ;
oobeDone = true ;
}
break ;
}
default :
page = 0 ;
}
}
ImGui : : End ( ) ;
// Handle finishing the out of box experience
if ( oobeDone ) {
static Blend backgroundFadeOut ( 0.0F , 1.0F ) ;
windowAlpha = 1.0F - backgroundFadeOut ;
if ( backgroundFadeOut > = 1.0F ) {
if ( tutorialEnabled ) {
TutorialManager : : startTutorial ( " hex.builtin.tutorial.introduction " ) ;
2024-03-28 22:15:48 +01:00
} else {
ContentRegistry : : Settings : : write < bool > ( " hex.builtin.setting.interface " , " hex.builtin.setting.interface.achievement_popup " , false ) ;
2024-01-21 18:39:51 +01:00
}
TaskManager : : doLater ( [ ] {
ImHexApi : : System : : setWindowResizable ( true ) ;
EventFrameBegin : : unsubscribe ( s_drawEvent ) ;
} ) ;
}
}
}
}
void setupOutOfBoxExperience ( ) {
2024-01-26 16:54:27 +01:00
// Don't show the out of box experience in the web version
# if defined(OS_WEB)
return ;
# endif
2024-01-21 18:39:51 +01:00
// Check if there is a telemetry uuid
2024-02-03 12:16:36 +01:00
s_uuid = ContentRegistry : : Settings : : read < std : : string > ( " hex.builtin.setting.general " , " hex.builtin.setting.general.uuid " , " " ) ;
2024-01-21 18:39:51 +01:00
if ( s_uuid . empty ( ) ) {
// Generate a new UUID
s_uuid = wolv : : hash : : generateUUID ( ) ;
// Save UUID to settings
2024-02-03 12:16:36 +01:00
ContentRegistry : : Settings : : write < std : : string > ( " hex.builtin.setting.general " , " hex.builtin.setting.general.uuid " , s_uuid ) ;
2024-01-21 18:39:51 +01:00
}
EventFirstLaunch : : subscribe ( [ ] {
ImHexApi : : System : : setWindowResizable ( false ) ;
const auto imageTheme = ThemeManager : : getImageTheme ( ) ;
2024-05-01 20:36:10 +02:00
s_imhexBanner = ImGuiExt : : Texture : : fromImage ( romfs : : get ( hex : : format ( " assets/{}/banner.png " , imageTheme ) ) . span < std : : byte > ( ) ) ;
s_compassTexture = ImGuiExt : : Texture : : fromImage ( romfs : : get ( " assets/common/compass.png " ) . span < std : : byte > ( ) ) ;
s_globeTexture = ImGuiExt : : Texture : : fromImage ( romfs : : get ( " assets/common/globe.png " ) . span < std : : byte > ( ) ) ;
2024-01-21 18:39:51 +01:00
s_screenshotDescriptions = nlohmann : : json : : parse ( romfs : : get ( " assets/screenshot_descriptions.json " ) . string ( ) ) ;
for ( const auto & path : romfs : : list ( " assets/screenshots " ) ) {
2024-05-01 20:36:10 +02:00
s_screenshots . emplace_back ( path . filename ( ) , ImGuiExt : : Texture : : fromImage ( romfs : : get ( path ) . span < std : : byte > ( ) , ImGuiExt : : Texture : : Filter : : Linear ) ) ;
2024-01-21 18:39:51 +01:00
}
s_drawEvent = EventFrameBegin : : subscribe ( drawOutOfBoxExperience ) ;
} ) ;
}
}