2525#include < algorithm>
2626#include < array>
2727#include < format>
28+ #include < vector>
29+ #include < optional>
2830
2931#define NOMINMAX
3032#define WIN32_LEAN_AND_MEAN
@@ -36,35 +38,79 @@ int wmain(int argc, wchar_t* argv[])
3638{
3739 SetConsoleOutputCP (CP_UTF8);
3840
39- if (argc < 2 )
41+ struct Arguments
4042 {
41- PrintLn (L" Usage: " TOOL_NAME L" <dll_path> [<label:" DEFAULT_LABEL L" >]" );
42- return 1 ;
43- }
43+ std::optional<std::wstring> dll;
44+ std::optional<std::wstring> label;
45+ std::vector<std::wstring> ignore;
46+ } arguments;
4447
45- const fs::path dllFilePath = fs::absolute (argv[1 ]);
46- std::wstring labelName = (argc >= 3 ) ? argv[2 ] : (L" " DEFAULT_LABEL);
48+ for (int i = 1 ; i < argc; ++i)
49+ {
50+ std::wstring_view arg = argv[i];
4751
48- if (!fs::exists (dllFilePath) || !fs::is_regular_file (dllFilePath))
52+ if (arg.length () < 2 || !arg.starts_with (L' -' ))
53+ continue ;
54+
55+ std::wstring key;
56+ std::wstring value;
57+
58+ if (const size_t pos = arg.find_first_of (L' :' , 1 ); pos != std::wstring_view::npos && pos > 1 )
59+ {
60+ key = arg.substr (1 , pos - 1 );
61+ value = arg.substr (pos + 1 );
62+ }
63+ else
64+ {
65+ key = arg.substr (1 );
66+ }
67+
68+ Trim (key);
69+ Trim (value);
70+
71+ if (key.empty ())
72+ continue ;
73+
74+ if (!_wcsnicmp (key.c_str (), L" dll" , key.size ()))
75+ {
76+ if (!value.empty () && !arguments.dll .has_value ())
77+ arguments.dll = value;
78+
79+ continue ;
80+ }
81+
82+ if (!_wcsnicmp (key.c_str (), L" label" , key.size ()))
83+ {
84+ if (!value.empty () && !arguments.label .has_value ())
85+ arguments.label = value;
86+
87+ continue ;
88+ }
89+
90+ if (!_wcsnicmp (key.c_str (), L" ignore" , key.size ()))
91+ {
92+ if (!value.empty ())
93+ arguments.ignore .emplace_back (value);
94+
95+ continue ;
96+ }
97+ }
98+
99+ if (!arguments.dll .has_value ())
49100 {
50- PrintErrorLn (L" {} is not a regular file " , dllFilePath. wstring () );
101+ PrintLn (L" Usage: " TOOL_NAME L" -dll:<path> [-label: " DEFAULT_LABEL L" ] [-ignore:<path>] " );
51102 return 1 ;
52103 }
53104
54- Trim (labelName);
105+ const fs::path dllFilePath = fs::absolute (arguments.dll .value ());
106+ std::wstring labelName = arguments.label .value_or (L" " DEFAULT_LABEL);
55107
56- if (labelName. empty ( ))
108+ if (! fs::exists (dllFilePath) || ! fs::is_regular_file (dllFilePath ))
57109 {
58- PrintWarningLn (L" 'label' program argument is empty, using " DEFAULT_LABEL );
59- labelName = ( L" " DEFAULT_LABEL) ;
110+ PrintErrorLn (L" DLL file '{}' does not exist or is not readable " , dllFilePath. wstring () );
111+ return 1 ;
60112 }
61113
62- #ifdef MTA_DEBUG
63- PrintLn (L" [~] " TOOL_NAME L" " TOOL_VERSION);
64- PrintLn (L" [~] Label: '{}'" , labelName);
65- PrintLn (L" [~] DLL: '{}'" , dllFilePath.wstring ());
66- #endif
67-
68114 DllAnalyzer dll;
69115
70116 if (auto [errorCode, hr] = dll.LoadDll (dllFilePath.wstring ()); errorCode != DllAnalyzer::LoadError::None)
@@ -133,7 +179,13 @@ int wmain(int argc, wchar_t* argv[])
133179 }
134180
135181#ifdef MTA_DEBUG
136- PrintLn (L" [~] PDB: '{}'" , pdbFilePath.wstring ());
182+ PrintLn (L" [~] " TOOL_NAME L" " TOOL_VERSION);
183+ PrintLn (L" [~] Label: '{}'" , labelName);
184+ PrintLn (L" [~] DLL: '{}'" , dllFilePath.wstring ());
185+ PrintLn (L" [~] PDB: '{}'" , pdbFilePath.wstring ());
186+
187+ for (const std::wstring& ignore : arguments.ignore )
188+ PrintLn (L" [~] Ignore: '{}'" , ignore);
137189#endif
138190
139191 if (!fs::exists (pdbFilePath) || !fs::is_regular_file (pdbFilePath))
@@ -184,11 +236,40 @@ int wmain(int argc, wchar_t* argv[])
184236 return 1 ;
185237 }
186238
187- const std::vector<PdbAnalyzer::Function>& functions = pdb.GetFunctions ();
239+ std::vector<PdbAnalyzer::Function>& functions = pdb.GetFunctions ();
188240
189241 if (functions.empty ())
190242 return 0 ;
191243
244+ const auto ContainsIgnore = [&ignored = arguments.ignore ](const PdbAnalyzer::Function& f)
245+ {
246+ for (const std::wstring& ignore : ignored)
247+ {
248+ if (StrStrIW (f.SourceFile .c_str (), ignore.c_str ()) != nullptr )
249+ return true ;
250+ }
251+
252+ return false ;
253+ };
254+
255+ functions.erase (std::remove_if (std::begin (functions), std::end (functions), ContainsIgnore), std::end (functions));
256+
257+ const auto SortBySource = [](const PdbAnalyzer::Function& lhs, const PdbAnalyzer::Function& rhs)
258+ {
259+ const auto compare =
260+ std::lexicographical_compare_three_way (std::begin (lhs.SourceFile ), std::end (lhs.SourceFile ), std::begin (rhs.SourceFile ), std::end (rhs.SourceFile ),
261+ [](wchar_t a, wchar_t b) { return std::tolower (a) <=> std::tolower (b); });
262+
263+ if (std::is_lt (compare))
264+ return true ;
265+ if (std::is_gt (compare))
266+ return false ;
267+
268+ return lhs.SourceLine < rhs.SourceLine ;
269+ };
270+
271+ std::sort (std::begin (functions), std::end (functions), SortBySource);
272+
192273 int exitCode = 0 ;
193274
194275 const auto isOurLabel = [&labelName](const PdbAnalyzer::Label& l) { return l.Name == labelName; };
@@ -200,6 +281,9 @@ int wmain(int argc, wchar_t* argv[])
200281
201282 std::vector<std::wstring> problems;
202283
284+ if (!hasLabel && !fs::is_regular_file (function.SourceFile ))
285+ continue ;
286+
203287 if (function.Name .starts_with (L" std::" ) || function.Name .starts_with (L' _' ))
204288 {
205289 if (!hasLabel)
@@ -246,19 +330,8 @@ int wmain(int argc, wchar_t* argv[])
246330 if (problems.empty ())
247331 continue ;
248332
249- if (function.SourceFile .empty ())
250- {
251- PrintLn (L" " );
252- PrintLn (L" [>] {}" , function.Name );
253-
254- for (const std::wstring& problem : problems)
255- PrintLn (L" [-] {}" , problem);
256- }
257- else
258- {
259- for (const std::wstring& problem : problems)
260- PrintLn (L" {}({}): {}" , function.SourceFile , function.SourceLine , problem);
261- }
333+ for (const std::wstring& problem : problems)
334+ PrintLn (L" {}({}): {}" , function.SourceFile , function.SourceLine , problem);
262335 }
263336
264337 return exitCode;
0 commit comments