diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..9b3aa8b --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: LLVM diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..70cba51 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tool/gui/QDarkStyleSheet"] + path = tool/gui/QDarkStyleSheet + url = https://github.com/gmh5225/QDarkStyleSheet diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8c9895b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +project(UnknownField-project) + +find_package(LLVM REQUIRED CONFIG) +include_directories(${LLVM_INCLUDE_DIRS}) +link_directories(${LLVM_LIBRARY_DIRS}) +add_definitions(${LLVM_DEFINITIONS}) + +find_package(Clang REQUIRED CONFIG) +include_directories(${CLANG_INCLUDE_DIRS}) +link_directories(${CLANG_LIBRARY_DIRS}) +add_definitions(${CLANG_DEFINITIONS}) + +list(APPEND CMAKE_MODULE_PATH ${LLVM_CMAKE_DIR}) +include(AddLLVM) +list(APPEND CMAKE_MODULE_PATH ${CLANG_CMAKE_DIR}) +include(AddClang) + +if(DEBUG_MODE) +add_definitions(-DDEBUG_MODE) +endif() + +set(CMAKE_CXX_FLAGS_RELEASE "/MT") + +add_library(UnknownField-lib STATIC +sdk/UnknownFieldSDK.h +ObfuscateField.h +ObfuscateField.cpp +) + +target_link_libraries(UnknownField-lib + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangDynamicASTMatchers + clangFrontend + clangTooling + clangSerialization + LLVMLineEditor + LLVMSupport + LLVMFrontendOpenMP +) + +add_subdirectory(tool) + + + + + + + + diff --git a/LICENSE b/LICENSE index 2071b23..8acf865 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,21 @@ MIT License -Copyright (c) +Copyright (c) 2022 gmh -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ObfuscateField.cpp b/ObfuscateField.cpp new file mode 100644 index 0000000..d48f0f4 --- /dev/null +++ b/ObfuscateField.cpp @@ -0,0 +1,13 @@ +#include "ObfuscateField.h" + +std::map> + GlobalFieldNodeVectorMap; +std::map> + GlobalClassFieldDeclStringVectorMap; +std::map GlobalSDKUnknownFieldProtectionEnabledMap; + +llvm::cl::OptionCategory + UnknownFieldOptionCategory("UnknownField OptionCategory"); + +cl::opt GlobalObfucated{"g", cl::desc("Enable global obfucation"), cl::init(false), + cl::cat(UnknownFieldOptionCategory)}; diff --git a/ObfuscateField.h b/ObfuscateField.h new file mode 100644 index 0000000..86fcc5a --- /dev/null +++ b/ObfuscateField.h @@ -0,0 +1,220 @@ +#ifndef _OBFUSCATE_FIELD_H +#define _OBFUSCATE_FIELD_H + +#include +#include +#include +#include +#include +#include + +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Lex/MacroArgs.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::driver; +using namespace clang::tooling; + +#define UnknownFieldProtectionTagName "UnknownFieldProtection" + +extern std::map> + GlobalFieldNodeVectorMap; +extern std::map> + GlobalClassFieldDeclStringVectorMap; +extern std::map GlobalSDKUnknownFieldProtectionEnabledMap; + +extern llvm::cl::OptionCategory UnknownFieldOptionCategory; +extern cl::opt GlobalObfucated; + +class ObfuscateFieldDeclHandler : public MatchFinder::MatchCallback { +public: + ObfuscateFieldDeclHandler(Rewriter &Rewrite) : Rewrite(Rewrite) {} + + virtual void run(const MatchFinder::MatchResult &Result) { + if (auto FDNode = + Result.Nodes.getNodeAs("ObfuscateField")) { + auto FieldStr = Rewrite.getRewrittenText(FDNode->getSourceRange()); + if (FieldStr.empty()) { + // Empty strings are not required + return; + } + + auto QualifiedNameStr = FDNode->getQualifiedNameAsString(); + auto ClassNameStr = QualifiedNameStr.substr( + 0, FDNode->getQualifiedNameAsString().length() - + FDNode->getNameAsString().length() - 2); + + if (FDNode->hasInClassInitializer()) { + // Filter field that in class initializer + GlobalSDKUnknownFieldProtectionEnabledMap[ClassNameStr] = false; + outs() << "Error: Found field(" << FieldStr << ")" + << " that in class(" << ClassNameStr << ")" + << " initializer we don't support!\n "; + exit(-1); + return; + } + + // Save sth that used later. + GlobalFieldNodeVectorMap[ClassNameStr].push_back(FDNode); + GlobalClassFieldDeclStringVectorMap[ClassNameStr].push_back(FieldStr); + } + } + +private: + Rewriter &Rewrite; +}; + +class ObfuscateFieldASTConsumer : public ASTConsumer { +public: + ObfuscateFieldASTConsumer(Rewriter &R) : HandlerForObfuscateFieldDecl(R) { + // Only match our main file.So we should use 'isExpansionInMainFile()' + Matcher.addMatcher( + traverse(TK_IgnoreUnlessSpelledInSource, + fieldDecl(isExpansionInMainFile()).bind("ObfuscateField")), + &HandlerForObfuscateFieldDecl); + } + + void HandleTranslationUnit(ASTContext &Context) override { + Matcher.matchAST(Context); + } + +private: + ObfuscateFieldDeclHandler HandlerForObfuscateFieldDecl; + MatchFinder Matcher; +}; + +class PreprocessorPPCallback : public PPCallbacks { +public: + /// Called by Preprocessor::HandleMacroExpandedIdentifier when a + /// macro invocation is found. + virtual void MacroExpands(const Token &MacroNameTok, + const MacroDefinition &MD, SourceRange Range, + const MacroArgs *Args) { + + if (GlobalObfucated) { + // This is a global tag + return; + } + + auto IdentifierInfo = MacroNameTok.getIdentifierInfo(); + if (!IdentifierInfo) { + return; + } + + if (!Args) { + return; + } + + // We only care about our markers + if (IdentifierInfo->getName().compare(UnknownFieldProtectionTagName) != 0) { + return; + } + + auto UnexpArgument0 = Args->getUnexpArgument(0); + if (!UnexpArgument0) { + return; + } + + auto Arg0IdentifierInfo = UnexpArgument0->getIdentifierInfo(); + if (!Arg0IdentifierInfo) { + return; + } + + StringRef ClassName = Arg0IdentifierInfo->getName(); + GlobalSDKUnknownFieldProtectionEnabledMap[ClassName.str()] = true; + outs() << "Scanning " << UnknownFieldProtectionTagName << " in class(" + << ClassName << ").\n"; + } +}; + +class ObfuscateFieldFrontendAction : public ASTFrontendAction { +private: + auto GenerateRandomKey() { + std::random_device RD; + // Generate seed + std::mt19937 G(RD()); + return G; + } + + bool EnableObfuscated(std::string ClassName) { + if (GlobalObfucated) { + // This is a global tag + return true; + } + + // Find class name that need to be protected. + if (GlobalSDKUnknownFieldProtectionEnabledMap.find(ClassName) != + GlobalSDKUnknownFieldProtectionEnabledMap.end()) { + if (GlobalSDKUnknownFieldProtectionEnabledMap[ClassName]) { + return true; + } + } + + return false; + } + +public: + ObfuscateFieldFrontendAction() {} + void EndSourceFileAction() override { + + // For Field + { + // Traverse map + for (auto &Map : GlobalClassFieldDeclStringVectorMap) { + if (!EnableObfuscated(Map.first)) { + continue; + } + + // Shuffle it + std::shuffle(Map.second.begin(), Map.second.end(), GenerateRandomKey()); + + // Replace it + size_t Count = Map.second.size(); + for (size_t i = 0; i < Count; ++i) { + if (Map.second[i].empty()) { + // Empty strings are not required + continue; + } + auto FDNode = GlobalFieldNodeVectorMap[Map.first][i]; + TheRewriter.ReplaceText(FDNode->getSourceRange(), + Map.second[i].c_str()); + } + } + } + +#ifdef DEBUG_MODE + TheRewriter.getEditBuffer(TheRewriter.getSourceMgr().getMainFileID()) + .write(llvm::outs()); +#else + TheRewriter.overwriteChangedFiles(); +#endif + } + + std::unique_ptr CreateASTConsumer(CompilerInstance &CI, + StringRef file) override { + // Add PPCallbacks + CI.getPreprocessor().addPPCallbacks( + std::make_unique()); + + // New ASTConsumer + TheRewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts()); + return std::make_unique(TheRewriter); + } + +private: + Rewriter TheRewriter; +}; + +#endif diff --git a/README.md b/README.md index 3aec0e5..b30f6a2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,69 @@ # UnknownField +UnknownField is a tool based clang that obfuscating the order of fields to protect your C/C++ game or code. -UnknownField is a tool based clang that obfuscating the order of fields to protect your C/C++ game or code. \ No newline at end of file +![image](images/UnknownField-gui.png) + +## Before +![image](images/UnknownField_before.png) + +## After +![image](images/UnknownField_after.png) + +## Usage +``` +UnknownField-cli.exe +Usage: UnknownField-cli.exe [options] [... ] + +Optional arguments: +-- -I - External include directory +-g - Enable global obfucation +``` + +## Example commands: +```bash +UnknownField-cli.exe test.cpp +UnknownField-cli.exe test.cpp -g +UnknownField-cli.exe test.cpp -- -IE:\External\Directory +UnknownField-cli.exe test.cpp -- -IE:\External\Directory1 -IE:\External\Directory2 +``` + +## Example SDK: +```C++ +#include "sdk/UnknownFieldSDK.h" +#include +class UnknownFieldProtection(MyClassX) { +public: + MyClassX(); + ~MyClassX(); +private: + UCHAR name[300]; + DWORD mp; + DWORD maxmp; + DWORD hp; + DWORD maxhp; + unsigned char level; +}; +``` + +## Usage Dependency +- Visual Studio with SDK10 (without this you could not include windows.h in your file) + +## Build Dependency +- [llvm-msvc](https://github.com/NewWorldComingSoon/llvm-msvc-build/releases) + +## Build +``` +git clone --recurse-submodules https://github.com/NewWorldComingSoon/UnknownField.git +cd UnknownField +mkdir build +cd build +set LLVM-MSVC-BIN=E:\llvm\llvm-msvc-bin -> You need to replace your path. +cmake ../ -DLLVM_DIR=%LLVM-MSVC-BIN%\lib\cmake\llvm -DClang_DIR=%LLVM-MSVC-BIN%\lib\cmake\clang +cmake --build . --config Release -- -m +``` + +## TODO +- Obfuscating the order of virtual functions. + +## Note +This project is currently still a demo. diff --git a/build-release.bat b/build-release.bat new file mode 100644 index 0000000..0d6e970 --- /dev/null +++ b/build-release.bat @@ -0,0 +1,8 @@ +cd ./ +mkdir build-release +cd build-release +cmake ../ -DLLVM_DIR=E:\llvm\llvm-msvc-bin\lib\cmake\llvm -DClang_DIR=E:\llvm\llvm-msvc-bin\lib\cmake\clang +cd ../ + + + diff --git a/images/UnknownField-gui.png b/images/UnknownField-gui.png new file mode 100644 index 0000000..d08d0c1 Binary files /dev/null and b/images/UnknownField-gui.png differ diff --git a/images/UnknownField_after.png b/images/UnknownField_after.png new file mode 100644 index 0000000..a73fcd1 Binary files /dev/null and b/images/UnknownField_after.png differ diff --git a/images/UnknownField_before.png b/images/UnknownField_before.png new file mode 100644 index 0000000..f614cea Binary files /dev/null and b/images/UnknownField_before.png differ diff --git a/sdk/UnknownFieldSDK.h b/sdk/UnknownFieldSDK.h new file mode 100644 index 0000000..18873ca --- /dev/null +++ b/sdk/UnknownFieldSDK.h @@ -0,0 +1,6 @@ +#ifndef _UNKNOWN_FIELD_SDK_H +#define _UNKNOWN_FIELD_SDK_H + +#define UnknownFieldProtection(CLASS) CLASS + +#endif diff --git a/test/test.cpp b/test/test.cpp new file mode 100644 index 0000000..db35d33 --- /dev/null +++ b/test/test.cpp @@ -0,0 +1,49 @@ +#include "../sdk/UnknownFieldSDK.h" +#include + +class UnknownFieldProtection(MyClassX) { +public: + MyClassX(); + ~MyClassX(); +private: + UCHAR name[300]; + DWORD mp; + DWORD maxmp; + DWORD hp; + DWORD maxhp; + unsigned char level; +}; + +class MyClassX2 { +public: + MyClassX2(); + ~MyClassX2(); + +private: + char name2[300]; + int mp2; + int maxmp2; + int hp2; + int maxhp2; + unsigned char level2; +}; + +class MyClassX3 { +public: + MyClassX3(); + ~MyClassX3(); + +private: + char name3[300]; + int mp3; + int maxmp3; + int hp3; + int maxhp3; + unsigned char level3; +}; + +int myfunct(int a, int b) { + int c; + c = a + b; + return c; +} diff --git a/test/test_UTF16_LE.cpp b/test/test_UTF16_LE.cpp new file mode 100644 index 0000000..fbb346a Binary files /dev/null and b/test/test_UTF16_LE.cpp differ diff --git a/test/test_p.cpp b/test/test_p.cpp new file mode 100644 index 0000000..d7caa46 --- /dev/null +++ b/test/test_p.cpp @@ -0,0 +1,20 @@ +#include +#include "../sdk/UnknownFieldSDK.h" + +static unsigned int gv = 0; +class C1 { +public: + C1() { gv = 1; } +}; + +class UnknownFieldProtection(C2) { +public: + C1 c; + unsigned int gv_ = gv; +}; + +int main() { + C2 c2; + printf("c2.gv=%d\n", c2.gv_); + return 0; +} diff --git a/test/test_p/test_p.sln b/test/test_p/test_p.sln new file mode 100644 index 0000000..ed036df --- /dev/null +++ b/test/test_p/test_p.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32228.430 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_p", "test_p.vcxproj", "{EEA027DD-C602-4EFB-9B3A-7FE8D8F2B39C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EEA027DD-C602-4EFB-9B3A-7FE8D8F2B39C}.Debug|x64.ActiveCfg = Debug|x64 + {EEA027DD-C602-4EFB-9B3A-7FE8D8F2B39C}.Debug|x64.Build.0 = Debug|x64 + {EEA027DD-C602-4EFB-9B3A-7FE8D8F2B39C}.Debug|x86.ActiveCfg = Debug|Win32 + {EEA027DD-C602-4EFB-9B3A-7FE8D8F2B39C}.Debug|x86.Build.0 = Debug|Win32 + {EEA027DD-C602-4EFB-9B3A-7FE8D8F2B39C}.Release|x64.ActiveCfg = Release|x64 + {EEA027DD-C602-4EFB-9B3A-7FE8D8F2B39C}.Release|x64.Build.0 = Release|x64 + {EEA027DD-C602-4EFB-9B3A-7FE8D8F2B39C}.Release|x86.ActiveCfg = Release|Win32 + {EEA027DD-C602-4EFB-9B3A-7FE8D8F2B39C}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FEA3B910-2314-4E79-87E6-7E78B8D4C2A6} + EndGlobalSection +EndGlobal diff --git a/test/test_p/test_p.vcxproj b/test/test_p/test_p.vcxproj new file mode 100644 index 0000000..986893c --- /dev/null +++ b/test/test_p/test_p.vcxproj @@ -0,0 +1,147 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {eea027dd-c602-4efb-9b3a-7fe8d8f2b39c} + testp + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/test/test_p/test_p.vcxproj.filters b/test/test_p/test_p.vcxproj.filters new file mode 100644 index 0000000..374e35b --- /dev/null +++ b/test/test_p/test_p.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt new file mode 100644 index 0000000..c7c34dc --- /dev/null +++ b/tool/CMakeLists.txt @@ -0,0 +1,12 @@ + +add_subdirectory(cli) +#add_subdirectory(gui) + + + + + + + + + diff --git a/tool/cli/CMakeLists.txt b/tool/cli/CMakeLists.txt new file mode 100644 index 0000000..ea1500c --- /dev/null +++ b/tool/cli/CMakeLists.txt @@ -0,0 +1,13 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../) + +set(CMAKE_CXX_FLAGS_RELEASE "/MT") + +add_executable(UnknownField-cli + UnknownField-cli.cpp + ) +target_link_libraries(UnknownField-cli + PRIVATE + UnknownField-lib + ) diff --git a/tool/cli/UnknownField-cli.cpp b/tool/cli/UnknownField-cli.cpp new file mode 100644 index 0000000..017735e --- /dev/null +++ b/tool/cli/UnknownField-cli.cpp @@ -0,0 +1,22 @@ + +#include "ObfuscateField.h" + +int main(int argc, const char **argv) { + Expected eOptParser = + clang::tooling::CommonOptionsParser::create( + argc, argv, UnknownFieldOptionCategory, llvm::cl::OneOrMore, nullptr, + false); + if (auto E = eOptParser.takeError()) { + errs() << "Problem constructing CommonOptionsParser " + << toString(std::move(E)) << '\n'; + return EXIT_FAILURE; + } + + clang::tooling::ClangTool Tool(eOptParser->getCompilations(), + eOptParser->getSourcePathList()); + int Ret = Tool.run( + clang::tooling::newFrontendActionFactory() + .get()); + outs() << "UnknownField finished!\n"; + return Ret; +} diff --git a/tool/gui/.gitignore b/tool/gui/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/tool/gui/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/tool/gui/NewWorld.ico b/tool/gui/NewWorld.ico new file mode 100644 index 0000000..d22e4d1 Binary files /dev/null and b/tool/gui/NewWorld.ico differ diff --git a/tool/gui/gui.pro b/tool/gui/gui.pro new file mode 100644 index 0000000..817d618 --- /dev/null +++ b/tool/gui/gui.pro @@ -0,0 +1,43 @@ +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp \ + mainwindow.cpp + +HEADERS += \ + mainwindow.h + +FORMS += \ + mainwindow.ui + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +## +## Name +## +TARGET = UnknownField-gui + +## +## ICO +## +RC_ICONS = NewWorld.ico + +## +## RESOURCES +## +RESOURCES += QDarkStyleSheet/qdarkstyle/dark/style.qrc +RESOURCES += QDarkStyleSheet/qdarkstyle/light/style.qrc +RESOURCES += QDarkStyleSheet/qdarkstyle/X64DBGDark/style.qrc + + diff --git a/tool/gui/main.cpp b/tool/gui/main.cpp new file mode 100644 index 0000000..90d7dcf --- /dev/null +++ b/tool/gui/main.cpp @@ -0,0 +1,26 @@ +#include "mainwindow.h" + +#include +#include +#include + +int main(int argc, char *argv[]) { +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif + + QApplication a(argc, argv); + MainWindow w; + + QFile f(":/qdarkstyle/dark/style.qss"); + if (!f.exists()) { + printf("Unable to set stylesheet, file not found\n"); + } else { + f.open(QFile::ReadOnly | QFile::Text); + QTextStream ts(&f); + qApp->setStyleSheet(ts.readAll()); + } + + w.show(); + return a.exec(); +} diff --git a/tool/gui/mainwindow.cpp b/tool/gui/mainwindow.cpp new file mode 100644 index 0000000..b107fbb --- /dev/null +++ b/tool/gui/mainwindow.cpp @@ -0,0 +1,126 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), ui(new Ui::MainWindow), mQStringFilename("") { + ui->setupUi(this); + + setDefaultDisposition(); + setDefaultComponent(); + + // signal-slot + connect(mQPushButtonRun, SIGNAL(clicked()), this, SLOT(qPushButtonRunSlot())); +} + +MainWindow::~MainWindow() { delete ui; } + +void MainWindow::setDefaultComponent() { + + mQLabelExternalInclude = new QLabel("External include directory"); + ui->verticalLayout_frame_top->addWidget(mQLabelExternalInclude); + mQTextEditExternalInclude = new QTextEdit(); + ui->verticalLayout_frame_top->addWidget(mQTextEditExternalInclude); + + mQLabelSourceFile = new QLabel("Source file"); + ui->verticalLayout_frame_middle->addWidget(mQLabelSourceFile); + + mQTextEditSourceFile = new QTextEdit(); + QFont qFont("Courier", 8); + qFont.setStyleHint(QFont::Monospace); + qFont.setBold(true); + qFont.setFixedPitch(true); + mQTextEditSourceFile->setFont(qFont); + ui->verticalLayout_frame_middle->addWidget(mQTextEditSourceFile); + + mQPushButtonRun = new QPushButton("Run"); + ui->verticalLayout_frame_middle->addWidget(mQPushButtonRun); + + mQLabelLog = new QLabel("Log"); + ui->verticalLayout_frame_bottom->addWidget(mQLabelLog); + mQTextEditLog = new QTextEdit(); + mQTextEditLog->setReadOnly(true); + mQTextEditLog->setUndoRedoEnabled(false); + ui->verticalLayout_frame_bottom->addWidget(mQTextEditLog); +} + +void MainWindow::setDefaultDisposition() { + int iHeight0 = ui->splitter->widget(0)->size().height(); + int iHeight1 = ui->splitter->widget(1)->size().height(); + int iHeight2 = ui->splitter->widget(2)->size().height(); + int iTotalHeight = iHeight0 + iHeight1 + iHeight2; + + QList qListSizes; + // 20% + qListSizes.append(iTotalHeight * 20 / 100); + // 70% + qListSizes.append(iTotalHeight * 70 / 100); + // 10% + qListSizes.append(iTotalHeight * 10 / 100); + ui->splitter->setSizes(qListSizes); +} + +void MainWindow::readSourceFileIntoTextEdit(QString filename) { + // clean content first + mQTextEditSourceFile->setText(QString("")); + + QFile qFile(filename); + if (!qFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::information(nullptr, QString("Error"), + QString("Open file failed!")); + return; + } + + QTextStream qTextStream(&qFile); + while (!qTextStream.atEnd()) { + QString qsLine = qTextStream.readLine(); + mQTextEditSourceFile->append(qsLine); + } +} + +void MainWindow::qPushButtonRunSlot() { + if (mQStringFilename.length() == 0) { + return; + } + + QProcess qProcess; + QString qsCmd = "UnknownField-cli.exe "; + qsCmd += mQStringFilename; + QString qsExternalInclude = mQTextEditExternalInclude->toPlainText(); + if (qsExternalInclude.length() != 0) { + QStringList qsList = + qsExternalInclude.split(QRegExp("[\n|;]"), QString::SkipEmptyParts); + qsCmd += " --"; + for (int i = 0; i < qsList.size(); ++i) { + QString qsInc = qsList[i].trimmed(); + if (qsInc.length() > 0) { + qsCmd += " -I"; + qsCmd += "\""; + qsCmd += qsInc; + qsCmd += "\""; + } + } + } + qProcess.start(qsCmd); + qProcess.waitForFinished(); + mQTextEditLog->append(qProcess.readAllStandardError()); + mQTextEditLog->append(qProcess.readAllStandardOutput()); + readSourceFileIntoTextEdit(mQStringFilename); +} + +void MainWindow::on_actionOpen_triggered() { + QString qsFilename = QFileDialog::getOpenFileName( + this, "Open file", 0, "Source file (*.h *.hpp *.c *.cpp *.cc)"); + if (qsFilename.length() == 0) { + return; + } + qsFilename = QDir::toNativeSeparators(qsFilename); + // Set mQStringFilename + mQStringFilename = qsFilename; + + readSourceFileIntoTextEdit(qsFilename); +} diff --git a/tool/gui/mainwindow.h b/tool/gui/mainwindow.h new file mode 100644 index 0000000..e59f0d5 --- /dev/null +++ b/tool/gui/mainwindow.h @@ -0,0 +1,45 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +class MainWindow : public QMainWindow { + Q_OBJECT +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +public: + void setDefaultDisposition(); + void setDefaultComponent(); + void readSourceFileIntoTextEdit(QString filename); +private slots: + void on_actionOpen_triggered(); + void qPushButtonRunSlot(); + +private: + Ui::MainWindow *ui; + + QString mQStringFilename; + + QLabel *mQLabelExternalInclude; + QTextEdit *mQTextEditExternalInclude; + + QLabel *mQLabelSourceFile; + QTextEdit *mQTextEditSourceFile; + QPushButton *mQPushButtonRun; + + QLabel *mQLabelLog; + QTextEdit *mQTextEditLog; +}; +#endif // MAINWINDOW_H diff --git a/tool/gui/mainwindow.ui b/tool/gui/mainwindow.ui new file mode 100644 index 0000000..175dd57 --- /dev/null +++ b/tool/gui/mainwindow.ui @@ -0,0 +1,83 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + UnknownField + + + + + + + Qt::Vertical + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + + 0 + 0 + 800 + 21 + + + + + File + + + + + + + + + Open + + + F3 + + + + + +