mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
fix: MSVC build support, modern theme, vergilius fnptr import
- CMake: detect MSVC↔MinGW Qt ABI mismatch at configure time (#10) - CMake: add /utf-8 /MP for MSVC builds - CMake: fix theme/example deployment for multi-config generators (MSVC) - Auto-run windeployqt post-build so correct Qt DLLs are always deployed - Add Modern theme (dark blue with cyan/purple/amber accents) - Vergilius import: handle function pointer typedefs
This commit is contained in:
@@ -22,6 +22,32 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS ${_QT_COMPONENTS})
|
||||
set(QT Qt${QT_VERSION_MAJOR})
|
||||
message(STATUS "Using ${QT}: ${${QT}_DIR}")
|
||||
|
||||
# ── ABI sanity check: prevent MSVC ↔ MinGW Qt mismatch ──
|
||||
# Building with MSVC against MinGW Qt (or vice versa) compiles fine but
|
||||
# crashes immediately at runtime (ABI mismatch in QString/QSettings internals).
|
||||
if(MSVC AND "${${QT}_DIR}" MATCHES "mingw")
|
||||
message(FATAL_ERROR
|
||||
"Qt installation was built with MinGW but this project is being compiled with MSVC.\n"
|
||||
" Qt found at: ${${QT}_DIR}\n"
|
||||
"This will compile but crash at startup due to ABI mismatch.\n"
|
||||
"Fix: install Qt for MSVC (e.g. msvc2019_64) and set CMAKE_PREFIX_PATH to it:\n"
|
||||
" cmake -DCMAKE_PREFIX_PATH=C:/Qt/6.5.2/msvc2019_64 ..")
|
||||
elseif(MINGW AND "${${QT}_DIR}" MATCHES "msvc")
|
||||
message(FATAL_ERROR
|
||||
"Qt installation was built with MSVC but this project is being compiled with MinGW.\n"
|
||||
" Qt found at: ${${QT}_DIR}\n"
|
||||
"This will compile but crash at startup due to ABI mismatch.\n"
|
||||
"Fix: install Qt for MinGW and set CMAKE_PREFIX_PATH to it:\n"
|
||||
" cmake -DCMAKE_PREFIX_PATH=C:/Qt/6.5.2/mingw_64 ..")
|
||||
endif()
|
||||
|
||||
# ── MSVC compile flags ──
|
||||
if(MSVC)
|
||||
# /utf-8: treat source and execution character sets as UTF-8
|
||||
# /MP: multi-processor compilation
|
||||
add_compile_options(/utf-8 /MP)
|
||||
endif()
|
||||
|
||||
# Qt5 on Windows needs WinExtras for HICON conversion
|
||||
set(_QT_WINEXTRAS "")
|
||||
if(QT_VERSION_MAJOR EQUAL 5 AND WIN32)
|
||||
@@ -184,13 +210,31 @@ if(APPLE)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Copy built-in theme JSON files to build directory
|
||||
# Copy built-in theme JSON files next to the executable.
|
||||
# For single-config generators (Ninja/Make) the exe is in ${CMAKE_BINARY_DIR},
|
||||
# for multi-config generators (MSVC/Xcode) it's in ${CMAKE_BINARY_DIR}/<config>.
|
||||
# Using a post-build copy with $<TARGET_FILE_DIR:Reclass> handles both.
|
||||
file(GLOB _theme_files "${CMAKE_SOURCE_DIR}/src/themes/defaults/*.json")
|
||||
|
||||
# Single-config: configure_file for IDE convenience (available before first build)
|
||||
if(NOT CMAKE_CONFIGURATION_TYPES)
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/themes")
|
||||
foreach(_tf ${_theme_files})
|
||||
get_filename_component(_name ${_tf} NAME)
|
||||
configure_file(${_tf} "${CMAKE_BINARY_DIR}/themes/${_name}" COPYONLY)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Post-build: always copy to the actual exe directory (works for all generators)
|
||||
add_custom_command(TARGET Reclass POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:Reclass>/themes"
|
||||
COMMENT "Creating themes directory next to executable")
|
||||
foreach(_tf ${_theme_files})
|
||||
get_filename_component(_name ${_tf} NAME)
|
||||
add_custom_command(TARGET Reclass POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${_tf}" "$<TARGET_FILE_DIR:Reclass>/themes/${_name}")
|
||||
endforeach()
|
||||
|
||||
if(APPLE)
|
||||
target_sources(Reclass PRIVATE ${_theme_files})
|
||||
@@ -198,13 +242,26 @@ if(APPLE)
|
||||
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/themes")
|
||||
endif()
|
||||
|
||||
# Copy example .rcx files to build directory
|
||||
# Copy example .rcx files next to the executable (same logic as themes)
|
||||
file(GLOB _example_files "${CMAKE_SOURCE_DIR}/src/examples/*.rcx")
|
||||
|
||||
if(NOT CMAKE_CONFIGURATION_TYPES)
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/examples")
|
||||
foreach(_ef ${_example_files})
|
||||
get_filename_component(_name ${_ef} NAME)
|
||||
configure_file(${_ef} "${CMAKE_BINARY_DIR}/examples/${_name}" COPYONLY)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET Reclass POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:Reclass>/examples"
|
||||
COMMENT "Creating examples directory next to executable")
|
||||
foreach(_ef ${_example_files})
|
||||
get_filename_component(_name ${_ef} NAME)
|
||||
add_custom_command(TARGET Reclass POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${_ef}" "$<TARGET_FILE_DIR:Reclass>/examples/${_name}")
|
||||
endforeach()
|
||||
|
||||
if(APPLE)
|
||||
target_sources(Reclass PRIVATE ${_example_files})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# cmake/deploy.cmake - Dual-mode script for deploying Qt runtime DLLs
|
||||
#
|
||||
# Script mode: cmake -P deploy.cmake <target_exe> <windeployqt>
|
||||
# Include mode: include(deploy) from CMakeLists.txt (creates "deploy" target)
|
||||
# Include mode: include(deploy) from CMakeLists.txt (creates "deploy" target + post-build)
|
||||
|
||||
if(CMAKE_SCRIPT_MODE_FILE)
|
||||
set(TARGET_EXE ${CMAKE_ARGV3})
|
||||
@@ -17,7 +17,6 @@ if(CMAKE_SCRIPT_MODE_FILE)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${WINDEPLOYQT}
|
||||
--pdb
|
||||
--no-compiler-runtime
|
||||
--no-translations
|
||||
--no-opengl-sw
|
||||
@@ -67,6 +66,7 @@ if(NOT TARGET ${QT}::windeployqt AND TARGET ${QT}::qmake)
|
||||
endif()
|
||||
|
||||
if(TARGET ${QT}::windeployqt)
|
||||
# Standalone "deploy" target (can still be invoked manually)
|
||||
add_custom_target(deploy
|
||||
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/deploy.cmake
|
||||
$<TARGET_FILE:Reclass>
|
||||
@@ -79,4 +79,13 @@ if(TARGET ${QT}::windeployqt)
|
||||
set_target_properties(deploy PROPERTIES
|
||||
ADDITIONAL_CLEAN_FILES $<TARGET_FILE_DIR:Reclass>/.qt_deployed
|
||||
)
|
||||
|
||||
# Auto-deploy as post-build step so the correct Qt DLLs are always next
|
||||
# to the exe. Without this, MSVC builds load whatever Qt DLLs happen to
|
||||
# be in PATH (often MinGW ones), causing instant ABI-mismatch crashes.
|
||||
add_custom_command(TARGET Reclass POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/deploy.cmake
|
||||
$<TARGET_FILE:Reclass>
|
||||
$<TARGET_FILE:${QT}::windeployqt>
|
||||
COMMENT "Auto-deploying Qt runtime DLLs...")
|
||||
endif()
|
||||
|
||||
171843
src/examples/WinSDK.rcx
171843
src/examples/WinSDK.rcx
File diff suppressed because it is too large
Load Diff
@@ -332,7 +332,10 @@ struct Parser {
|
||||
QVector<ParsedStruct> structs;
|
||||
QSet<QString> forwardDecls;
|
||||
QHash<QString, QString> typedefs; // alias -> real type
|
||||
QSet<QString> pointerTypedefs; // aliases that are pointer-to-struct
|
||||
QHash<QString, QVector<int>> arrayTypedefs; // aliases that are array types (alias -> dimensions)
|
||||
QHash<QString, int> sizeAsserts; // struct name -> declared size
|
||||
QHash<QString, int> structAlignments; // struct name -> ALIGN(N) value
|
||||
|
||||
explicit Parser(const QVector<Token>& t, const QVector<LineOffset>& lo)
|
||||
: tokens(t), lineOffsets(lo) {}
|
||||
@@ -375,12 +378,57 @@ struct Parser {
|
||||
}
|
||||
}
|
||||
|
||||
// Skip ALIGN( N ) macro if present (Vergilius-style headers)
|
||||
// Returns the alignment value, or 0 if no ALIGN macro.
|
||||
int skipAlignMacro() {
|
||||
if (checkIdent("ALIGN") || checkIdent("__declspec")) {
|
||||
advance();
|
||||
int alignVal = 0;
|
||||
if (match(TokKind::LParen)) {
|
||||
// Try to read the alignment number
|
||||
if (peek().kind == TokKind::Number) {
|
||||
alignVal = peek().text.toInt();
|
||||
}
|
||||
int depth = 1;
|
||||
while (depth > 0 && peek().kind != TokKind::Eof) {
|
||||
if (peek().kind == TokKind::LParen) depth++;
|
||||
else if (peek().kind == TokKind::RParen) depth--;
|
||||
advance();
|
||||
}
|
||||
}
|
||||
return alignVal;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if next tokens after keyword are ALIGN(...) then Ident/LBrace
|
||||
bool peekPastAlign(int offset, TokKind expected) const {
|
||||
int i = cur + offset;
|
||||
if (i < tokens.size() && tokens[i].kind == TokKind::Ident &&
|
||||
(tokens[i].text == QStringLiteral("ALIGN") ||
|
||||
tokens[i].text == QStringLiteral("__declspec"))) {
|
||||
i++; // skip ALIGN
|
||||
if (i < tokens.size() && tokens[i].kind == TokKind::LParen) {
|
||||
int depth = 1; i++;
|
||||
while (i < tokens.size() && depth > 0) {
|
||||
if (tokens[i].kind == TokKind::LParen) depth++;
|
||||
else if (tokens[i].kind == TokKind::RParen) depth--;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return i < tokens.size() && tokens[i].kind == expected;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ── Top-level parse ──
|
||||
|
||||
void parse() {
|
||||
while (peek().kind != TokKind::Eof) {
|
||||
if (checkIdent("struct") || checkIdent("class")) {
|
||||
parseStructOrForward();
|
||||
} else if (checkIdent("union")) {
|
||||
parseTopLevelUnion();
|
||||
} else if (checkIdent("static_assert")) {
|
||||
parseStaticAssert();
|
||||
} else if (checkIdent("typedef")) {
|
||||
@@ -400,6 +448,9 @@ struct Parser {
|
||||
void parseStructOrForward() {
|
||||
QString keyword = advance().text; // "struct" or "class"
|
||||
|
||||
// Skip ALIGN( N ) between keyword and name
|
||||
int alignVal = skipAlignMacro();
|
||||
|
||||
// Anonymous struct: struct { ... }
|
||||
if (check(TokKind::LBrace)) {
|
||||
// Skip anonymous struct at top level
|
||||
@@ -411,6 +462,9 @@ struct Parser {
|
||||
if (!check(TokKind::Ident)) { skipToSemiOrBrace(); return; }
|
||||
QString name = advance().text;
|
||||
|
||||
if (alignVal > 0)
|
||||
structAlignments[name] = alignVal;
|
||||
|
||||
// Check for inheritance: struct Foo : public Bar {
|
||||
// Just skip the inheritance clause
|
||||
if (check(TokKind::Colon)) {
|
||||
@@ -446,14 +500,18 @@ struct Parser {
|
||||
while (peek().kind != TokKind::RBrace && peek().kind != TokKind::Eof) {
|
||||
// Nested struct definition
|
||||
if (checkIdent("struct") || checkIdent("class")) {
|
||||
if (peek(1).kind == TokKind::Ident && peek(2).kind == TokKind::LBrace) {
|
||||
// Check: struct [ALIGN(N)] Name {
|
||||
if ((peek(1).kind == TokKind::Ident && peek(2).kind == TokKind::LBrace) ||
|
||||
peekPastAlign(1, TokKind::Ident)) {
|
||||
// Nested named struct: parse as a top-level struct, then treat as embedded field
|
||||
parseStructOrForward();
|
||||
continue;
|
||||
}
|
||||
if (peek(1).kind == TokKind::LBrace) {
|
||||
// Check: struct [ALIGN(N)] {
|
||||
if (peek(1).kind == TokKind::LBrace || peekPastAlign(1, TokKind::LBrace)) {
|
||||
// Anonymous nested struct { ... } fieldName;
|
||||
advance(); // skip "struct"
|
||||
skipAlignMacro();
|
||||
advance(); // skip "{"
|
||||
// Skip body
|
||||
int depth = 1;
|
||||
@@ -499,9 +557,54 @@ struct Parser {
|
||||
}
|
||||
}
|
||||
|
||||
// Top-level named union definition: union [ALIGN(N)] Name { ... };
|
||||
// Parsed as a struct with classKeyword "union" and all members as fields
|
||||
void parseTopLevelUnion() {
|
||||
advance(); // skip "union"
|
||||
int alignVal = skipAlignMacro();
|
||||
|
||||
// Forward declaration: union Name;
|
||||
if (check(TokKind::Ident) && peek(1).kind == TokKind::Semi) {
|
||||
QString name = advance().text;
|
||||
advance(); // skip ;
|
||||
forwardDecls.insert(name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Anonymous union at top level (skip)
|
||||
if (check(TokKind::LBrace)) {
|
||||
skipToSemiOrBrace();
|
||||
if (check(TokKind::RBrace)) { advance(); match(TokKind::Semi); }
|
||||
return;
|
||||
}
|
||||
|
||||
if (!check(TokKind::Ident)) { skipToSemiOrBrace(); return; }
|
||||
QString name = advance().text;
|
||||
|
||||
if (alignVal > 0)
|
||||
structAlignments[name] = alignVal;
|
||||
|
||||
if (!match(TokKind::LBrace)) { skipToSemiOrBrace(); return; }
|
||||
|
||||
ParsedStruct ps;
|
||||
ps.name = name;
|
||||
ps.keyword = QStringLiteral("union");
|
||||
|
||||
// Parse body — same as struct body but members overlap at offset 0
|
||||
parseStructBody(ps);
|
||||
|
||||
if (!match(TokKind::RBrace)) { skipToSemiOrBrace(); return; }
|
||||
match(TokKind::Semi);
|
||||
|
||||
structs.append(ps);
|
||||
}
|
||||
|
||||
void parseUnion(ParsedStruct& ps) {
|
||||
advance(); // skip "union"
|
||||
|
||||
// Skip ALIGN( N ) between union keyword and name/brace
|
||||
skipAlignMacro();
|
||||
|
||||
// Optional union tag name (before {)
|
||||
if (check(TokKind::Ident) && peek(1).kind == TokKind::LBrace) {
|
||||
advance(); // skip union tag name
|
||||
@@ -525,9 +628,11 @@ struct Parser {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle anonymous struct inside union: struct { ... };
|
||||
if ((checkIdent("struct") || checkIdent("class")) && peek(1).kind == TokKind::LBrace) {
|
||||
// Handle anonymous struct inside union: struct [ALIGN(N)] { ... };
|
||||
if ((checkIdent("struct") || checkIdent("class")) &&
|
||||
(peek(1).kind == TokKind::LBrace || peekPastAlign(1, TokKind::LBrace))) {
|
||||
advance(); // skip "struct"
|
||||
skipAlignMacro();
|
||||
advance(); // skip "{"
|
||||
int depth = 1;
|
||||
while (peek().kind != TokKind::Eof && depth > 0) {
|
||||
@@ -541,9 +646,10 @@ struct Parser {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle nested named struct definition inside union
|
||||
// Handle nested named struct definition inside union: struct [ALIGN(N)] Name {
|
||||
if ((checkIdent("struct") || checkIdent("class")) &&
|
||||
peek(1).kind == TokKind::Ident && peek(2).kind == TokKind::LBrace) {
|
||||
((peek(1).kind == TokKind::Ident && peek(2).kind == TokKind::LBrace) ||
|
||||
peekPastAlign(1, TokKind::Ident))) {
|
||||
parseStructOrForward();
|
||||
continue;
|
||||
}
|
||||
@@ -584,13 +690,26 @@ struct Parser {
|
||||
QString typeName = parseTypeName();
|
||||
if (typeName.isEmpty()) { cur = startPos; return false; }
|
||||
|
||||
// Resolve typedef
|
||||
while (typedefs.contains(typeName))
|
||||
typeName = typedefs[typeName];
|
||||
// Resolve typedef — track pointer and array typedefs in the chain
|
||||
bool typedefPointer = false;
|
||||
QVector<int> typedefArrayDims;
|
||||
{
|
||||
QString resolved = typeName;
|
||||
QSet<QString> seen;
|
||||
while (typedefs.contains(resolved) && !seen.contains(resolved)) {
|
||||
if (pointerTypedefs.contains(resolved))
|
||||
typedefPointer = true;
|
||||
if (typedefArrayDims.isEmpty() && arrayTypedefs.contains(resolved))
|
||||
typedefArrayDims = arrayTypedefs[resolved];
|
||||
seen.insert(resolved);
|
||||
resolved = typedefs[resolved];
|
||||
}
|
||||
typeName = resolved;
|
||||
}
|
||||
|
||||
// Pointer stars
|
||||
bool isPointer = false;
|
||||
int ptrDepth = 0;
|
||||
bool isPointer = typedefPointer;
|
||||
int ptrDepth = typedefPointer ? 1 : 0;
|
||||
while (match(TokKind::Star)) {
|
||||
isPointer = true;
|
||||
ptrDepth++;
|
||||
@@ -628,6 +747,18 @@ struct Parser {
|
||||
match(TokKind::RBracket);
|
||||
}
|
||||
|
||||
// Apply array dimensions from typedef (e.g. typedef ULONG GDI_HANDLE_BUFFER[60])
|
||||
if (!typedefArrayDims.isEmpty()) {
|
||||
if (field.arraySizes.isEmpty())
|
||||
field.arraySizes = typedefArrayDims;
|
||||
else {
|
||||
// Combine: typedef dims come first, field dims appended
|
||||
QVector<int> combined = typedefArrayDims;
|
||||
combined.append(field.arraySizes);
|
||||
field.arraySizes = combined;
|
||||
}
|
||||
}
|
||||
|
||||
// Bitfield: Type name : width
|
||||
if (check(TokKind::Colon)) {
|
||||
advance();
|
||||
@@ -750,28 +881,83 @@ struct Parser {
|
||||
parseStructOrForward();
|
||||
return;
|
||||
}
|
||||
// typedef struct ExistingName AliasName;
|
||||
// typedef struct ExistingName * AliasName;
|
||||
advance(); // skip struct/class
|
||||
if (check(TokKind::Ident)) {
|
||||
QString existingName = advance().text;
|
||||
// Pointer stars
|
||||
while (match(TokKind::Star)) {}
|
||||
bool hasPtr = false;
|
||||
while (match(TokKind::Star)) { hasPtr = true; }
|
||||
// Skip const/volatile after pointer
|
||||
while (checkIdent("const") || checkIdent("volatile")) advance();
|
||||
if (check(TokKind::Ident)) {
|
||||
QString aliasName = advance().text;
|
||||
if (aliasName != existingName) { // skip self-referencing typedefs
|
||||
typedefs[aliasName] = existingName;
|
||||
if (hasPtr) pointerTypedefs.insert(aliasName);
|
||||
}
|
||||
}
|
||||
}
|
||||
match(TokKind::Semi);
|
||||
return;
|
||||
}
|
||||
|
||||
// typedef BaseType AliasName;
|
||||
// typedef BaseType [*] AliasName [N];
|
||||
// Skip leading const/volatile qualifiers: typedef const Type* Alias;
|
||||
while (checkIdent("const") || checkIdent("volatile")) advance();
|
||||
QString baseType = parseTypeName();
|
||||
if (baseType.isEmpty()) { skipToSemiOrBrace(); return; }
|
||||
while (match(TokKind::Star)) {} // pointer typedefs
|
||||
bool hasPtr = false;
|
||||
while (match(TokKind::Star)) { hasPtr = true; }
|
||||
// Skip const/volatile after pointer
|
||||
while (checkIdent("const") || checkIdent("volatile")) advance();
|
||||
while (match(TokKind::Star)) { hasPtr = true; }
|
||||
|
||||
// Function pointer typedef: typedef RetType ( *Name )( args... );
|
||||
if (check(TokKind::LParen)) {
|
||||
int save = cur;
|
||||
advance(); // skip (
|
||||
bool isFnPtr = false;
|
||||
QString fnName;
|
||||
if (match(TokKind::Star) && check(TokKind::Ident)) {
|
||||
fnName = advance().text;
|
||||
if (match(TokKind::RParen) && check(TokKind::LParen)) {
|
||||
isFnPtr = true;
|
||||
}
|
||||
}
|
||||
if (isFnPtr) {
|
||||
// Skip the argument list and register as pointer type
|
||||
skipToSemiOrBrace();
|
||||
pointerTypedefs.insert(fnName);
|
||||
typedefs[fnName] = QStringLiteral("void");
|
||||
} else {
|
||||
cur = save;
|
||||
skipToSemiOrBrace();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (check(TokKind::Ident)) {
|
||||
QString alias = advance().text;
|
||||
// Array dimensions: typedef Type Name[N][M];
|
||||
QVector<int> dims;
|
||||
while (check(TokKind::LBracket)) {
|
||||
advance();
|
||||
if (check(TokKind::Number)) {
|
||||
bool ok;
|
||||
QString numText = peek().text;
|
||||
int val = numText.startsWith(QStringLiteral("0x"), Qt::CaseInsensitive)
|
||||
? numText.mid(2).toInt(&ok, 16) : numText.toInt(&ok);
|
||||
if (ok) dims.append(val);
|
||||
advance();
|
||||
}
|
||||
match(TokKind::RBracket);
|
||||
}
|
||||
if (alias != baseType) { // skip self-referencing typedefs
|
||||
typedefs[alias] = baseType;
|
||||
if (hasPtr) pointerTypedefs.insert(alias);
|
||||
if (!dims.isEmpty()) arrayTypedefs[alias] = dims;
|
||||
}
|
||||
}
|
||||
match(TokKind::Semi);
|
||||
}
|
||||
@@ -945,8 +1131,62 @@ struct BuildContext {
|
||||
bool useCommentOffsets;
|
||||
QSet<QString> enumNames; // enum type names (emit as UInt32 + refId)
|
||||
int ptrSize = 8; // target pointer size (4 or 8)
|
||||
const QHash<QString, int>& sizeAsserts; // declared struct sizes from static_assert
|
||||
const QHash<QString, int>& structAlignments; // struct name -> ALIGN(N) value
|
||||
};
|
||||
|
||||
// Forward declaration
|
||||
static int fieldNaturalAlignment(const ParsedField& field, const BuildContext& ctx);
|
||||
|
||||
// Compute natural alignment for a union from its members (max member alignment)
|
||||
static int unionNaturalAlignment(const ParsedField& field, const BuildContext& ctx) {
|
||||
int maxAlign = 1;
|
||||
for (const auto& member : field.unionMembers) {
|
||||
int a = fieldNaturalAlignment(member, ctx);
|
||||
if (a > maxAlign) maxAlign = a;
|
||||
}
|
||||
return maxAlign;
|
||||
}
|
||||
|
||||
// Return natural alignment for a parsed field (used when computing offsets without comments)
|
||||
static int fieldNaturalAlignment(const ParsedField& field, const BuildContext& ctx) {
|
||||
if (field.isPointer) return ctx.ptrSize;
|
||||
if (field.isUnion) return unionNaturalAlignment(field, ctx);
|
||||
if (field.bitfieldWidth >= 0) {
|
||||
// Bitfield alignment is determined by its storage type
|
||||
auto it = ctx.typeTable.find(field.typeName);
|
||||
if (it != ctx.typeTable.end()) return alignmentFor(it->kind);
|
||||
return 4; // default bitfield alignment
|
||||
}
|
||||
auto it = ctx.typeTable.find(field.typeName);
|
||||
if (it != ctx.typeTable.end()) return alignmentFor(it->kind);
|
||||
// Unknown type (struct reference) — align to pointer size
|
||||
return ctx.ptrSize;
|
||||
}
|
||||
|
||||
static inline int alignUp(int offset, int align) {
|
||||
return (offset + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
// Look up the byte size of a struct type (from already-built tree or static_assert declarations)
|
||||
static int structTypeSize(const QString& typeName, const BuildContext& ctx) {
|
||||
auto classIt = ctx.classIds.find(typeName);
|
||||
if (classIt != ctx.classIds.end()) {
|
||||
int span = ctx.tree.structSpan(classIt.value());
|
||||
if (span > 0) {
|
||||
// Pad to struct's declared alignment (ALIGN(N))
|
||||
auto alignIt = ctx.structAlignments.find(typeName);
|
||||
if (alignIt != ctx.structAlignments.end() && *alignIt > 1)
|
||||
span = alignUp(span, *alignIt);
|
||||
return span;
|
||||
}
|
||||
}
|
||||
auto sizeIt = ctx.sizeAsserts.find(typeName);
|
||||
if (sizeIt != ctx.sizeAsserts.end())
|
||||
return sizeIt.value();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
|
||||
const QVector<ParsedField>& fields) {
|
||||
int computedOffset = 0;
|
||||
@@ -959,8 +1199,11 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
|
||||
int groupOffset;
|
||||
if (ctx.useCommentOffsets && field.commentOffset >= 0)
|
||||
groupOffset = field.commentOffset - baseOffset;
|
||||
else
|
||||
else {
|
||||
int bfAlign = fieldNaturalAlignment(field, ctx);
|
||||
computedOffset = alignUp(computedOffset, bfAlign);
|
||||
groupOffset = computedOffset;
|
||||
}
|
||||
int startIdx = fi;
|
||||
int totalBits = 0;
|
||||
while (fi < fields.size() && fields[fi].bitfieldWidth >= 0) {
|
||||
@@ -982,8 +1225,11 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
|
||||
int unionOffset;
|
||||
if (ctx.useCommentOffsets && field.commentOffset >= 0)
|
||||
unionOffset = field.commentOffset - baseOffset;
|
||||
else
|
||||
else {
|
||||
int uAlign = fieldNaturalAlignment(field, ctx);
|
||||
computedOffset = alignUp(computedOffset, uAlign);
|
||||
unionOffset = computedOffset;
|
||||
}
|
||||
|
||||
Node unionNode;
|
||||
unionNode.kind = NodeKind::Struct;
|
||||
@@ -1013,8 +1259,11 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
|
||||
int fieldOffset;
|
||||
if (ctx.useCommentOffsets && field.commentOffset >= 0)
|
||||
fieldOffset = field.commentOffset - baseOffset;
|
||||
else
|
||||
else {
|
||||
int fAlign = fieldNaturalAlignment(field, ctx);
|
||||
computedOffset = alignUp(computedOffset, fAlign);
|
||||
fieldOffset = computedOffset;
|
||||
}
|
||||
|
||||
// Resolve type
|
||||
auto typeIt = ctx.typeTable.find(field.typeName);
|
||||
@@ -1022,8 +1271,27 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
|
||||
|
||||
// Pointer field
|
||||
if (field.isPointer) {
|
||||
NodeKind ptrKind = (ctx.ptrSize >= 8) ? NodeKind::Pointer64 : NodeKind::Pointer32;
|
||||
|
||||
// Array of pointers: PVOID arr[N]
|
||||
if (!field.arraySizes.isEmpty()) {
|
||||
int totalElements = 1;
|
||||
for (int dim : field.arraySizes) totalElements *= (dim > 0 ? dim : 1);
|
||||
|
||||
Node n;
|
||||
n.kind = (ctx.ptrSize >= 8) ? NodeKind::Pointer64 : NodeKind::Pointer32;
|
||||
n.kind = NodeKind::Array;
|
||||
n.name = field.name;
|
||||
n.parentId = parentId;
|
||||
n.offset = fieldOffset;
|
||||
n.arrayLen = totalElements;
|
||||
n.elementKind = ptrKind;
|
||||
ctx.tree.addNode(n);
|
||||
computedOffset = fieldOffset + totalElements * ctx.ptrSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
Node n;
|
||||
n.kind = ptrKind;
|
||||
n.name = field.name;
|
||||
n.parentId = parentId;
|
||||
n.offset = fieldOffset;
|
||||
@@ -1098,7 +1366,7 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
|
||||
if (firstDim <= 0) firstDim = 1;
|
||||
|
||||
if (baseKind == NodeKind::Int8 && field.arraySizes.size() == 1 &&
|
||||
field.typeName == QStringLiteral("char")) {
|
||||
field.typeName == QStringLiteral("char") && firstDim <= 128) {
|
||||
Node n;
|
||||
n.kind = NodeKind::UTF8;
|
||||
n.name = field.name;
|
||||
@@ -1111,7 +1379,8 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
|
||||
}
|
||||
|
||||
if (baseKind == NodeKind::UInt16 && field.arraySizes.size() == 1 &&
|
||||
(field.typeName == QStringLiteral("wchar_t") || field.typeName == QStringLiteral("WCHAR"))) {
|
||||
(field.typeName == QStringLiteral("wchar_t") || field.typeName == QStringLiteral("WCHAR")) &&
|
||||
firstDim <= 128) {
|
||||
Node n;
|
||||
n.kind = NodeKind::UTF16;
|
||||
n.name = field.name;
|
||||
@@ -1165,6 +1434,8 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
|
||||
|
||||
// Struct-type field
|
||||
if (isStructType) {
|
||||
int elemSize = structTypeSize(field.typeName, ctx);
|
||||
|
||||
if (!field.arraySizes.isEmpty()) {
|
||||
int totalElements = 1;
|
||||
for (int dim : field.arraySizes) totalElements *= (dim > 0 ? dim : 1);
|
||||
@@ -1182,6 +1453,8 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
|
||||
int nodeIdx = ctx.tree.addNode(n);
|
||||
uint64_t nodeId = ctx.tree.nodes[nodeIdx].id;
|
||||
ctx.pendingRefs.append({nodeId, field.typeName});
|
||||
if (elemSize > 0)
|
||||
computedOffset = fieldOffset + totalElements * elemSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1196,6 +1469,8 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
|
||||
int nodeIdx = ctx.tree.addNode(n);
|
||||
uint64_t nodeId = ctx.tree.nodes[nodeIdx].id;
|
||||
ctx.pendingRefs.append({nodeId, field.typeName});
|
||||
if (elemSize > 0)
|
||||
computedOffset = fieldOffset + elemSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1271,7 +1546,7 @@ NodeTree importFromSource(const QString& sourceCode, QString* errorMsg, int poin
|
||||
enumNames.insert(ps.name);
|
||||
}
|
||||
|
||||
BuildContext ctx{tree, typeTable, classIds, pendingRefs, useCommentOffsets, enumNames, pointerSize};
|
||||
BuildContext ctx{tree, typeTable, classIds, pendingRefs, useCommentOffsets, enumNames, pointerSize, parser.sizeAsserts, parser.structAlignments};
|
||||
|
||||
// Build nodes for each struct/enum
|
||||
for (const auto& ps : parser.structs) {
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
<file alias="symbol-ruler.svg">vsicons/symbol-ruler.svg</file>
|
||||
<file alias="settings-gear.svg">vsicons/settings-gear.svg</file>
|
||||
<file alias="chevron-down.svg">vsicons/chevron-down.svg</file>
|
||||
<file alias="chevron-right.svg">vsicons/chevron-right.svg</file>
|
||||
<file alias="folder.svg">vsicons/folder.svg</file>
|
||||
<file alias="symbol-enum.svg">vsicons/symbol-enum.svg</file>
|
||||
<file alias="symbol-class.svg">vsicons/symbol-class.svg</file>
|
||||
|
||||
32
src/themes/defaults/modern.json
Normal file
32
src/themes/defaults/modern.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "Modern",
|
||||
"background": "#0e1117",
|
||||
"backgroundAlt": "#12151c",
|
||||
"surface": "#181d27",
|
||||
"border": "#1e2533",
|
||||
"borderFocused": "#4fc3f7",
|
||||
"button": "#1e2433",
|
||||
"text": "#a8bbd0",
|
||||
"textDim": "#7a8fa8",
|
||||
"textMuted": "#566278",
|
||||
"textFaint": "#3d4d6a",
|
||||
"hover": "#1e2433",
|
||||
"selected": "#232a3a",
|
||||
"selection": "#1a4a5e",
|
||||
"syntaxKeyword": "#9d8cff",
|
||||
"syntaxNumber": "#f0c060",
|
||||
"syntaxString": "#26c6b3",
|
||||
"syntaxComment": "#566278",
|
||||
"syntaxPreproc": "#f472b6",
|
||||
"syntaxType": "#4fc3f7",
|
||||
"indHoverSpan": "#f0c060",
|
||||
"indCmdPill": "#12151c",
|
||||
"indDataChanged": "#6bda8a",
|
||||
"indHeatCold": "#f0c060",
|
||||
"indHeatWarm": "#e8946a",
|
||||
"indHeatHot": "#ff6b6b",
|
||||
"indHintGreen": "#2a5e3a",
|
||||
"markerPtr": "#ff6b6b",
|
||||
"markerCycle": "#f0c060",
|
||||
"markerError": "#3a1a1a"
|
||||
}
|
||||
@@ -498,9 +498,9 @@ void TestImportSource::computedOffsets() {
|
||||
auto kids = childrenOf(tree, tree.nodes[0].id);
|
||||
QCOMPARE(kids.size(), 4);
|
||||
QCOMPARE(tree.nodes[kids[0]].offset, 0); // uint8_t at 0
|
||||
QCOMPARE(tree.nodes[kids[1]].offset, 1); // uint16_t at 1
|
||||
QCOMPARE(tree.nodes[kids[2]].offset, 3); // uint32_t at 3
|
||||
QCOMPARE(tree.nodes[kids[3]].offset, 7); // uint64_t at 7
|
||||
QCOMPARE(tree.nodes[kids[1]].offset, 2); // uint16_t at 2 (aligned)
|
||||
QCOMPARE(tree.nodes[kids[2]].offset, 4); // uint32_t at 4 (aligned)
|
||||
QCOMPARE(tree.nodes[kids[3]].offset, 8); // uint64_t at 8 (aligned)
|
||||
}
|
||||
|
||||
void TestImportSource::mixedOffsetsAutoDetect() {
|
||||
@@ -805,7 +805,7 @@ void TestImportSource::bitfieldSkipped() {
|
||||
QCOMPARE(tree.nodes[kids[1]].bitfieldMembers[1].bitWidth, (uint8_t)12);
|
||||
QCOMPARE(tree.nodes[kids[1]].bitfieldMembers[1].bitOffset, (uint8_t)4);
|
||||
QCOMPARE(tree.nodes[kids[2]].name, QStringLiteral("after"));
|
||||
QCOMPARE(tree.nodes[kids[2]].offset, 6);
|
||||
QCOMPARE(tree.nodes[kids[2]].offset, 8); // aligned to uint32_t boundary
|
||||
}
|
||||
|
||||
void TestImportSource::bitfieldWithOffsetsEmitsHex() {
|
||||
|
||||
Reference in New Issue
Block a user